java中注解本身不能直接切换数据源,需结合abstractroutingdatasource、threadlocal和自定义注解+aop实现:注解标记目标数据源→aop拦截存入threadlocal→abstractroutingdatasource读取并路由。

Java 中注解本身不能直接切换数据源,它只是元数据标记。真正实现动态数据源切换,需要结合 Spring 的 AbstractRoutingDataSource、线程局部变量(ThreadLocal)和自定义注解 + AOP(或拦截器)协同完成。核心思路是:用注解声明目标数据源标识 → AOP 拦截方法并把标识存入 ThreadLocal → AbstractRoutingDataSource 在每次获取连接时读取该标识并路由到对应数据源。
定义自定义注解标识数据源
创建一个运行时保留、可作用于方法上的注解,用于声明当前操作应使用的数据源名称(如 "master" 或 "slave1"):
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
String value() default "master"; // 默认主库
}
这个注解不带逻辑,只起标记作用,后续由 AOP 解析。
用 ThreadLocal 管理当前线程的数据源上下文
创建一个工具类,通过 ThreadLocal 存储当前线程要使用的数据源名:
public class DataSourceContextHolder {
private static final ThreadLocal<string> contextHolder = new ThreadLocal();
public static void setDataSource(String dataSourceName) {
contextHolder.set(dataSourceName);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}</string>
注意:每次方法执行完必须调用 clearDataSource(),否则可能污染后续请求(尤其在线程复用场景如 Tomcat 线程池中)。
实现 AbstractRoutingDataSource 动态路由
继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey() 方法,从 ThreadLocal 读取数据源名:
@Configuration
public class DynamicDataSourceConfig {
@Bean
@Primary
public DataSource dynamicDataSource(@Autowired DataSource masterDs,
@Autowired DataSource slaveDs) {
DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
Map<object object> targetDataSources = new HashMap();
targetDataSources.put("master", masterDs);
targetDataSources.put("slave", slaveDs);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDs); // 默认主库
return routingDataSource;
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}</object>
Spring 启动时会将这个 dynamicDataSource 注册为默认 DataSource Bean,所有 JdbcTemplate、MyBatis 的 SqlSessionFactory 都会自动使用它。
用 AOP 自动绑定注解与数据源上下文
编写切面,在方法执行前设置数据源名,执行后清理:
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object routeDataSource(ProceedingJoinPoint joinPoint,
TargetDataSource targetDataSource) throws Throwable {
String dataSourceName = targetDataSource.value();
DataSourceContextHolder.setDataSource(dataSourceName);
try {
return joinPoint.proceed();
} finally {
DataSourceContextHolder.clearDataSource(); // 关键:务必清理
}
}
}
这样,只要在 Service 方法上加 @TargetDataSource("slave"),AOP 就会在调用前切换上下文,且保证线程安全和资源隔离。
不复杂但容易忽略的是 ThreadLocal 清理时机——漏掉 clear() 会导致连接池复用线程时误用旧数据源,引发跨库写错、读写分离失效等问题。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











