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));

    }

}
相关推荐
Leon-Ning Liu2 分钟前
Oracle 19c RAC报错ORA-17503,ORA-27300,ORA-27301,ORA-27302
数据库·oracle
爱笑的眼睛113 分钟前
FastAPI 请求验证:超越 Pydantic 基础,构建企业级验证体系
java·人工智能·python·ai
czlczl200209256 分钟前
Spring Boot 参数校验进阶:抛弃复杂的 Group 分组,用 @AssertTrue 实现“动态逻辑校验”
java·spring boot·后端
得物技术6 分钟前
Java 设计模式:原理、框架应用与实战全解析|得物技术
java
阿拉斯攀登9 分钟前
ThreadLocal 全解析(Spring Boot 实战篇)
java·spring boot·threadlocal
BBB努力学习程序设计11 分钟前
Java模块化系统深度解析:从JAR地狱到JPMS模块化
java
dddaidai12313 分钟前
深入JVM(三):JVM执行引擎
java·jvm
Hui Baby15 分钟前
saga文件使用
java
墨夶18 分钟前
交易所安全保卫战:从冷钱包到零知识证明,让黑客连边都摸不着!
java·安全·区块链·零知识证明
山风wind21 分钟前
Tomcat三步搭建局域网文件共享
java·tomcat