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

    }

}
相关推荐
缺点内向3 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅3 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看5 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程5 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t5 小时前
ZIP工具类
java·zip
舒一笑5 小时前
大模型时代的程序员成长悖论:如何在AI辅助下不失去竞争力
后端·程序员·掘金技术征文
lang201509285 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
△曉風殘月〆6 小时前
WPF中的坐标转换
wpf
言德斐6 小时前
SQL性能优化的思路及策略
数据库·sql·性能优化
码界奇点6 小时前
Django视图从基础到高级的全面解析
数据库·django·sqlite·web·python3.11