Spring多数据源环境下的事务与数据源切换

核心问题说明

注意事项:

同一个事务下是无法切换数据源

禁止父方法使用@Transactional创建事务,子方法使用@DataSource切换数据源

正确用法: 子方法单独创建事务或父方法使用@Transactional(propagation = Propagation.REQUIRES_NEW)为所有子方法创建新事务

这个注意事项涉及Spring中事务管理和动态数据源切换的关键约束,让我深入解释这个问题。

为什么同一事务下无法切换数据源?

技术原理

  1. 事务与连接的绑定关系
  • 当事务开始时,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));

    }

}
相关推荐
weixin_580614002 小时前
如何提取SQL日期中的年份_使用YEAR或EXTRACT函数
jvm·数据库·python
2301_813599553 小时前
SQL生产环境规范_数据库使用最佳实践
jvm·数据库·python
a9511416423 小时前
Go 中通过 channel 传递切片时的数据竞争与深拷贝解决方案
jvm·数据库·python
我学上瘾了3 小时前
Spring Cloud的前世今生
后端·spring·spring cloud
qq_189807033 小时前
如何修改RAC数据库名_NID工具在集群环境下的改名步骤
jvm·数据库·python
aXin_ya3 小时前
Redis 高级篇(最佳实践)
数据库·redis·缓存
zhangchaoxies3 小时前
如何检测SQL注入风险_利用模糊测试技术发现漏洞
jvm·数据库·python
波波0074 小时前
ASP.NET Core 健康检查实战:不只是一个 /health 接口
后端·asp.net
zhangchaoxies4 小时前
CSS如何实现响应式弹性网格布局_配合media query修改flex-wrap属性
jvm·数据库·python
小码哥_常4 小时前
Spring Boot 搭建邮件发送系统:开启你的邮件自动化之旅
后端