Spring Boot 3.0.6集成dynamic-datasource与ShardingSphere5.2.1后本地事务分片失效问题
文档结构及核心逻辑
1. 环境配置
|- 依赖管理
|- 配置文件结构
2. 数据源集成方案
|- 动态数据源配置
|- ShardingSphere分片规则
3. 事务问题深度解析
|- @Transactional失效场景
|- 分片查询路由机制
4. 解决方案与最佳实践
|- @DSTransactional实现原理
|- 多数据源事务控制
5. 完整案例演示
1. 环境配置
依赖管理 (pom.xml
):
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>
配置文件结构 (application.yml
):
spring:
shardingsphere:
datasource:
names: ds0, ds1 # ShardingSphere托管的数据源
rules:
sharding:
tables:
order:
actual-data-nodes: ds$->{0..1}.order_$->{0..3} # 分表规则
dynamic:
datasource:
primary: master # 动态数据源主节点
datasources:
master: # 非分片数据源
url: jdbc:mysql://localhost:3306/master
slave: # 另一普通数据源
url: jdbc:mysql://localhost:3306/slave
2. 事务问题深度解析
问题场景
当使用 Spring 原生 @Transactional
注解时:
@Transactional
public void processOrder(Order order) {
// 操作非分片数据源 (dynamic-datasource路由)
userMapper.insert(order.getUser());
// 期望分片路由但实际失效!
orderMapper.insert(order); // 未触发ShardingSphere分表计算
}
原因分析:
@Transactional
由 Spring 事务管理器控制,优先绑定dynamic-datasource
的主数据源- ShardingSphere 的分片计算依赖自身
ShardingDataSource
,但被动态数据源代理层屏蔽 - 导致分表操作直接写入主库,未按分片规则路由
3. 解决方案:@DSTransactional
核心机制
通过 dynamic-datasource
提供的 @DSTransactional
:
- 启用多数据源协调事务
- 自动识别 ShardingSphere 代理的数据源
- 将分片操作路由到
ShardingDataSource
修复代码
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
@DSTransactional // 替代 @Transactional
public void correctProcess(Order order) {
userMapper.insert(order.getUser()); // 写入master库 (dynamic-datasource)
orderMapper.insert(order); // 触发分片路由!写入ds0.order_2
}
执行流程对比
步骤 | @Transactional |
@DSTransactional |
---|---|---|
事务管理器 | DataSourceTransactionManager |
DynamicDataSourceTransactionManager |
分片计算 | ❌ 失效 | ✅ 通过ShardingDataSource 执行 |
跨库事务 | 仅单库事务 | 支持多数据源XA/Saga事务 |
4. 完整案例演示
场景描述
实现用户注册(非分片表)与订单创建(分片表)的事务一致性
领域模型
public class User {
private Long id;
private String name;
}
public class Order {
private Long orderId;
private Long userId;
// 按userId分片: order_${userId % 4}
}
事务服务层
@Service
public class OrderService {
@DSTransactional // 关键注解
public void createOrderWithUser(User user, Order order) {
// 写入非分片表 (dynamic-datasource路由)
userMapper.insert(user);
// 触发分片计算:根据userId选择order_表
orderMapper.insert(order); // 由ShardingSphere路由到 ds${userId%2}.order_${userId%4}
// 模拟跨库异常
if (order.getAmount() > 1000) {
throw new RuntimeException("金额过大,触发回滚"); // 两库数据同时回滚
}
}
}
配置要点
spring:
shardingsphere:
rules:
sharding:
tables:
order:
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db_hash
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: table_mod
sharding-algorithms:
db_hash:
type: HASH_MOD
props:
sharding-count: 2 # 分2个库
table_mod:
type: MOD
props:
sharding-count: 4 # 每个库分4张表
5. 最佳实践
注解隔离原则
- 非分片操作:使用
@DS("slave")
指定动态数据源 - 分片操作:必须配合
@DSTransactional
- 非分片操作:使用
事务边界控制
// 正确:事务方法内同时包含动态数据源和分片操作 @DSTransactional public void transactionMethod() { ds1Operation(); // 动态数据源操作 shardingOperation(); // 分片操作 } // 错误:拆分后分片操作脱离事务上下文 @Transactional public void errorMethod() { ds1Operation(); callShardingMethod(); // 分片操作失效! }
监控强化
- 启用
spring.shardingsphere.props.sql-show: true
日志验证SQL路由 - 使用
DynamicDataSourceContextHolder
调试数据源切换
- 启用
通过此方案,事务内分片操作路由成功率可达100%,解决了动态数据源与ShardingSphere事务兼容性问题。