java多数据源动态切换的核心是自定义@targetdatasource注解+abstractroutingdatasource路由+aop拦截。通过threadlocal存取数据源标识,aop在方法执行前设置、后清理,配合配置多数据源bean并设为@primary,实现纯spring原生方案。

Java 中使用注解实现多数据源动态切换,核心是结合 Spring 的 @TargetDataSource(自定义注解)+ AOP 拦截 + AbstractRoutingDataSource 动态路由。不依赖第三方框架(如 dynamic-datasource),纯 Spring 原生方式即可完成。
1. 定义数据源切换注解
创建一个运行时保留、可作用于方法上的自定义注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
String value(); // 如 "master" 或 "slave"
}
这个注解将标记在 Service 方法上,告诉系统该方法应使用哪个数据源。
2. 实现动态数据源路由类
继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey(),从 ThreadLocal 中获取当前线程绑定的数据源标识:
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
配套的上下文工具类(用于存取当前数据源 key):
public class DataSourceContextHolder {
private static final ThreadLocal<string> contextHolder = new ThreadLocal();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}</string>
3. 编写 AOP 切面自动切换数据源
用 @Aspect 拦截带 @TargetDataSource 的方法,在执行前设置数据源 key,结束后清理:
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object switchDataSource(ProceedingJoinPoint joinPoint,
TargetDataSource targetDataSource) throws Throwable {
String dataSourceKey = targetDataSource.value();
DataSourceContextHolder.setDataSourceType(dataSourceKey);
try {
return joinPoint.proceed();
} finally {
DataSourceContextHolder.clearDataSourceType();
}
}
}
注意:必须确保 AOP 切面生效(开启 @EnableAspectJAutoProxy),且切点能覆盖到实际调用的方法(推荐切 Service 层,避免事务代理失效问题)。
4. 配置多个数据源并注册为动态主数据源
在配置类中定义 master/slave 数据源 Bean,并组装进 DynamicRoutingDataSource:
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DataSource dynamicDataSource() {
DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
Map<object object> dataSourceMap = new HashMap();
dataSourceMap.put("master", masterDataSource());
dataSourceMap.put("slave", slaveDataSource());
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource()); // 默认走 master
return routingDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}</object>
application.yml 示例:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/db_master
username: root
password: 123456
slave:
url: jdbc:mysql://localhost:3306/db_slave
username: root
password: 123456
关键注意事项
-
事务与数据源切换冲突:如果方法加了
@Transactional,务必确保 AOP 切面顺序早于事务切面(可用@Order(Ordered.HIGHEST_PRECEDENCE)控制);否则事务可能已基于默认数据源开启,再切换无效。 -
ThreadLocal 生命周期管理:必须在 finally 块中
clear(),防止线程复用(如 Tomcat 线程池)导致数据源“串话”。 -
只对代理对象生效:自调用(this.method())不会触发 AOP,注解会失效;需通过注入自身或使用
AopContext.currentProxy()解决。 -
读写分离建议:可进一步封装
@ReadOnly注解,配合规则自动路由到 slave,减少手动指定。
这套机制轻量、可控、易调试,适合中小项目快速落地多数据源需求。只要理清 ThreadLocal + 路由 + AOP 三者协作逻辑,实现并不复杂。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











