spring数据库主从配置
Jacky
Java
2021-11-22
16
配置文件
application.yml文件配置
spring:
datasources:
# 默认连接数据源配置
default-type: slave
# 主数据源参数配置
master:
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库连接地址
url: jdbc:mysql://127.0.0.1:3306/blog
# 数据库用户名
username: root
# 数据库密码
password: root
# 从数据源参数配置
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/blog
username: root
password: root
数据源标记
public enum DbType {
/**
* 主数据源标记
*/
master,
/**
* 从数据源标记
*/
slave
}
配置类
import lombok.Data;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@EnableConfigurationProperties
@ConfigurationProperties("spring.datasources")
public class Config {
/**
* 主数据源配置
*/
DataSourceProperties master;
/**
* 从数据源配置
*/
DataSourceProperties slave;
/**
* 默认连接数据源配置
*/
DbType defaultType = DbType.slave;
}
数据源
由于在代码中库的实例需要动态选择,因此我们继承AbstractRoutingDataSource类来聚合多个数据源 通过实现determineCurrentLookupKey方法来筛选我们需要的数据源
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 数据源
*/
@Slf4j
public class DbRouting extends AbstractRoutingDataSource {
/**
* 默认
*/
private DbType def;
public DbRouting(DbType def) {
this.def = def;
}
@Override
protected Object determineCurrentLookupKey() {
DbType type = DbContextHolder.get().orElse(def);
log.debug("连接的数据库是:{}", type);
return type;
}
}
数据库切换标记类
import java.lang.annotation.*;
/**
* 切换数据库标记<br/>
* 默认指向主库标记
**/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DB {
DbType type() default DbType.master;
}
数据库切换控制类
使用面向切面编程的方式,通过声明注解 ** @DB **,来拦截选择master和slave库
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Optional;
/**
* 数据库切换控制类
*/
@Slf4j
@Aspect
@Order(0)
@Component
public class DbContextHolder {
private static final ThreadLocal<DbType> LOCAL = new ThreadLocal<>();
/**
* 定义切点
*/
@Pointcut("@annotation(com.tanshuyi.db.DB)")
public void pointcut() {
}
/**
* 前置通知,做一些参数的预处理,不能阻止进入切点除非报错
*/
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint) {
DB db = getDB(joinPoint);
if (db != null) {
enableSlaveOrMaster(db.type());
log.debug("切换到:{}", db.type());
} else {
log.debug("继续使用默认库");
}
}
/**
* 后置通知,切点执行后执行,就像 finally
*/
@After("pointcut()")
public void doAfter(JoinPoint joinPoint) {
DB db = getDB(joinPoint);
if (db != null) {
closeSlaveOrMaster();
log.debug("切换回默认库");
}
}
public DB getDB(JoinPoint joinPoint) {
try {
Object target = joinPoint.getTarget();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
if (method.isAnnotationPresent(DB.class)) {
return method.getAnnotation(DB.class);
} else if (target.getClass().isAnnotationPresent(DB.class)) {
return target.getClass().getAnnotation(DB.class);
}
} catch (Exception e) {
log.error("获取注释失败:", e);
}
return null;
}
/**
* 获取当前需要使用的库标记
*/
public static Optional<DbType> get() {
return Optional.ofNullable(LOCAL.get());
}
/**
* 启用库
*/
public void enableSlaveOrMaster(DbType dbType) {
LOCAL.set(dbType);
}
/**
* 关闭
*/
public void closeSlaveOrMaster() {
LOCAL.remove();
}
}
数据源声明类
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
public class DbConfig {
@Bean
public DataSource dataSource(Config config) {
log.debug("默认连接数据库:{}", config.getDefaultType());
Assert.notNull(config.master, "必须配置主数据库");
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DbType.master, getDataSource(config.master));
if (config.slave != null) {
targetDataSources.put(DbType.slave, getDataSource(config.slave));
} else {
targetDataSources.put(DbType.slave, targetDataSources.get(DbType.master));
}
DbRouting dbRouting = new DbRouting(config.getDefaultType());
dbRouting.setTargetDataSources(targetDataSources);
return dbRouting;
}
/**
* 创建库的datasource
**/
private DataSource getDataSource(DataSourceProperties pp) {
DataSourceBuilder<?> builder = DataSourceBuilder.create();
builder.driverClassName(pp.getDriverClassName());
builder.url(pp.getUrl());
builder.username(pp.getUsername());
builder.password(pp.getPassword());
return builder.build();
}
}
应用实例
@Service
public class TypeServiceImpl{
@Resource
private TypeMapper typeMapper;
...
@DB
public int updateType(Type type) {
return typeMapper.updateById(type);
}
public Type getType(Long id) {
return typeMapper.selectById(id);
}
...
}