一、配置 application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.100:7001/xxxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: xxx
password: xxx
slave1:
# 从数据源开关/默认关闭
enabled: true
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.100:7001/xxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: xxx
password: xxx
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
rewriteBatchedStatements: true
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
login-username: xxx
login-password: xxx
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
二、配置类实现多数据源配置
@Configuration
@Slf4j
public class MultiDataSourceConfig {
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
@Bean
@Primary
@DependsOn("primaryDataSource")
public DataSource dynamicDataSource(@Qualifier(DataSourceType.PRIMARY) DataSource primaryDataSource,
@Qualifier(DataSourceType.SECOND) DataSource secondDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 1.设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
// 2.配置多数据源
Map<Object, Object> map = new HashMap<>();
map.put(DataSourceType.PRIMARY, primaryDataSource);
map.put(DataSourceType.SECOND, secondDataSource);
// 3.存放数据源集
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
@Bean(name = DataSourceType.PRIMARY)
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource primaryDataSource() {
log.info("数据库1连接池创建中.......");
return DruidDataSourceBuilder.create().build();
}
@Bean(name = DataSourceType.SECOND)
@ConfigurationProperties(prefix = "spring.datasource.druid.slave1")
public DataSource secondDataSource() {
log.info("数据库2连接池创建中.......");
return DruidDataSourceBuilder.create().build();
}
}
三、实现自定义注解
自定义@DataSource注解,用于动态指定方法或整个类执行时应用的数据源。它接受一个字符串参数value,该参数表示目标数据源的名称。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface DataSource {
String value() default DataSourceType.PRIMARY;
}
四、定义DataSourceAspect切面类
利用AOP(面向切面编程)技术,在方法执行前动态切换数据源。该切面通过环绕通知(@Around)拦截所有被@DataSource注解标记的方法或类,并在方法执行前后设置和清除相应的数据源。
@Aspect
@Order(value=1)
@Component
@Slf4j
public class DataSourceAspect {
/** 定义切入点表达式*/
@Pointcut("@annotation(com.example.zsj.api.aop.DataSource) || @within(com.example.zsj.api.aop.DataSource) ")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 尝试获取方法上的@DataSource注解
DataSource methodAnnotation = method.getAnnotation(DataSource.class);
// 获取目标类上的@DataSource注解
DataSource classAnnotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
// 默认数据源名称
String dataSource = DataSourceType.PRIMARY;
if (methodAnnotation != null) {
// 方法级别注解优先
dataSource = methodAnnotation.value();
} else if (classAnnotation != null) {
// 类级别注解作为备选
dataSource = classAnnotation.value();
}
try {
// 设置数据源
DataSourceContextHolder.setDataSource(dataSource);
// 继续执行目标方法
return joinPoint.proceed();
} finally {
// 清除线程局部变量中的数据源设置
DataSourceContextHolder.clearDataSourceType();
}
}
}
五、定义数据库源枚举类
public class DataSourceType {
public static final String PRIMARY = "primaryDataSource";
public static final String SECOND = "secondDataSource";
}
五、定义DataSourceContextHolder工具类存储和获取当前线程数据源的上下文
使用ThreadLocal来存储当前线程使用的数据源标识符。它提供了设置(setDataSource)、获取(getDataSource)和清除(clearDataSourceType)当前线程数据源标识符的方法,确保每个线程都能独立地选择自己的数据源。
public class DataSourceContextHolder {
/**
* 创建FastThreadLocal对象,存储当前线程的数据源信息
*/
private static final FastThreadLocal<String> CONTEXT_HOLDER = new FastThreadLocal<String>();
/**
* 设置数据源
*/
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
/**
* 获取数据源
*/
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
* 清除数据源
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
六、重写determineCurrentLookupKey方法
在每次数据库操作之前,基于当前线程保存的数据源标识符(通过DataSourceContextHolder获取)来动态决定使用哪个数据源。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}