dynamic-datasource-spring-boot-starter -动态数据源详解
-------------------------------------------------------------------------
-------------------------------------------------------------------------
DynamicDataSourceStrategy 及子类(负载均衡 / 随机策略)使用详解
DynamicDataSourceStrategy 是 dynamic-datasource-spring-boot-starter 中数据源负载均衡策略的核心接口,LoadBalanceDynamicDataSourceStrategy(轮询)和 RandomDynamicDataSourceStrategy(随机)是其内置实现类,专门解决「多从库分组负载均衡」场景(如 @Slave 注解匹配多个从库时,自动选择其中一个执行)。以下是配置方式 + 代码示例 + 场景适配,新手也能直接落地。一、核心概念梳理
1. 策略接口与实现类
表格
| 类名 | 核心作用 | 适用场景 |
|---|---|---|
DynamicDataSourceStrategy |
负载均衡策略接口,定义「从数据源列表中选一个」的规则 | 自定义策略时实现该接口 |
LoadBalanceDynamicDataSourceStrategy |
内置轮询策略(默认):按顺序循环选择数据源 | 从库性能相近,需均匀分配请求 |
RandomDynamicDataSourceStrategy |
内置随机策略:随机选择一个数据源 | 从库性能差异大,需分散压力 |
2. 核心使用场景
只有当你配置了数据源分组(如
slave_1、slave_2 前缀为 slave_ 的从库),并通过 @DS("slave")/@Slave 引用分组名时,策略才会生效 —— 组件会先筛选出该分组下的所有数据源,再通过指定策略选择一个执行。二、快速使用(配置文件方式)
1. 基础配置(默认轮询策略)
步骤 1:配置多从库分组
在
application.yml 中配置 slave_ 前缀的从库(分组名是 slave):yaml
spring:
datasource:
dynamic:
primary: master # 默认主库
strict: false
# 全局负载均衡策略(可选,默认轮询)
strategy: com.baomidou.dynamic.datasource.strategy.LoadBalanceDynamicDataSourceStrategy
datasource:
# 主库
master:
url: jdbc:mysql://127.0.0.1:3306/db_master
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库1(前缀 slave_)
slave_1:
url: jdbc:mysql://127.0.0.1:3306/db_slave1
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库2(前缀 slave_)
slave_2:
url: jdbc:mysql://127.0.0.1:3306/db_slave2
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库3(前缀 slave_)
slave_3:
url: jdbc:mysql://127.0.0.1:3306/db_slave3
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
步骤 2:使用分组名触发负载均衡
在 Service 层用
@Slave(等价于 @DS("slave")),组件会自动按轮询策略选择 slave_1/slave_2/slave_3:java
运行
import com.baomidou.dynamic.datasource.annotation.Slave;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// @Slave 等价于 @DS("slave"),触发分组负载均衡
@Slave
public List<User> listAllUser() {
// 第一次调用:slave_1
// 第二次调用:slave_2
// 第三次调用:slave_3
// 第四次调用:slave_1(轮询重置)
return userMapper.selectList(null);
}
}
2. 切换为随机策略(两种方式)
方式 1:配置文件指定(全局生效)
修改
application.yml 中的 strategy 配置:yaml
spring:
datasource:
dynamic:
# 全局切换为随机策略
strategy: com.baomidou.dynamic.datasource.strategy.RandomDynamicDataSourceStrategy
# 其他配置不变...
方式 2:自定义配置类(全局生效)
通过
@Bean 注入策略,优先级高于配置文件:java
运行
import com.baomidou.dynamic.datasource.strategy.RandomDynamicDataSourceStrategy;
import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DynamicDataSourceConfig {
/**
* 注入随机负载均衡策略(全局生效)
*/
@Bean
public DynamicDataSourceStrategy dynamicDataSourceStrategy() {
return new RandomDynamicDataSourceStrategy();
}
}
此时
@Slave 注解的方法会随机选择 slave_1/slave_2/slave_3,每次调用的数据源不固定。三、进阶使用(自定义策略)
若内置的轮询 / 随机策略不满足需求(如按从库负载、响应时间选择),可实现
DynamicDataSourceStrategy 接口自定义策略。1. 自定义策略示例(按数据源名称匹配优先级)
需求:优先选择
slave_1,若不可用则选 slave_2,最后选 slave_3。java
运行
import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class PriorityDynamicDataSourceStrategy implements DynamicDataSourceStrategy {
// 记录slave_1是否可用(示例,实际可通过健康检查判断)
private volatile boolean slave1Available = true;
@Override
public String determineDataSource(List<String> dataSources) {
// dataSources:分组下的所有数据源名称(如 [slave_1, slave_2, slave_3])
if (dataSources.isEmpty()) {
throw new RuntimeException("分组下无可用数据源");
}
// 优先级1:slave_1可用则选它
if (slave1Available && dataSources.contains("slave_1")) {
return "slave_1";
}
// 优先级2:选slave_2
else if (dataSources.contains("slave_2")) {
return "slave_2";
}
// 优先级3:选第一个数据源(slave_3)
else {
return dataSources.get(0);
}
}
// 手动切换slave_1可用性(示例)
public void setSlave1Available(boolean available) {
this.slave1Available = available;
}
}
2. 启用自定义策略
java
运行
@Configuration
public class DynamicDataSourceConfig {
// 注入自定义策略(覆盖内置策略)
@Bean
public DynamicDataSourceStrategy dynamicDataSourceStrategy() {
return new PriorityDynamicDataSourceStrategy();
}
}
四、关键细节与避坑指南
1. 策略生效条件
- 必须配置分组数据源(名称带下划线前缀,如
slave_1),且通过@DS("分组名")(如@DS("slave"))引用; - 若分组下只有一个数据源,策略不会生效(直接选该数据源);
- 策略仅作用于「读操作」(从库),主库(
@Master/@DS("master"))无负载均衡(只有一个主库)。
2. 内置策略核心逻辑(源码简化版)
(1)轮询策略(LoadBalanceDynamicDataSourceStrategy)
java
运行
public class LoadBalanceDynamicDataSourceStrategy implements DynamicDataSourceStrategy {
private final AtomicInteger index = new AtomicInteger(0);
@Override
public String determineDataSource(List<String> dataSources) {
// 超出列表长度则重置为0,循环选择
int i = index.getAndIncrement() % dataSources.size();
if (i < 0) {
i = 0;
index.set(0);
}
return dataSources.get(i);
}
}
(2)随机策略(RandomDynamicDataSourceStrategy)
java
运行
public class RandomDynamicDataSourceStrategy implements DynamicDataSourceStrategy {
private final Random random = new Random();
@Override
public String determineDataSource(List<String> dataSources) {
// 随机选一个索引
int index = random.nextInt(dataSources.size());
return dataSources.get(index);
}
}
3. 常见问题
问题 1:策略不生效?
- 排查:是否配置了下划线前缀的分组数据源?是否用
@DS("分组名")而非具体数据源名(如@DS("slave_1"))? - 解决:确保分组名和数据源前缀一致(如
slave分组对应slave_前缀)。
问题 2:轮询策略出现重复选择?
- 原因:
AtomicInteger是全局的,若分组下有多个不同前缀的分组(如slave_、read_),会共用同一个计数器; - 解决:自定义策略,为不同分组维护独立计数器。
问题 3:随机策略导致某从库压力过大?
- 原因:纯随机无权重,可能导致请求集中;
- 解决:自定义加权随机策略(按从库性能分配权重)。
五、适用场景总结
表格
| 策略类 | 核心特点 | 适用场景 |
|---|---|---|
LoadBalanceDynamicDataSourceStrategy(轮询) |
请求均匀分配,无热点从库 | 从库配置相同、性能相近 |
RandomDynamicDataSourceStrategy(随机) |
分散压力,避免固定顺序 | 从库性能差异大、需打散请求 |
| 自定义策略 | 按需定制规则 | 有特殊优先级、健康检查、权重需求 |
总结
DynamicDataSourceStrategy是负载均衡策略接口,内置轮询(默认)和随机两种实现;- 使用核心:配置下划线前缀的分组数据源 +
@DS("分组名"),策略自动生效; - 全局切换策略可通过「配置文件指定类名」或「配置类注入 Bean」实现,自定义策略需实现接口并注入;
- 策略仅作用于多从库分组,主库无负载均衡,单从库分组也不会触发策略。
-------------------------------------------------------------------------
dynamic-datasource-spring-boot-starter 全维度使用详解
dynamic-datasource-spring-boot-starter(简称动态数据源组件)是苞米豆(baomidou)开源的 Spring Boot 多数据源解决方案,核心价值是无侵入、低成本实现多数据源切换,支持读写分离、多租户、动态增删数据源等主流场景。以下从「核心原理→基础配置→高级用法→避坑指南」全流程讲解,新手也能直接落地。一、核心原理
1. 整体架构
配置多数据源
DynamicRoutingDataSource(核心路由类)
生成失败,请重试
配置多数据源
DynamicRoutingDataSource(核心路由类)
生成失败,请重试
豆包
你的 AI 助手,助力每日工作学习
- 核心类:
DynamicRoutingDataSource实现 SpringDataSource接口,负责路由到目标数据源; - 上下文管理:
DynamicDataSourceContextHolder基于ThreadLocal存储当前线程的数据源名称,避免多线程污染; - 切换触发:
@DS注解(AOP 拦截)或 API 手动切换; - 负载均衡:
DynamicDataSourceStrategy实现多从库的轮询 / 随机 / 自定义负载均衡。
2. 核心特性
- 无侵入:无需修改业务代码,注解 / API 即可切换;
- 多数据源支持:配置文件 / 动态新增多数据源;
- 负载均衡:内置轮询 / 随机策略,支持自定义;
- 事务兼容:适配
@DSTransactional/Seata 分布式事务; - 国产数据库适配:支持达梦、人大金仓、高斯等信创库。
二、快速入门(基础配置)
1. 环境依赖
表格
| 组件 | 版本要求 |
|---|---|
| Spring Boot | 2.0.x ~ 3.x(3.x 需 3.6.0+) |
| JDK | 8+ |
| 数据库驱动 | 对应使用的数据库(MySQL / 达梦等) |
2. 引入依赖(Maven)
xml
<!-- 动态数据源核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version> <!-- 稳定版 -->
</dependency>
<!-- 数据库驱动(示例:MySQL) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus(可选,简化CRUD) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
3. 配置多数据源(application.yml)
yaml
spring:
datasource:
dynamic:
# 1. 核心配置
primary: master # 默认数据源(未指定时使用)
strict: false # 非严格模式:找不到数据源时用默认库;true则抛异常
strategy: com.baomidou.dynamic.datasource.strategy.LoadBalanceDynamicDataSourceStrategy # 全局负载均衡策略(默认轮询)
# 2. 多数据源列表
datasource:
# 主库(写)
master:
url: jdbc:mysql://127.0.0.1:3306/db_master?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池配置(默认HikariCP)
hikari:
maximum-pool-size: 20
minimum-idle: 5
# 从库1(读,前缀slave_,用于分组负载均衡)
slave_1:
url: jdbc:mysql://127.0.0.1:3306/db_slave1?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库2(读,前缀slave_)
slave_2:
url: jdbc:mysql://127.0.0.1:3306/db_slave2?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 业务库(独立数据源)
business_db:
url: jdbc:mysql://127.0.0.1:3306/db_business?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
4. 基础使用(@DS 注解)
@DS 是核心注解,优先级:方法注解 > 类注解 > 默认数据源,仅在 Service 层生效(Mapper/Controller 层失效)。(1)类级别注解(默认数据源)
java
运行
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.stereotype.Service;
// 类级别:该Service所有方法默认使用business_db数据源
@Service
@DS("business_db")
public class BusinessService {
// 无方法注解:使用类注解的business_db
public void doBusiness() {
// SQL操作 → 走business_db
}
}
(2)方法级别注解(覆盖类注解)
java
运行
@Service
// 类级别默认master
// @DS("master")
public class UserService {
// 无注解:使用默认master(写操作)
public void addUser(User user) {
userMapper.insert(user);
}
// 指定slave_1(读操作)
@DS("slave_1")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
// 指定分组名slave,自动负载均衡slave_1/slave_2
@DS("slave")
public List<User> listAllUser() {
return userMapper.selectList(null);
}
// 覆盖类注解,使用business_db
@DS("business_db")
public void crossDbOperation() {
// SQL操作 → 走business_db
}
}
(3)简化注解(读写分离)
@Master/@Slave 是 @DS 的别名,简化读写分离场景:java
运行
@Service
public class OrderService {
// 等价于@DS("master"),强制主库写
@Master
public void createOrder(Order order) {
orderMapper.insert(order);
}
// 等价于@DS("slave"),从库读(负载均衡)
@Slave
public List<Order> listOrder(Long userId) {
return orderMapper.selectByUserId(userId);
}
}
三、进阶用法
1. API 手动切换数据源
适合复杂逻辑(如多租户、动态参数切换),核心类
DynamicDataSourceContextHolder:java
运行
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.springframework.stereotype.Service;
@Service
public class TenantService {
// 多租户按库隔离:根据租户ID切换数据源
public void handleTenant(Long tenantId) {
try {
// 切换到租户专属数据源(如tenant_001)
String dsName = "tenant_" + tenantId;
DynamicDataSourceContextHolder.push(dsName);
// 执行业务逻辑 → 走tenant_001
queryTenantData(tenantId);
} finally {
// 必须出栈,恢复上下文(避免污染)
DynamicDataSourceContextHolder.poll();
}
}
// 嵌套切换数据源
public void nestedDsOperation() {
try {
DynamicDataSourceContextHolder.push("slave_1");
System.out.println("当前数据源:" + DynamicDataSourceContextHolder.peek()); // slave_1
// 内层临时切换
try {
DynamicDataSourceContextHolder.push("slave_2");
System.out.println("当前数据源:" + DynamicDataSourceContextHolder.peek()); // slave_2
} finally {
DynamicDataSourceContextHolder.poll(); // 回到slave_1
}
} finally {
DynamicDataSourceContextHolder.poll(); // 回到默认master
}
}
}
2. 动态增删数据源(运行时配置)
支持项目启动后新增 / 删除数据源(多租户、动态扩容场景):
java
运行
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Component
public class DynamicDsManager {
@Resource
private DynamicRoutingDataSource dynamicRoutingDataSource;
@Resource
private DataSourceCreator dataSourceCreator;
// 新增数据源
public void addDataSource(String dsName, String url, String username, String password) {
DataSourceProperty property = new DataSourceProperty();
property.setUrl(url);
property.setUsername(username);
property.setPassword(password);
property.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 创建数据源并添加到管理器
DataSource dataSource = dataSourceCreator.createDataSource(property);
dynamicRoutingDataSource.addDataSource(dsName, dataSource);
}
// 删除数据源
public void removeDataSource(String dsName) {
dynamicRoutingDataSource.removeDataSource(dsName);
}
}
3. 自定义负载均衡策略
实现
DynamicDataSourceStrategy 接口,替代内置的轮询 / 随机策略:java
运行
import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
// 自定义策略:优先选择slave_1,不可用时选slave_2
public class PriorityDsStrategy implements DynamicDataSourceStrategy {
@Override
public String determineDataSource(List<String> dataSources) {
if (dataSources.contains("slave_1")) {
return "slave_1";
}
return dataSources.get(0);
}
}
// 注入自定义策略
@Configuration
public class DsConfig {
@Bean
public DynamicDataSourceStrategy dynamicDataSourceStrategy() {
return new PriorityDsStrategy();
}
}
4. 事务支持
(1)单数据源事务(@DSTransactional)
替代 Spring 原生
@Transactional,确保事务绑定当前数据源:java
运行
@Service
public class UserService {
// 绑定slave_1数据源的事务
@DS("slave_1")
@DSTransactional(rollbackFor = Exception.class)
public void batchUpdate(List<Long> ids) {
for (Long id : ids) {
userMapper.updateStatus(id, 1);
if (id == 100L) {
throw new RuntimeException("触发回滚"); // 事务回滚
}
}
}
}
(2)分布式事务(Seata + @GlobalTransactional)
跨多数据源事务需整合 Seata,替换
@DSTransactional 为 @GlobalTransactional:java
运行
import io.seata.spring.annotation.GlobalTransactional;
@Service
public class OrderUserService {
// 全局事务:跨master(订单库)和slave1(用户库)
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrderAndDeduct(Long userId, Integer amount) {
// 切换到master创建订单
DynamicDataSourceContextHolder.push("master");
orderMapper.insert(new Order(userId, amount));
// 切换到slave1扣减余额
DynamicDataSourceContextHolder.push("slave1");
userMapper.deductBalance(userId, amount);
// 异常时全局回滚
// throw new RuntimeException("模拟异常");
}
}
四、信创适配(国产数据库)
只需替换驱动和连接配置,核心用法不变:
yaml
spring:
datasource:
dynamic:
datasource:
# 达梦数据库
dm_db:
url: jdbc:dm://192.168.1.100:5236/DB_NAME?SYSDBA=1
username: SYSDBA
password: DAMENG123
driver-class-name: dm.jdbc.driver.DmDriver
# 人大金仓
kingbase_db:
url: jdbc:kingbase8://192.168.1.101:54321/DB_NAME
username: SYSTEM
password: 123456
driver-class-name: com.kingbase8.Driver
五、避坑指南(高频问题)
1. @DS 注解不生效?
- ❌ 注解加在 Mapper/Controller 层 → ✅ 仅加在 Service 层;
- ❌ 同类内部调用加注解的方法 → ✅ 通过 Bean 调用(AOP 生效);
- ❌ 数据源名称拼写错误 → ✅ 检查配置文件的数据源 Key;
- ❌ 事务内切换数据源 → ✅ 事务外切换(事务绑定初始数据源)。
2. 多线程数据源串库?
- 原因:
ThreadLocal线程私有,线程池复用导致上下文残留; - 解决:线程任务结束前调用
DynamicDataSourceContextHolder.clear()。
3. 负载均衡策略不生效?
- ❌ 数据源名称无下划线前缀(如
slave1)→ ✅ 配置为slave_1; - ❌ 直接指定具体数据源(
@DS("slave_1"))→ ✅ 指定分组名(@DS("slave"))。
4. 分布式事务不生效?
- ❌ 未代理动态数据源 → ✅ 用
DataSourceProxy包裹DynamicRoutingDataSource; - ❌ XID 未透传 → ✅ 线程池任务中手动绑定 XID。
六、核心配置项速查
表格
| 配置项 | 默认值 | 说明 |
|---|---|---|
| spring.datasource.dynamic.primary | master | 默认数据源名称 |
| spring.datasource.dynamic.strict | false | 找不到数据源时是否抛异常 |
| spring.datasource.dynamic.strategy | LoadBalanceDynamicDataSourceStrategy | 全局负载均衡策略 |
| spring.datasource.dynamic.datasource | - | 多数据源配置列表 |
| spring.datasource.dynamic.type | HikariCP | 数据源连接池类型 |
总结
- 核心定位:
dynamic-datasource-spring-boot-starter是 Spring Boot 多数据源的首选组件,无侵入、配置简单; - 核心用法:
@DS注解(Service 层)实现静态切换,DynamicDataSourceContextHolderAPI 实现动态切换; - 关键原则:
@DS仅在 Service 层生效,事务需用@DSTransactional/@GlobalTransactional; - 进阶能力:支持动态增删数据源、自定义负载均衡、国产数据库适配、分布式事务整合。
-------------------------------------------------------------------------
-------------------------------------------------------------------------

浙公网安备 33010602011771号