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

使用@DS注解切换数据源,@DS注解可加在类或者方法上,方法上注解优先于类上注解。对于没有使用@DS注解的类或方法,使用默认数据源。以下是官方示例:

@Service
@DS("slave")
public class UserServiceImpl implements UserService {@Autowiredprivate 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") {@Overrideprotected 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() 会获取到 DynamicRoutingDataSourcetx = 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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/855099.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源系列】CentOS7下Docker环境搭建开源堡垒机Apache Guacamole

Apache Guacamole 是一个无客户端远程桌面网关。它支持 VNC、RDP 和 SSH 等标准协议。不需要插件或客户端软件。借助 HTML5,一旦在服务器上安装了 Guacamole,只需使用 Web 浏览器即可访问桌面。 1.Guacamole的架构介绍 Guacamole不是一个独立的网络应用程序,而是由多个部分组…

ThreeJs-07操控物体实现家具编辑器

本章节实现效果,通过gui快速添加场景,家具,并且可以快速设置家具实现一个编辑器效果一.基础设置与物体添加列表 用之前做过的一个案例来改首先不要这个模型,然后换个背景颜色,并且添加一个网格辅助器1.1 添加场景 先往事件对象里面添加一个函数,到时候点击就会调用这个函…

数据集划分;参数超参数;交叉验证

在机器学习和深度学习中,将数据分为训练集(Training Set)、验证集(Validation Set)和测试集(Test Set)是常见的做法,每部分数据承担不同的任务: 一.基本概念 1.训练集(Training Set):训练集用于训练模型,即通过算法调整模型的参数以最小化损失函数(Loss Function…

爱米导航网(imi),您的互联网书签搭子

爱米导航(Imi)网是一个综合性的互联网资源聚合平台,它以其丰富的内容和便捷的服务受到了广大用户的喜爱。该网站收录了数千个不同类型的互联网工具网站,覆盖了AI工具、自媒体运营工具、产品经理工具以及UI设计师工具等多个领域,为用户提供了一个一站式的解决方案。 爱米导…

记录一次springboot启动流程不完整版

1.Sort ApplicationInitializer:2.sort Listener:3.getRunListener:4.eventPublishingRunListener.starting 发布启动中事件; 构建 ApplicationArguments 参数, 4.1prepareEnvement: [StubPropertySource {name=servletConfigInitParams}, StubPropertySource {name=servletC…

键盘连击软件解决方案

解决的问题 jjjjjjjjjjjjjjjj键盘连击了,windows系统中的“筛选键”功能就能短暂解决这个问题,可是筛选的时间太长无法在面板设置更短的时间,但可以使用软件解决。 方法一 系统 筛选键 win+i 打开设置 -> 搜索“筛选键” -> 打开筛选键开关 使用限制:重复键最低只能设…

【建议收藏】最新版IDEA2024.3及 AI Assistant 一键激活到2099

成功永久激活 一、支持的IDE和版本支持JB家族所有IDE激活 支持版本为2021.3~2024.1二、如何破解激活 第一步:激活工具下载 为了防止破解工具被删除,通过公众号回复“「永久激活」”获取下载最新工具(如过期,请记得提醒我哦) 关注公众号后台回复“「永久激活」”,获取最新…

《平衡树》读后感

第一框,世界属于fhq-treap 是什么? 你说的对,但是《fhq-treap》是由范浩强自主研发的一款全新树形数据结构。数据结构发生在一个被称作「二叉搜索树」的幻想世界,在这里,被人创造的节点将被授予「随机优先级」,导引期望 \(O(\log n)\) 之力。treap 将扮演一位名为「根据优…

虚拟机unraid系统安装

首先下载unraid文件https://unraid.net/getting-started windows系统使用vmware虚拟机 创建虚拟机过程中注意usb的不同协议 2.0 3.0 由于unraid系统只能识别fat32协议的文件系统,因此需要将u盘格式化 可以使用easeus 或者 傲梅 https://www.easeus.com/partition-manager/ htt…

基于.NET WinForm开发的一款硬件及协议通讯工具

前言 今天大姚给大家分享一款基于.NET WinForm开发的一款硬件及协议通讯工具:PLC-CommunTools。 项目介绍 PLC-CommunTools是一款基于.NET WinForm开发的一款硬件及协议通讯工具,包含各类厂商的PLC协议及基础的TCP、串口通讯、IO口通讯等协议通讯功能的实现整合,注意还有一部…

游戏过程

根据是否结束游戏的逻辑选择对掷骰子的过程用bool申明变量掷骰子 static bool RollDice (w,h,ref Player p1,ref Player p2,Map map) {InfoClear(h);Console.ForegroundColor=p1.type==E_PlayerType.Player?ConsoleColor.Cyan:ConsoleColor.Meganta;if(p1.isPause){p1.isPause…

6.Group组件

关于Group组件的简单介绍首先,Group组件本身并不是一个“布局”类的组件,它只是一个容器,没有提供调整内部组件展示位置的方法,也就是说,当我们将多个组件(比如button)放在group中时,他们(根据流式规则?)会全部挤在窗口的左上角,当然,如果我们没有设置组件的大小的话…