动态数据源切换——@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)自动装配:

image-20240116184508599

image-20240116184945397

2)跟随DynamicDataSourceAutoConfiguration类进入到DynamicDataSourceAopConfiguration,可以看到注册了两个切面通知

image-20240116185333877

image-20240116185801260

3)接下来介绍主角:DynamicRoutingDataSource,其在扩展点InitializingBean处加载yml配置文件中的数据源,并调用addDataSource(String ds, DataSource dataSource)方法将数据源存入dataSourceMap中:

image-20240116191159315

image-20240116191228018

4)通过DynamicDataSourceAnnotationInterceptor拦截器增强带有@DS注解的方法,将注解中的值(数据源名)存入holder由线程携带

image-20240114232645945

5)DynamicDataSourceContextHolder是由ThreadLocal实现的存放数据源的栈:

image-20240114231259994

6)由于是AbstractRoutingDataSource实现DataSource,所以在获取数据库连接时(执行getConnection()方法)会调用其实现类DynamicRoutingDataSource#getConnection

image-20240114232050187

image-20240114232342491

解释一下为什么用栈:首先因为holder是ThreadLocal类型跟随线程,其次方法中假设存在嵌套调用,即methodA->methodB,这俩方法需要切换不同的数据源,此时就需要栈先进后出,因为线程也会进入不同的栈帧,随着方法进入到methodB,取出拦截@DS注解时后入栈的数据源名,进而执行其对应的数据库操作。

7)在getDataSource()中可以看到,如果上次入栈的数据源名为空,就取默认数据源,否则从dataSourceMap中获取对应的数据源(自动装配时存入),此时完成数据源的切换:

image-20240114232836716

8)接着代理执行方法invocation.proceed(),然后出栈,以免影响后面的操作:

image-20240114234159785

@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

本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。

posted @ 2024-01-14 23:55  爱吃麦辣鸡翅  阅读(3419)  评论(2编辑  收藏  举报