如何使用自定义@DS注解切换数据源
目录
方法一:使用Spring的@Transactional注解与动态数据源
1.定义多个数据源:
2.配置动态数据源:
3.实现动态数据源类:
4.使用AOP切换数据源:
5.创建一个自定义注解@DS。
6.使用自定义 @DS 注解切换
⚠️ 手动方案的重要缺陷
生产环境建议
总结
在Spring框架中,通过使用@DS
注解切换数据源是一种常见需求,尤其是在使用多数据源的微服务架构中。@DS
并不是Spring框架原生提供的注解,但可以通过一些方式实现类似功能。通常,我们可以借助AOP(面向切面编程)和动态数据源的实现来达到切换数据源的目的。
方法一:使用Spring的@Transactional
注解与动态数据源
1.定义多个数据源:
首先,定义你的数据源配置。
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource firstDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.druid.slave")@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")public DataSource secondDataSource() {return DataSourceBuilder.create().build();}
}
你的yml中也要有相应的数据库连接信息
spring:# 数据源配置datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:# 从数据源oracle: # Oracle从库url: jdbc:oracle:thin:@//localhost:1521/ORCLusername: scottpassword: tigerdriver-class-name: oracle.jdbc.OracleDriver # 必须指定!# mysql主库数据源master:url: jdbc:mysql://127.0.0.1:3306/axhyh-xs-local?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver # 必须指定!# 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsewebStatFilter:enabled: truestatViewServlet:enabled: true# 设置白名单,不填则允许所有访问allow:url-pattern: /druid/*# 控制台管理用户名和密码login-username:login-password:filter:stat:enabled: true# 慢SQL记录log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: true
-
2.配置动态数据源:
创建一个动态数据源的Bean。
@Configuration
public class DynamicDataSourceConfig {@Autowiredprivate DataSource firstDataSource;@Autowiredprivate DataSource secondDataSource;@Beanpublic DataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();Map<Object, Object> dataSourceMap = new HashMap<>();dataSourceMap.put("dataSource1", firstDataSource);dataSourceMap.put("dataSource2", secondDataSource);dynamicDataSource.setTargetDataSources(dataSourceMap);dynamicDataSource.setDefaultTargetDataSource(firstDataSource); // 默认数据源return dynamicDataSource;}
}
3.实现动态数据源类:
创建一个动态数据源类。
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> dataSourceKey = new NamedThreadLocal<>("dataSourceKey");public static void setDataSourceKey(String key) {dataSourceKey.set(key);}public static String getDataSourceKey() {return dataSourceKey.get();}public static void clearDataSourceKey() {dataSourceKey.remove();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSourceKey();}
}
4.使用AOP切换数据源:
通过AOP在方法执行前后切换数据源。
@Aspect
@Component
public class DataSourceAspect {@Before("@annotation(ds)") // 在有@DS注解的方法执行前切换数据源public void changeDataSource(JoinPoint point, DS ds) throws Throwable {String dsId = ds.value(); // 获取注解中的值,即数据源标识符DynamicDataSource.setDataSourceKey(dsId); // 切换数据源}@After("@annotation(ds)") // 在有@DS注解的方法执行后清除数据源标识符,以避免影响其他方法执行时的数据源选择。public void clearDataSource(JoinPoint point, DS ds) {DynamicDataSource.clearDataSourceKey(); // 清除数据源标识符,恢复默认或上一个数据源设置。}
}
5.创建一个自定义注解@DS
。
@Target(ElementType.METHOD) // 指定注解可以应用于方法上。
@Retention(RetentionPolicy.RUNTIME) // 运行时保留注解信息。
public @interface DS { String value(); // 数据源标识符,例如 "dataSource1", "dataSource2" 等。 需要在配置中定义。 对应到具体的dataSourceMap的key值。 例如:@DS("dataSource1") 表示使用第一个数据源。 需要在DynamicDataSource的dataSourceMap中设置对应的key值。 例如:dataSourceMap.put("dataSource1", firstDataSource); 对应这里的"dataSource1"。 确保这里的key值与@DS注解中的value值一致。这样在切换数据源时才能正确匹配到对应的数据源。 例如:@DS("dataSource1") 表示使用第一个数据源。 需要在DynamicDataSource的dataSourceMap中设置对应的key值。 例如:dataSourceMap.put("dataSource1", firstDataSource); 这样在切换数据源时才能正确匹配到对应的数据源。 确保这里的key值与@DS注解中的value值一致。这样在切换数据
6.使用自定义 @DS
注解切换
// 使用MySQL数据源
@Service
public class UserService {@DS("master") // 切换到MySQL数据源public List<User> getUsers() {return userMapper.selectList(); // 操作MySQL}
}// 使用Oracle数据源
@Service
public class OrderService {@DS("oracle") // 切换到Oracle数据源public List<Order> getOrders() {return orderMapper.selectList(); // 操作Oracle}
}
⚠️ 手动方案的重要缺陷
虽然能实现基础切换,但相比配置多数据源dynamic-datasource存在明显短板:
问题 | 手动方案(配置一) | 配置二(dynamic-datasource) |
---|---|---|
多数据库驱动冲突 | 需手动指定driver-class-name (易遗漏) | 自动隔离驱动 |
连接池配置继承 | 需为每个数据源单独配置连接池参数 | 通过spring.datasource.druid 全局统一配置 |
分布式事务支持 | 需自研Seata/XA集成(复杂) | dynamic.seata: true 一键开启 |
读写分离 | 需手写AOP逻辑 | 内置@DS("slave") 自动负载均衡 |
多租户支持 | 需完全自研 | 内置多租户路由策略 |
监控集成 | 需单独配置每个Druid监控 | 自动聚合所有数据源监控 |
生产环境建议
-
简单场景(开发/测试环境)
可用手动方案快速验证多数据库切换,但注意:- 严格检查所有数据源的
driver-class-name
- 为每个数据源重复配置连接池参数(易错!)
- 严格检查所有数据源的
-
生产环境严肃建议 → 采用配置二(dynamic-datasource)
添加依赖后直接配置:
spring:datasource:dynamic:primary: mysql # 默认数据源datasource:mysql: url: jdbc:mysql://localhost:3306/dboracle:url: jdbc:oracle:thin:@//localhost:1521/ORCLdruid: # 全局连接池配置initial-size: 5max-active: 20# ...其他参数自动继承到所有数据源
使用内置注解无感切换:
@DS("mysql") // 无需自研注解
public void mysqlQuery() { ... }@DS("oracle") // 自动处理驱动隔离和连接池
public void oracleUpdate() { ... }
总结
- 能实现但繁琐:手动方案可通过显式声明驱动和重复配置实现多库切换
- 生产级差距:缺少连接池继承、驱动自动隔离、分布式事务等关键能力
- 终极建议:优先使用 dynamic-datasource,它专为解决此类问题设计,可节省 80% 的调试成本。尤其涉及 Oracle 等商业数据库时,驱动兼容性问题在手动方案中极易引发生产事故。