dynamic-datasource是由baomidou团队开发的多数据源管理
框架,其4.3.1版本是目前企业级应用中使用广泛的稳定版本。该框架解决了 多数据源动态切换
的核心需求,在微服务架构和复杂业务系统中具有重要价值。在应用中,当业务需要同时访问多个数据库(如MySQL、Oracle、SQL Server等)或需要实现读写分离架构时,开发者往往需要编写大量样板代码来管理不同数据源的连接和切换。dynamic-datasource通过注解声明式
的方式简化了这一过程,使开发者能够专注于业务逻辑的实现。
dynamic-datasource 4.3.1的核心功能亮点包括:支持多数据源的灵活配置与管理;提供基于@DS
注解的声明式数据源切换;实现分组数据源及负载均衡策略;兼容多种主流连接池(Druid、HikariCP、DBCP2等);并且与MyBatis/MyBatis-Plus框架无缝集成。这些特性使其成为处理复杂数据源场景的首选解决方案。
dynamic-datasource的架构设计围绕两大核心支柱:AOP拦截机制与动态路由数据源。整个框架通过巧妙利用Spring的扩展点和线程本地存储(ThreadLocal)实现了高效的数据源切换。
DynamicDataSourceAnnotationAdvisor
切面拦截所有带有@DS
注解的方法或类。在方法执行前,切面解析注解值(如@DS("slave_1")
),并将数据源标识压入DynamicDataSourceContextHolder
维护的双端队列(Deque)中。方法执行完成后,再将该标识弹出队列。这里采用Deque而非简单String的设计是为了支持方法嵌套调用场景,确保在多层调用中数据源能正确恢复。// 示例:DynamicDataSourceAnnotationInterceptor关键处理逻辑
public Object invoke(MethodInvocation invocation) throws Throwable {
String dsKey = determineDatasource(invocation); // 解析@DS注解值
DynamicDataSourceContextHolder.push(dsKey); // 压栈
try {
return invocation.proceed(); // 执行目标方法
} finally {
DynamicDataSourceContextHolder.poll(); // 出栈
}
}
DynamicRoutingDataSource
继承与Spring的AbstractRoutingDataSource
实现思路一样的AbstractRoutingDataSource
类,并重写了关键的determineCurrentLookupKey()
方法。该方法从DynamicDataSourceContexHolder
中获取当前数据源标识,实现运行时动态路由。真正的数据库连接获取发生在getConnection()
方法调用时,该方法首先确定目标数据源,然后委托给具体数据源获取连接。
// 示例:AbstractRoutingDataSource关键处理逻辑
public Connection getConnection() throws SQLException {
String xid = TransactionContext.getXID();
if (DsStrUtils.isEmpty(xid)) {
return determineDataSource().getConnection();
} else {
String ds = DynamicDataSourceContextHolder.peek();
ds = DsStrUtils.isEmpty(ds) ? getPrimary() : ds;
ConnectionProxy connection = ConnectionFactory.getConnection(xid, ds);
return connection == null ? getConnectionProxy(xid, ds, determineDataSource().getConnection()) : connection;
}
}
// 示例:DynamicRoutingDataSource的determineDataSource关键处理逻辑
public DataSource determineDataSource() {
String dsKey = DynamicDataSourceContextHolder.peek();
return getDataSource(dsKey);
}
dynamic-datasource 4.3.1引入了分组数据源的概念,用于实现读写分离或多副本负载均衡的高级场景。分组数据源通过特定的命名规则(group_xxx
)配置,例如将多个从库命名为slave_1
、slave_2
,它们自动归属于slave
组。
@DS("slave")
指定组名而非具体数据源时,框架会从该组内所有可用数据源中按预设策略选择一个。这种设计解耦了业务代码与具体数据源的映射关系,当数据库拓扑变化时只需调整配置而无需修改代码。LoadBalanceDynamicDataSourceStrategy
:基于轮询算法的负载均衡,按固定顺序循环选择组内数据源RandomDynamicDataSourceStrategy
:基于随机算法的选择策略,每次随机挑选组内数据源# 分组数据源配置示例
spring:
datasource:
dynamic:
datasource:
master:# 主库
url:jdbc:mysql://master:3306/db
slave_1:# 从库1
url:jdbc:mysql://slave1:3306/db
slave_2:# 从库2
url:jdbc:mysql://slave2:3306/db
表:分组数据源策略配置对比
策略类型 | 类名 | 适用场景 | 特点 |
---|---|---|---|
轮询策略 | LoadBalanceDynamicDataSourceStrategy | 读多写少、负载均衡 | 均匀分配查询请求,避免单节点过载 |
随机策略 | RandomDynamicDataSourceStrategy | 读请求随机分发 | 实现简单,适合节点性能相近场景 |
DynamicDataSourceAutoConfiguration
自动配置类加载配置。YmlDynamicDataSourceProvider
解析application.yml
中以spring.datasource.dynamic.datasource
为前缀的配置项,并利用DataSourceBuilder
反射创建实际数据源实例。创建后的数据源被注册到DynamicRoutingDataSource
的dataSourceMap
中。//DynamicDataSourceAopConfiguration 配置文件加载
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
public static DynamicDataSourceProperties dynamicDataSourceProperties() {
returnnew DynamicDataSourceProperties();
}
//DynamicDataSourceProperties 配置文件属性定义
@Slf4j
@Getter
@Setter
@ConfigurationProperties(prefix = DynamicDataSourceProperties.PREFIX)
publicclass DynamicDataSourceProperties {
publicstaticfinal String PREFIX = "spring.datasource.dynamic";
/**
* 必须设置默认的库,默认master
*/
private String primary = "master";
/**
* 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源primary所设置的数据源
*/
private Boolean strict = false;
... ... ... ...
}
//YmlDynamicDataSourceProvider的示例化:
//传入DynamicDataSourceProperties数据源配置
@Configuration
@RequiredArgsConstructor
publicclass DynamicDataSourceAssistConfiguration {
privatefinal DynamicDataSourceProperties properties;
@Bean
@Order(0)
public DynamicDataSourceProvider ymlDynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator) {
returnnew YmlDynamicDataSourceProvider(defaultDataSourceCreator, properties.getDatasource());
}
... ... ... ...
}
//DynamicDataSourceAutoConfiguration类,配置数DynamicRoutingDataSource据源类
//需要注入DynamicDataSourceProvider(数据源的配置及根据配置生成数据源实现方式)
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(List<DynamicDataSourceProvider> providers) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(providers);
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
dataSource.setGraceDestroy(properties.getGraceDestroy());
return dataSource;
}
//DynamicRoutingDataSource在实例初始化后,开始根据配置生成配置的数据源实例,
//放入com.baomidou.dynamic.datasource.DynamicRoutingDataSource#dataSourceMap中
//com.baomidou.dynamic.datasource.DynamicRoutingDataSource#afterPropertiesSet
public void afterPropertiesSet() {
// 检查开启了配置但没有相关依赖
checkEnv();
// 添加并分组数据源
Map<String, DataSource> dataSources = new HashMap<>(16);
for (DynamicDataSourceProvider provider : providers) {
//根据配置,生成数据源实例,放入dataSourceMap中。
Map<String, DataSource> dsMap = provider.loadDataSources();
if (dsMap != null) {
dataSources.putAll(dsMap);
}
}
for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
addDataSource(dsItem.getKey(), dsItem.getValue());
}
// 检测默认数据源是否设置
if (groupDataSources.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);
} elseif (dataSourceMap.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);
} else {
log.warn("dynamic-datasource initial loaded [{}] datasource,Please add your primary datasource or check your configuration", dataSources.size());
}
}
DbHealthIndicator
扩展Spring Boot的健康检查端点(/actuator/health
)。该检查遍历所有数据源执行SELECT 1
查询验证连接有效性。然而,其默认实现存在一个关键缺陷——任一数据源连接失败会导致整个应用启动失败。这是因为在健康检查中未妥善处理异常,当某个数据源不可用时抛出异常中断了启动过程。问题描述:用户配置了连接池参数(如HikariCP的maximum-pool-size
),但实际未生效。这是因为动态数据源初始化过程中,连接池参数需放置在与数据源同级的hikari
子项下,而非顶层配置。
解决方案:严格遵循分层配置规则,确保连接池参数位于正确层级:
# 正确配置示例
spring:
datasource:
dynamic:
datasource:
master:
type:com.zaxxer.hikari.HikariDataSource
url:jdbc:mysql://localhost:3306/main
username:root
password:root
hikari:# Hikari专属配置
maximum-pool-size:20
connection-timeout:30000
问题描述:当数据源创建失败时,不同连接池的异常处理方式不一致:
这种不一致性导致排查问题时需针对不同连接池采取不同策略,增加故障排查难度。
解决方案:推荐以下两种应对策略:
DataSourceCreator
,在创建数据源时捕获所有异常并统一包装为ErrorCreateDataSourceException
,确保原始堆栈不丢失DEBUG
级别日志,通过logging.level.com.baomidou.dynamic.datasource=DEBUG
获取详细错误信息问题描述:在带有@Transactional
注解的方法中,@DS
数据源切换失效。这是因为Spring事务管理器在开启事务时(beginTransaction
)就确定了Connection
,后续在同一事务内所有操作都使用同一连接。
解决方案:
TransactionTemplate
编程式事务管理,将不同数据源操作放在独立事务中@Transactional(readOnly=true)
,结合@DS("slave")
实现路由// 错误示例:事务内切换失效
@Transactional
@DS("master")
public void updateOrder(Order order) {
orderMapper.update(order);
// 下一行@DS注解在事务内不生效
@DS("slave")
List<Log> logs = logMapper.queryByOrderId(order.getId());
}
// 正确示例:拆分方法
@DS("master")
@Transactional
public void updateOrder(Order order) {
orderMapper.update(order);
// 调用独立事务方法
queryLogs(order.getId());
}
@DS("slave")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public List<Log> queryLogs(Long orderId) {
return logMapper.queryByOrderId(orderId);
}
问题描述:当项目中存在多个数据源时,若任一数据源不可用(如网络故障),整个应用启动失败。这是因为DbHealthIndicator
在健康检查中未处理异常,导致启动过程中断。
解决方案:
management.health.db.enabled=false
(不推荐生产环境长期使用)spring.datasource.dynamic.strict=false
,使未匹配数据源时回退到主库而非抛出异常// 自定义健康检查器示例
publicclass SafeDbHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) {
Map<String, DataSource> dataSources = dynamicDataSource.getDataSources();
for (Map.Entry<String, DataSource> entry : dataSources) {
try {
// 执行简单查询验证
Integer result = executeQuery(entry.getValue());
builder.withDetail(entry.getKey(), result == 1 ? "UP" : "DOWN");
} catch (Exception e) {
builder.withDetail(entry.getKey(), "DOWN - " + e.getMessage());
}
}
}
}
问题描述:dynamic-datasource 4.3.1与Spring Boot 3.3.x存在兼容性问题,主要涉及自动配置加载顺序和类路径扫描变化。表现症状包括@EnableDynamicDataSource
导入失败或核心Bean未创建。
解决方案:
<!-- 版本降级示例 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version> <!-- 兼容版本 -->
</parent>
问题描述:启动时报错Consider marking one of the beans as @Primary
,表明存在多个DataSource
类型Bean,Spring无法自动选择。
解决方案:
@Primary
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@Qualifier
指定需要注入的具体数据源@Configuration
publicclass DataSourceConfig {
@Bean
@Primary// 关键:标记具体数据源为Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DynamicRoutingDataSource dynamicDataSource(DataSource masterDataSource) {
DynamicRoutingDataSource ds = new DynamicRoutingDataSource();
ds.setDefaultTargetDataSource(masterDataSource);
// 添加其他数据源...
return ds;
}
}
maximum-pool-size
= T(N) * 2
(其中T(N)
为最大线程数)connection-timeout
建议3000-5000ms,避免网络波动导致线程阻塞test-on-borrow
或test-on-return
,但会牺牲少量性能application-dev.yml
:开发环境,使用本地数据库application-test.yml
:测试环境,指向测试数据库集群application-prod.yml
:生产环境,配置高可用集群地址DataSourceCreateFailStrategy
接口,实现以下策略:STRICT
模式:快速失败,抛出包含详细错误信息的异常LOG_ONLY
模式:记录错误日志但继续启动(适用于次要数据源)RETRY
模式:对网络抖动类异常实施指数退避重试public interface DataSourceCreateFailStrategy {
void handle(String dsName, DataSourceProperty property, Exception ex);
}
// 严格模式实现
public class StrictStrategy implements DataSourceCreateFailStrategy {
@Override
public void handle(String dsName, DataSourceProperty property, Exception ex) {
throw new DataSourceCreateException("创建数据源[" + dsName + "]失败", ex);
}
}
/actuator/health
端点,自定义健康指示器包含各数据源状态micrometer-registry-prometheus
采集连接池关键指标(活动连接数、等待线程数等)DynamicDataSourceContextHolder
添加DEBUG日志,记录数据源切换轨迹SELECT 1
检查数据源活性SICK
,不再分配请求public class SmartDataSourceStrategy implements DynamicDataSourceStrategy {
@Override
public DataSource determineDataSource(List<DataSource> dataSources) {
// 过滤掉不健康的数据源
List<DataSource> healthyDs = dataSources.stream()
.filter(ds -> HEALTHY_STATUS.equals(getStatus(ds)))
.collect(Collectors.toList());
if (healthyDs.isEmpty()) {
thrownew NoHealthyDataSourceException("无可用数据源");
}
// 在健康数据源中轮询选择
return loadBalance(healthyDs);
}
}
DynamicRoutingDataSource
中的数据源实例dynamic-datasource 4.3.1通过AOP拦截与动态路由的巧妙设计,为Spring Boot应用提供了简洁高效的多数据源解决方案。在使用过程中,开发者需特别注意事务边界对数据源切换的影响、连接池配置的正确方式以及版本兼容性等关键风险点。
随着Spring Boot 3.x的普及,建议密切关注官方版本更新,及时升级至全面兼容Spring Boot 3.3+的dynamic-datasource新版本。当面对超大规模集群或异地多活等复杂场景时,可考虑结合ShardingSphere
或Spring AbstractRoutingDataSource
原生方案进行深度定制,实现更细粒度的数据路由策略。
架构选型建议: