当前位置: 首页 > news >正文

Spring Boot 多数据源切换:AbstractRoutingDataSource

在实际项目中,我们常常会用到多个数据库,比如一个主数据库(master)专门用来写入一个从数据库(slave)专门用来读取。这种场景非常常见,那么问题来了:

我该如何在项目中动态地切换主从数据库?
Spring 是怎么实现“在运行时自动选择数据库”的?

什么是动态数据源?

动态数据源就是在运行时,Spring 动态决定使用哪个数据库连接。

也就是说:

  • 有多个数据库(比如 master、slave)

  • 程序运行过程中自动切换用哪个数据库

  • 你不用手动写 if...else 来判断

第一步:我们需要哪些数据源?

我们以最常见的主从结构举例:

spring:
datasource:
druid:
master:
url: jdbc:mysql://localhost:3306/db_master
username: root
password: 123456
slave:
enabled: true
url: jdbc:mysql://localhost:3306/db_slave
username: root
password: 123456

我们准备两个数据源:

  • 主数据源 master

  • 从数据源 slave(注意加了一个 enabled=true)

第二步:配置两个数据源 Bean

在 Spring Boot 中,我们通过 @Bean 创建连接池:

@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);
}@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties) {DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);
}

这样我们就有了两个数据库连接池。

第三步:创建一个“数据源路由器”

我们不能在代码中每次都自己写 if 判断用哪个数据源,这太麻烦了。

Spring 给我们提供了抽象类AbstractRoutingDataSource,它是一个“动态路由器”,且实现了DataSource接口

public abstract class AbstractRoutingDataSource

它是怎么工作的?

假设你调用了:

dataSource.getConnection();

如果这个 dataSource 是继承了 AbstractRoutingDataSource 的类,它就会:

  1. 自动调用 determineCurrentLookupKey()
  2. 返回一个 key(比如 "MASTER" 或 "SLAVE")
  3. 根据 key 从 map 中找到对应的数据源
  4. 最终返回真正的数据库连接

源码也是非常通俗易懂:

可以看到在getConnection方法中,它会先去获取一下DataSource。其中就会先去获取key,如果为空就是默认的DataSource。

第四步:定义我们的 DynamicDataSource 类

public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceType(); // 返回 master 或 slave}
}

它的作用就是:

每次执行数据库操作前,去上下文 ThreadLocal 中取出当前线程应该使用哪个数据源。

第五步:设置 dynamicDataSource Bean

@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("MASTER", masterDataSource);setDataSource(targetDataSources, "SLAVE", "slaveDataSource");return new DynamicDataSource(masterDataSource, targetDataSources);
}

注意:

  • @Primary 让 Spring 默认使用这个数据源

  • setDataSource() 会从 Spring 容器中动态拿到 slaveDataSource

第六步:用 ThreadLocal 保存当前线程的数据源类型

public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void set(String key) {contextHolder.set(key);}public static String get() {return contextHolder.get();}public static void clear() {contextHolder.remove();}
}

这样每个线程就可以独立地记录当前要使用哪个数据源了!

第七步:通过 AOP 自动切换数据源

你可以加一个注解,比如:

@DS("SLAVE")
public List<User> getUserList() {...
}

AOP 在方法执行前会调用:

DataSourceContextHolder.set("SLAVE");

这样你的方法执行时,就自动使用了从数据库!

整体流程图

最后

多数据源非常适合读写分离、分库分表、租户隔离场景,还是很值得研究一下的。

http://www.lryc.cn/news/582962.html

相关文章:

  • 语言模型 RLHF 实践指南(一):策略网络、价值网络与 PPO 损失函数
  • MySQL索引面试问题梳理
  • 【Android】组件及布局介绍
  • Flutter基础(前端教程②-卡片列表)
  • 【牛客刷题】小红的v三元组
  • 从单体到微服务:Spring Cloud 开篇与微服务设计
  • 音频主动降噪技术
  • 暑假算法日记第四天
  • Spring AI:检索增强生成(RAG)
  • 工作中的思考
  • Java教程:【程序调试技巧】入门
  • 项目Win系统下可正常获取Header字段,但是到了linux、docker部署后无法获取
  • 数据湖技术之Iceberg-03 Iceberg整合Flink 实时写入与增量读取
  • 【HarmonyOS】鸿蒙端云一体化开发入门详解 (一)
  • 深度剖析 Linux ip neigh:邻居表项的查看与添加实践
  • RabbitMQ第二章(RocketMQ的五大工作模式)
  • 二进制安全-汇编语言-04-第一个程序
  • 为什么elementui的<el-table-column label=“名称“ prop=“name“ label不用写成:label
  • Docker快速部署Hive服务
  • C++ 遍历可变参数的几种方法
  • 零基础|宝塔面板|frp内网穿透|esp32cam远程访问|微信小程序
  • 链表算法之【移除链表元素】
  • 【深度学习新浪潮】什么是上下文长度?
  • C++异步编程入门
  • 猿人学js逆向比赛第一届第十五题
  • Java面试基础:概念
  • 部署并运行Vim/Vmamba在ImageNet上的训练与测试
  • JavaScript之数组方法详解
  • (C++)list列表相关基础用法(C++教程)(STL库基础教程)
  • HTTP/3.x协议详解:基于QUIC的下一代Web传输协议