首页 > 编程语言 >动态数据源 @DS 注解源码解析

动态数据源 @DS 注解源码解析

时间:2024-12-18 23:42:29浏览次数:3  
标签:DynamicRoutingDataSource 数据源 public xx 源码 dataSource DS

参考:动态数据源切换——@DS 注解源码解析

前言

借助 dynamic-datasource 可实现多数据源读写,其核心注解@DS用来动态切换数据源。

下面介绍@DS注解的实现原理。

如何使用

在 pom 中引入依赖:

<!-- spring-boot 1.5.x 2.x.x -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>${version}</version>
</dependency>

配置数据源:

spring:
  datasource:
    dynamic:
      enabled: true        # 启用动态数据源,默认 true
      primary: master      # 设置默认的数据源或者数据源组,默认值即为 master
      strict: false        # 严格匹配数据源,默认 false. true 未匹配到指定数据源时抛异常,false 使用默认数据源
      grace-destroy: false # 是否优雅关闭数据源,默认为 false,设置为 true 时,关闭数据源时如果数据源中还存在活跃连接,至多等待 10s 后强制关闭
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0 开始支持 SPI 可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        # 以上会配置一个默认库 master,一个组 slave 下有两个子库 slave_1 slave_2

使用@DS注解切换数据源,@DS注解可加在类或者方法上,方法上注解优先于类上注解。对于没有使用@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");
    }
}

实现原理

进入自动配置类DynamicDataSourceAutoConfiguration,可以看到其注册了两个切面通知:

image-20241217213257530
image-20241217213434381
image-20241217213510524

DynamicDataSourceAnnotationInterceptor拦截器用来增强带有@DS注解的方法,获取注解中的值(数据源名),交给DynamicDataSourceContextHolder管理:

image-20241217220837000

DynamicDataSourceContextHolder中维护了一个ThreadLocal<Deque<String>>,其中的 Deque 是用来存放数据源名称的栈:

public final class DynamicDataSourceContextHolder {

    private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
        @Override
        protected Deque<String> initialValue() {
            return new ArrayDeque<>();
        }
    };

    private DynamicDataSourceContextHolder() {
    }

    /**
     * 获得当前线程数据源
     */
    public static String peek() {
        return LOOKUP_KEY_HOLDER.get().peek();
    }

    /**
     * 设置当前线程数据源
     */
    public static String push(String ds) {
        String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;
        LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
        return dataSourceStr;
    }

    /**
     * 清空当前线程数据源
     */
    public static void poll() {
        Deque<String> deque = LOOKUP_KEY_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            LOOKUP_KEY_HOLDER.remove();
        }
    }

    /**
     * 强制清空本地线程
     */
    public static void clear() {
        LOOKUP_KEY_HOLDER.remove();
    }
}

DynamicDataSourceAutoConfiguration中,会在容器中注册DynamicRoutingDataSource

@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
    DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
    dataSource.setPrimary(properties.getPrimary());
    dataSource.setStrict(properties.getStrict());
    dataSource.setStrategy(properties.getStrategy());
    dataSource.setP6spy(properties.getP6spy());
    dataSource.setSeata(properties.getSeata());
    return dataSource;
}

DynamicRoutingDataSource继承自AbstractRoutingDataSourceAbstractRoutingDataSource继承自AbstractDataSourceAbstractDataSource实现了DataSource

DynamicRoutingDataSource最终会被注入到 MyBatis 等 ORM 框架中,比如MybatisAutoConfiguration用其初始化 SqlSessionFactory:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  factory.setDataSource(dataSource);
  // ...
}

继而用来初始化 Executor:

// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // ⭐ environment.getDataSource() 会获取到 DynamicRoutingDataSource
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

Executor 在执行 SQL 时会从中获取数据库连接:

// org.apache.ibatis.executor.BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
  // ⭐ 会从 DynamicRoutingDataSource 中获取数据源
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

回到DynamicRoutingDataSource,其InitializingBean方法会加载 yml 配置文件中的数据源,并调用addDataSource(String ds, DataSource dataSource)方法将数据源存入dataSourceMap中:

image-20241217213744162
image-20241217214352249

前面说了,在获取数据库连接时(执行DataSource#getConnection方法),最终会调用到DynamicRoutingDataSource#getConnection,进入该方法:

image-20241217222751310
image-20241217222853280

可以看到上面的DynamicDataSourceContextHolder.peek()会取到栈顶的数据源名称,所以DynamicRoutingDataSource#getConnection会返回栈顶的数据源。

这里解释一下为什么DynamicDataSourceContextHolder要用栈:为了支持嵌套切换,如 ABC 三个 service 都是不同的数据源,其中 A 的某个业务要调 B 的方法,B 的方法需要调用 C 的方法,一级一级调用切换,形成了链,此时就需要用栈保证先进后出。

image-20241217222946285

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

最后,在执行完invocation.proceed()之后,保证出栈,以免影响后面的操作:

image-20241217223905309

另,参考上面的代码,可以通过如下方式来实现手动切换数据源:

try {
    DynamicDataSourceContextHolder.push("ds1");
} finally {
    DynamicDataSourceContextHolder.poll();
}

标签:DynamicRoutingDataSource,数据源,public,xx,源码,dataSource,DS
From: https://www.cnblogs.com/Higurashi-kagome/p/18613553

相关文章

  • CVPR-23 Towards Universal Fake Image Detectors that Generalize Across Generative
    论文标题:TowardsUniversalFakeImageDetectorsthatGeneralizeAcrossGenerativeModels论文链接:https://arxiv.org/abs/2302.10174 01摘要翻译随着生成模型的快速发展,人们对通用假图像检测器的需求日益增长。在这项工作中,我们首先展示了现有的模式,即训练一个深......
  • flask框架美食网毕设源码+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容美食网毕业设计相关内容说明一、选题背景关于美食网的研究,现有研究主要以美食的展示与推广为主。在国内外,美食类网站众多,如国外的Yelp,国内的大众......
  • flask框架魔方教学网站毕设源码+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于魔方教学网站的研究,现有研究主要以魔方的解法技巧、魔方的历史文化等实体内容为主,专门针对魔方教学网站这一特定平台的研究较少。......
  • 基于java的SpringBoot/SSM+Vue+uniapp的大学校园防疫与服务系统的详细设计和实现(源码
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • ssm高校学生宿舍管理系统0dh5y(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、研究背景高校学生宿舍作为学生学习与生活的重要场所,其管理效率与质量直接影响到学生的校园生活体验。然而,传统的人工管理方式存在诸多不足,如信......
  • ssm高校学生学业预警系统6lc3u程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景随着高等教育的普及,高校学生人数激增,学业问题日益突出。为及时发现并解决学生的学业困难,预防学业失败,我们计划开发“高校学生学业预警......
  • JAVA无人共享24小时自助洗车扫码洗车系统源码支持小程序
    JAVA无人共享24小时自助洗车扫码洗车系统源码支持小程序在当今快节奏的生活中,自助洗车服务以其高效、便捷的特点逐渐受到广大车主的青睐。为满足这一市场需求,我们精心打造了一款JAVA无人共享24小时自助洗车扫码洗车系统,其源码全面支持小程序接入,不仅极大地提升了用户体验,还为......
  • JAVA数字人创作文案视频链接提取数字人源码小程序+公众号+APP+H5
    JAVA数字人创作文案提取与生成系统:全能型内容创作与运营解决方案在当今数字化内容井喷的时代,如何高效、创新地生产并传播内容,成为了众多内容创作者、商户乃至企业面临的重要课题。我们的JAVA数字人创作文案提取与生成系统,正是基于这一市场需求,集成了文案提取、文案生成、视频......
  • JAVA无人共享24小时自助洗车扫码洗车系统源码支持小程序
    JAVA无人共享24小时自助洗车扫码洗车系统源码支持小程序在当今快节奏的生活中,自助洗车服务以其高效、便捷的特点逐渐受到广大车主的青睐。为满足这一市场需求,我们精心打造了一款JAVA无人共享24小时自助洗车扫码洗车系统,其源码全面支持小程序接入,不仅极大地提升了用户体验,还为......
  • macOS Monterey(MacOS 12) 系统升级cocoapods
    老款MacBook系统Monterey(MacOS12)由于brew停止了从上游下载cocoapods提示不支持os12系统,无法安装最新版cocoapods,本文讲述了另一种方法来更新cocoapods原文链接:http://www.kovli.com/2024/12/18/old-macos-install-cocoapods/作者:Kovli重要通知:红宝书第5版2024年12月1日出炉......