在Java中通过自定义注解@TargetDataSource结合AOP与AbstractRoutingDataSource实现动态多数据源切换:定义注解→继承AbstractRoutingDataSource重写determineCurrentLookupKey从ThreadLocal取值→用@Around切面在方法执行前设数据源键、结束后清理→注意事务顺序、嵌套调用及连接泄漏。

在 Java 中,通过自定义注解实现动态多数据源切换,核心思路是:定义一个标记注解(如 @TargetDataSource),结合 Spring 的 AOP 或事务管理器扩展机制,在方法执行前根据注解值切换当前线程绑定的数据源(通常基于 AbstractRoutingDataSource 实现的路由数据源)。
1. 定义自定义注解
注解用于标记需要指定数据源的方法或类,保留策略为 RUNTIME,作用范围通常是方法和类型:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "primary"; // 默认使用主数据源
}
2. 实现动态数据源路由
继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey() 方法,从 ThreadLocal 中获取当前线程绑定的数据源标识:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
dataSourceKey.set(key);
}
public static void clearDataSourceKey() {
dataSourceKey.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
该类需配置为 Spring Bean,并将多个真实数据源(如 primaryDataSource、slaveDataSource)注入其 targetDataSources 映射中,同时设置默认数据源(defaultTargetDataSource)。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
3. 用 AOP 拦截注解并切换数据源
编写切面,在目标方法执行前读取 @TargetDataSource 注解值,并调用 DynamicDataSource.setDataSourceKey(...);方法结束后清理 ThreadLocal(尤其注意异常场景):
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object switchDataSource(ProceedingJoinPoint joinPoint,
TargetDataSource targetDataSource) throws Throwable {
String key = targetDataSource.value();
DynamicDataSource.setDataSourceKey(key);
try {
return joinPoint.proceed();
} finally {
DynamicDataSource.clearDataSourceKey();
}
}
}
确保该切面能生效,需开启 AOP 支持(@EnableAspectJAutoProxy)且切点覆盖到被注解的 Service 方法(避免在事务方法内部被代理丢失)。
4. 注意事项与常见问题
-
事务与数据源切换顺序:若方法加了
@Transactional,Spring 事务代理会在 AOP 前触发,此时数据源尚未切换 → 应让数据源切换切面优先于事务切面(用@Order(Ordered.HIGHEST_PRECEDENCE))。 -
嵌套调用失效:同一类内方法调用不会走代理,注解不生效 → 避免本类调用,或通过
ApplicationContext获取代理对象调用。 -
读写分离场景:可约定注解值如
"read"/"write",配合不同数据源配置;也可结合方法名(如 select 开头自动走从库),但注解方式更显式可控。 -
连接泄漏风险:务必在
finally块中清理 ThreadLocal,防止线程复用导致数据源错乱。

















