java注解本身不实现动态数据源切换,而是作为声明式标记,配合aop与abstractroutingdatasource协作完成路由:定义@targetdatasource注解标记方法、通过aop将值存入threadlocal、routingdatasource读取该值决定实际数据源。

Java 中注解本身不直接“实现”动态数据源切换,而是作为**声明式标记**,配合 AOP(如 Spring AOP)或代理机制,在方法/类执行前解析注解、决定使用哪个数据源,并通过 AbstractRoutingDataSource 完成实际路由。核心在于“注解 + 切面 + 路由数据源”三者协作。
定义自定义注解标记数据源
创建一个运行时保留的注解,用于标注需要切换数据源的方法或类:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
String value() default "master"; // 默认主库
}
例如在 Service 方法上使用:
@Service
public class UserService {
@TargetDataSource("slave1")
public List<user> findAllUsers() { ... }
@TargetDataSource("master")
public void updateUser(User user) { ... }
}
</user>
基于 AbstractRoutingDataSource 实现动态路由
Spring 提供的 AbstractRoutingDataSource 是关键组件,它通过重写 determineCurrentLookupKey() 方法返回当前数据源标识(如 "master"),容器据此从配置的 targetDataSources 中查找对应数据源:
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slave1DataSource") DataSource slave1,
@Qualifier("slave2DataSource") DataSource slave2) {
AbstractRoutingDataSource routing = new AbstractRoutingDataSource();
Map<object object> targets = new HashMap();
targets.put("master", master);
targets.put("slave1", slave1);
targets.put("slave2", slave2);
routing.setTargetDataSources(targets);
routing.setDefaultTargetDataSource(master); // 默认走 master
return routing;
}
}</object>
注意:必须确保该 Bean 是事务管理器(DataSourceTransactionManager)所管理的唯一数据源。
用 AOP 解析注解并设置路由键
通过环绕通知(@Around)拦截带 @TargetDataSource 的方法,提取注解值,并将其存入线程上下文(如 ThreadLocal),供 AbstractRoutingDataSource 读取:
@Aspect
@Component
public class DataSourceAspect {
private final static ThreadLocal<string> dataSourceKey = new ThreadLocal();
@Around("@annotation(targetDataSource)")
public Object routeDataSource(ProceedingJoinPoint pjp, TargetDataSource targetDataSource) throws Throwable {
String key = targetDataSource.value();
dataSourceKey.set(key);
try {
return pjp.proceed();
} finally {
dataSourceKey.remove(); // 防止内存泄漏和脏数据
}
}
public static String getDataSourceKey() {
return dataSourceKey.get();
}
}</string>
然后在 AbstractRoutingDataSource 子类中重写方法:
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceAspect.getDataSourceKey();
}
}
注意事项与常见问题
-
事务方法需谨慎:若方法加了
@Transactional,且事务管理器已绑定某个数据源,则注解切换可能失效;建议将@Transactional和@TargetDataSource分离到不同方法,或确保事务传播行为兼容(如REQUIRES_NEW) -
切面优先级:若同时存在多个切面(如日志、权限),需用
@Order明确DataSourceAspect在事务切面之前执行 - 多数据源配置:所有真实数据源(master/slave)需独立配置(如 HikariCP),不能共用同一连接池
-
读写分离场景:可结合方法名前缀(如
select*自动走 slave)+ 注解覆盖,提升灵活性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











