核心问题说明
注意事项:
同一个事务下是无法切换数据源
禁止父方法使用@Transactional创建事务,子方法使用@DataSource切换数据源
正确用法: 子方法单独创建事务或父方法使用@Transactional(propagation = Propagation.REQUIRES_NEW)为所有子方法创建新事务
这个注意事项涉及Spring中事务管理和动态数据源切换的关键约束,让我深入解释这个问题。
为什么同一事务下无法切换数据源?
技术原理
- 事务与连接的绑定关系
- 当事务开始时,Spring会从当前数据源获取一个数据库连接
- 该连接会被绑定到当前线程的事务资源中(ThreadLocal)
- 事务管理器使用这个固定的连接执行所有SQL操作
- 数据源切换的时机
- 数据源切换需要在获取连接前完成
- 一旦事务开启并获取连接,数据源已确定且不可更改
- 切换数据源实际是更换连接工厂,而非已获取的连接
问题场景示例
错误示例1:事务中尝试切换数据源
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional // 主数据源的事务
public void processUserOrder(Long userId) {
// 使用主数据源操作用户数据
updateUserStatus(userId);
// 尝试在同一事务中切换数据源 - 这将失败!
orderService.createOrder(userId);
}
}
@Service
public class OrderService {
@DataSource(DataSourceType.SLAVE) // 尝试切换到从数据源
public void createOrder(Long userId) {
// 此方法实际上仍会使用主数据源,因为它在父事务中执行
saveOrder(new Order(userId));
}
}
问题解释:
- 当processUserOrder方法开始时,Spring创建一个事务并从主数据源获取连接
- 调用createOrder时,虽然标注了@DataSource,但由于已在事务中,数据源切换不会生效
- 所有操作都会使用主数据源的连接
错误示例2:跨数据源的分布式事务期望
@Transactional
@DataSource(DataSourceType.MASTER)
public void transferMoney() {
// 主库操作
accountDao.deductFromMaster(100);
// 错误:期望切换到从库并在同一事务中操作
@DataSource(DataSourceType.SLAVE)
accountDao.depositToSlave(100);
// 如果出错希望两边都回滚,但实际上这是不可能的
}
正确的解决方案
方案1:为子方法创建独立事务
@Service
public class UserService {
@Autowired
private OrderService orderService;
// 不在父方法开启事务
public void processUserOrder(Long userId) {
// 主数据源操作,单独事务
updateUserWithTransaction(userId);
// 从数据源操作,另一个独立事务
orderService.createOrder(userId);
}
@Transactional
public void updateUserWithTransaction(Long userId) {
updateUserStatus(userId);
}
}
@Service
public class OrderService {
@DataSource(DataSourceType.SLAVE)
@Transactional // 独立事务
public void createOrder(Long userId) {
// 现在使用的是从数据源,且在独立事务中
saveOrder(new Order(userId));
}
}
方案2:使用REQUIRES_NEW传播行为
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional // 主数据源事务
public void processUserOrder(Long userId) {
// 使用主数据源
updateUserStatus(userId);
// 子方法会挂起当前事务并创建新事务
orderService.createOrder(userId);
}
}
@Service
public class OrderService {
@DataSource(DataSourceType.SLAVE)
@Transactional(propagation \= Propagation.REQUIRES\_NEW) // 关键点:创建新事务
public void createOrder(Long userId) {
// 此方法会挂起父事务,创建新事务,并使用从数据源
saveOrder(new Order(userId));
}
}