Spring编程式事务全解析:从DataSource到TxManager再到TxTemplate

Spring编程式事务全解析:封装流程、原理与常见疑问

Spring的事务管理是个让人又爱又恨的东西,声明式事务(@Transactional)用着方便,但不够灵活;编程式事务则完全放开手脚,适合复杂场景。今天我们从最底层的实现聊起,逐步走进封装的编程式事务,剖析它与声明式的关系,再解答一个常见疑问:为什么查询不同Repository不能放在同一个@Transactional里?通过代码、表格和分析,咱们把这些问题掰开揉碎聊清楚。


从最底层开始:朴素的DataSource事务

咱们先从最原始的方式入手,用DataSource直接操作事务,假设要保存用户信息:

java 复制代码
@Service
public class UserService {
    @Autowired
    private DataSource dataSource;

    public void saveUser(String username) {
        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);
            PreparedStatement ps = conn.prepareStatement("INSERT INTO users (username) VALUES (?)");
            ps.setString(1, username);
            ps.executeUpdate();
            conn.commit();
        } catch (SQLException e) {
            try (Connection conn = dataSource.getConnection()) {
                conn.rollback();
            } catch (SQLException ex) {
                throw new RuntimeException("Rollback failed", ex);
            }
            throw new RuntimeException("Save failed", e);
        }
    }
}

这代码很直白:拿Connection,关自动提交,手动执行SQL,成功就提交,失败就回滚。但问题也明显:

  • 代码量大:手动管理连接和异常,太繁琐。
  • 资源管理麻烦:得自己释放连接,容易漏。
  • 不灵活:没法跟Spring的其他机制无缝衔接。

第一步封装:PlatformTransactionManager

Spring提供了PlatformTransactionManager,把事务逻辑抽象出来,咱们改写一下:

java 复制代码
@Service
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private PlatformTransactionManager txManager;

    public void saveUser(String username) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = txManager.getTransaction(def);

        try {
            jdbcTemplate.update("INSERT INTO users (username) VALUES (?)", username);
            txManager.commit(status);
        } catch (Exception e) {
            txManager.rollback(status);
            throw e;
        }
    }
}
  • 封装意义TxManager接管了Connection的获取、提交和回滚,底层还是JDBC,但开发者不用直接碰Connection
  • 进步:事务配置(传播行为、隔离级别)可定制,资源管理交给Spring。
  • 不足:还是得手动提交和回滚,代码不够简洁。

第二步封装:TransactionTemplate

再进一步,Spring提供了TransactionTemplate,直接把提交回滚包起来:

java 复制代码
@Service
public class UserService {
    @Autowired
    private TransactionTemplate txTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void saveUser(String username) {
        txTemplate.execute(status -> {
            jdbcTemplate.update("INSERT INTO users (username) VALUES (?)", username);
            return null;
        });
    }
}
  • 封装意义:事务的生命周期(开启、提交、回滚)全自动化,异常时自动回滚。
  • 进步:代码简洁到飞起,专注业务逻辑就好。
  • 联系声明式@Transactional其实就是用AOP把类似TransactionTemplate的逻辑加到方法前后,Spring核心的事务机制一脉相承。

封装流程表格

层级 操作方式 封装内容 带来的好处 局限性
DataSource 直接用Connection控制事务 无封装,纯手动操作 完全掌控,灵活性高 代码繁琐,资源管理复杂
TxManager PlatformTransactionManager管理 抽象事务定义和状态 配置灵活,与Spring集成 仍需手动提交回滚
TxTemplate TransactionTemplate执行 自动提交回滚,回调式API 简洁高效,专注业务 粒度仍受限于单一事务

每一步封装都在减少重复劳动,提升开发效率,同时保留必要的灵活性。DataSource是基础,TxManager是桥梁,TxTemplate是现代编程式事务的标配。


复杂场景:为什么查询不同Repository不行?

现在聊聊一个常见疑问:为什么查询不同的Repository不能放在同一个@Transactional里?比如:

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepo; // 数据源1
    @Autowired
    private OrderRepository orderRepo; // 数据源2

    @Transactional
    public void process(String username) {
        User user = userRepo.findByUsername(username); // 数据源1
        Order order = orderRepo.findByOrderId(1L);    // 数据源2
        // 业务逻辑
    }
}

运行后可能会发现,第二个查询压根没被事务管理,甚至报错。这是啥情况?

是数据源不同的问题吗?

大部分时候是的。Spring的事务默认绑定到一个TransactionManager,而每个TransactionManager通常对应一个DataSource。上面例子中:

  • @Transactional默认用userRepo的数据源事务管理器。
  • orderRepo用的是另一个数据源,事务管不到它。

如果没配置多数据源支持(比如JTA),第二个查询要么跑在独立连接上,要么压根没事务。这会导致:

  • 一致性问题:异常时只有第一个数据源回滚。
  • 性能隐患:多个连接没统一管理,可能锁冲突。
同数据源不同表呢?

如果userRepoorderRepo用的是同一个DataSource,那没问题,同一个事务完全可以管多个表:

java 复制代码
@Transactional
public void process(String username) {
    User user = userRepo.findByUsername(username); // 表users
    Order order = orderRepo.findByOrderId(1L);    // 表orders
    // 正常事务管理
}

只要是同一DataSource,事务没毛病。所以,关键不是"不同表",而是"不同数据源"。


优化方向:解决多数据源问题

面对多数据源,咱们得优化:

  1. 指定TransactionManager
java 复制代码
@Transactional(transactionManager = "userTxManager")
public void processUser(String username) {
    userRepo.findByUsername(username);
}

@Transactional(transactionManager = "orderTxManager")
public void processOrder(Long orderId) {
    orderRepo.findByOrderId(orderId);
}

分开事务,各自管理。

  1. 用编程式事务
java 复制代码
public void process(String username, Long orderId) {
    TransactionTemplate userTx = new TransactionTemplate(userTxManager);
    TransactionTemplate orderTx = new TransactionTemplate(orderTxManager);

    userTx.execute(status -> {
        userRepo.findByUsername(username);
        return null;
    });
    orderTx.execute(status -> {
        orderRepo.findByOrderId(orderId);
        return null;
    });
}

细粒度控制,每个数据源独立。

  1. 分布式事务(JTA) : 如果需要统一管理多数据源,用AtomikosBitronix实现XA事务:
java 复制代码
@Transactional // JTA事务管理器
public void process(String username, Long orderId) {
    userRepo.findByUsername(username);
    orderRepo.findByOrderId(orderId);
}

所有数据源一致提交或回滚。


总结

DataSourceTxManager再到TxTemplate,Spring编程式事务的封装让代码从繁琐走向优雅,每层都在解决资源管理、配置灵活性和开发效率的问题。它跟声明式事务共享核心逻辑,只是控制权交给了开发者。至于查询不同Repository的坑,主要源自数据源差异,同源不同表完全没问题。优化方向上,细粒度可以用TxTemplate,多数据源可以用JTA,具体看场景需求。希望这篇聊下来,你对事务的"前世今生"更有感觉了!

相关推荐
lllsure3 小时前
【快速入门】MyBatis
java·后端·mybatis
叶雅茗3 小时前
PHP语言的区块链扩展性
开发语言·后端·golang
Stark、5 小时前
【MySQL】多表查询(笛卡尔积现象,联合查询、内连接、左外连接、右外连接、子查询)-通过练习快速掌握法
数据库·后端·sql·mysql
Moment6 小时前
如果你想找国外远程,首先让我先给你推荐这几个流行的技术栈 🤪🤪🤪
前端·后端·github
Ttang236 小时前
SpringBoot(4)——SpringBoot自动配置原理
java·开发语言·spring boot·后端·spring·自动配置·原理
Asthenia04126 小时前
Spring声明式事务失效场景分析与总结
后端
Asthenia04126 小时前
Spring七种声明式事务传播机制深度解析:内外层行为与异常处理
后端
努力的小雨7 小时前
行业案例分享:汽车售后智能助手
后端
GoGeekBaird7 小时前
69天探索操作系统-第53天:高级分布式操作系统算法和共识协议
后端·操作系统