动态数据源切换——@DS注解源码解析
1.前言
多数据源读写是我们日常开发工作中不可避免的场景,手动定义 Datasource 和 SqlSessionTemplate 不仅繁琐而且不够优雅。dynamic-datasource这个项目你可能没听过,但是作者团队的另一个项目你肯不陌生,那就是MyBatis-Plus。废话不多说,直接进入正题,本文讨论@DS
注解如何实现动态切换数据源。
2.如何使用
@DS
注解在使用时需要加在类或者方法上。以下是官方示例:
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
3.数据源动态切换实现原理
1)自动装配:
2)跟随DynamicDataSourceAutoConfiguration
类进入到DynamicDataSourceAopConfiguration
,可以看到注册了两个切面通知
3)接下来介绍主角:DynamicRoutingDataSource
,其在扩展点InitializingBean
处加载yml配置文件中的数据源,并调用addDataSource(String ds, DataSource dataSource)
方法将数据源存入dataSourceMap
中:
4)通过DynamicDataSourceAnnotationInterceptor
拦截器增强带有@DS
注解的方法,将注解中的值(数据源名)存入holder由线程携带
5)DynamicDataSourceContextHolder
是由ThreadLocal实现的存放数据源的栈:
6)由于是AbstractRoutingDataSource
实现DataSource
,所以在获取数据库连接时(执行getConnection()方法)会调用其实现类DynamicRoutingDataSource#getConnection
:
解释一下为什么用栈:首先因为holder是ThreadLocal类型跟随线程,其次方法中假设存在嵌套调用,即methodA->methodB,这俩方法需要切换不同的数据源,此时就需要栈先进后出,因为线程也会进入不同的栈帧,随着方法进入到methodB,取出拦截
@DS
注解时后入栈的数据源名,进而执行其对应的数据库操作。
7)在getDataSource()
中可以看到,如果上次入栈的数据源名为空,就取默认数据源,否则从dataSourceMap
中获取对应的数据源(自动装配时存入),此时完成数据源的切换:
8)接着代理执行方法invocation.proceed()
,然后出栈,以免影响后面的操作:
@DS
动态切换数据源的原理到这里就结束了。最后附上DynamicDatasource的配置格式,需要注意的是,如果同时还使用了Druid,需要在配置中排除其自动装配,否则会报错:Failed to determine a suitable driver class
server:
port: 8081
spring:
datasource:
dynamic:
primary: db1
datasource:
db1:
url: jdbc:mysql://localhost:3306/message?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: zhaobo
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
db2:
url: jdbc:mysql://192.168.25.131:3306/userdb?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: zhaobo
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
max-active: 10
min-idle: 5
max-wait: 60000
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。