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

    }

}
相关推荐
多多*1 分钟前
图解Redis的分布式锁的历程 从单机到集群
java·开发语言·javascript·vue.js·spring·tomcat·maven
想用offer打牌6 分钟前
2025年总结:一个树苗倔强生长
java·后端·开源·go
a程序小傲21 分钟前
国家电网面试被问:FactoryBean与BeanFactory的区别和动态代理生成
java·linux·服务器·spring boot·spring·面试·职场和发展
电商API&Tina26 分钟前
Python请求淘宝商品评论API接口全指南||taobao评论API
java·开发语言·数据库·python·json·php
小北方城市网26 分钟前
Redis 分布式锁与缓存三大问题解决方案
spring boot·redis·分布式·后端·缓存·wpf·mybatis
若鱼191927 分钟前
SpringBoot4.0新特性-Resilience之失败重试
java·spring
摩西蒙31 分钟前
业务监控和常用产品
java·大数据·人工智能
哪里不会点哪里.31 分钟前
Spring 核心原理解析:它到底解决了什么问题?
java·后端·spring
小杍随笔1 小时前
【Rust Cargo 目录迁移到 D 盘:不改变安装路径和环境变量的终极方案】
开发语言·后端·rust