1. 概述

Spring 的 @Transactional 注解为我们提供了一种简洁的声明式方式来定义事务边界,背后由 AOP 切面自动处理事务的开启、提交或回滚。这种方式让业务逻辑和事务管理这类横切关注点(cross-cutting concerns)解耦,代码更干净。

但现实往往没那么理想。在某些场景下,声明式事务反而会带来问题。本文将探讨何时应避免使用 @Transactional,并介绍 Spring 提供的编程式事务管理方案,比如 TransactionTemplatePlatformTransactionManager,以及它们的适用场景。

✅ 声明式事务:简洁,适合大多数 CRUD 场景
❌ 编程式事务:灵活,适合复杂控制逻辑或混合 I/O 场景


2. 声明式事务的“坑”

来看一个典型的“踩坑”案例:在一个事务方法中混合了数据库操作和远程 API 调用。

@Transactional
public void initialPayment(PaymentRequest request) {
    savePaymentRequest(request);      // DB 操作
    callThePaymentProviderApi(request); // 远程 API 调用
    updatePaymentState(request);      // DB 操作
    saveHistoryForAuditing(request);  // DB 操作
}

初看似乎合理:所有操作要么全部成功,要么全部回滚,保证原子性。但问题出在那个远程 API 调用上。

2.1. 问题本质:连接池被“饿死”

当执行 initialPayment 方法时,实际流程如下:

  1. 事务切面获取一个数据库连接(从连接池中借出),开启事务
  2. 执行第一个 DB 操作后,进入远程 API 调用
  3. 此时事务未结束,连接仍被占用
  4. 等待 API 响应期间,该连接一直无法归还给连接池
  5. API 返回后,继续执行后续 DB 操作,最后提交事务并释放连接

⚠️ 如果 API 响应慢,这个连接就会被长时间“霸占”。在高并发场景下,大量请求堆积,连接池迅速耗尽,导致后续请求直接失败。

结论:不要在事务中执行耗时的外部 I/O 操作!

解决方案有两个:

  • ✅ 拆分逻辑:将外部调用移出事务边界
  • ✅ 保留混合操作:改用编程式事务,精细控制事务范围

3. 使用 TransactionTemplate

TransactionTemplate 是 Spring 提供的模板类,用于以回调方式手动管理事务。相比 @Transactional,它更灵活,适合需要动态控制事务边界的场景。

首先通过依赖注入获取 PlatformTransactionManager

@DataJpaTest
@ActiveProfiles("test")
@Transactional(propagation = NOT_SUPPORTED)
public class ManualTransactionIntegrationTest {

    @Autowired 
    private PlatformTransactionManager transactionManager;

    @Autowired 
    private EntityManager entityManager;

    private TransactionTemplate transactionTemplate;

    @BeforeEach
    public void setUp() {
        transactionTemplate = new TransactionTemplate(transactionManager);
    }
}

✅ Spring Boot 会自动配置合适的 PlatformTransactionManager,直接注入即可
⚠️ 手动配置时需根据数据访问技术选择实现类(如 JpaTransactionManager

3.1. 示例领域模型

为演示方便,使用一个简化的支付模型:

@Entity
public class Payment {

    @Id
    @GeneratedValue
    private Long id;

    private Long amount;

    @Column(unique = true)
    private String referenceNumber;

    @Enumerated(EnumType.STRING)
    private State state;

    // getters and setters

    public enum State {
        STARTED, FAILED, SUCCESSFUL
    }
}

3.2. 带返回值的事务执行

使用 execute 方法在事务中执行逻辑并返回结果:

@Test
void givenAPayment_WhenNotDuplicate_ThenShouldCommit() {
    Long id = transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setAmount(1000L);
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);

        entityManager.persist(payment);

        return payment.getId();
    });

    Payment payment = entityManager.find(Payment.class, id);
    assertThat(payment).isNotNull();
}

✅ 事务自动提交:所有操作成功完成
❌ 自动回滚:任意操作失败(如唯一键冲突)

@Test
void givenTwoPayments_WhenRefIsDuplicate_ThenShouldRollback() {
    try {
        transactionTemplate.execute(status -> {
            Payment first = new Payment();
            first.setAmount(1000L);
            first.setReferenceNumber("Ref-1");
            first.setState(Payment.State.SUCCESSFUL);

            Payment second = new Payment();
            second.setAmount(2000L);
            second.setReferenceNumber("Ref-1"); // 冲突!

            entityManager.persist(first);
            entityManager.persist(second); // 抛异常

            return "Ref-1";
        });
    } catch (Exception ignored) {}

    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).isEmpty();
}

也可以手动标记回滚:

@Test
void givenAPayment_WhenMarkAsRollback_ThenShouldRollback() {
    transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setAmount(1000L);
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);

        entityManager.persist(payment);
        status.setRollbackOnly(); // 手动标记

        return payment.getId();
    });

    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).isEmpty();
}

3.3. 无返回值的事务执行

如果不需要返回值,可使用 TransactionCallbackWithoutResult

@Test
void givenAPayment_WhenNotExpectingAnyResult_ThenShouldCommit() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            Payment payment = new Payment();
            payment.setReferenceNumber("Ref-1");
            payment.setState(Payment.State.SUCCESSFUL);

            entityManager.persist(payment);
        }
    });

    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).hasSize(1);
}

3.4. 自定义事务配置

TransactionTemplate 支持细粒度配置,常见设置包括:

transactionTemplate = new TransactionTemplate(transactionManager);

// 设置隔离级别
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);

// 设置传播行为:总是新建事务
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

// 设置超时时间(秒)
transactionTemplate.setTimeout(1000);

// 标记为只读事务(可触发性能优化)
transactionTemplate.setReadOnly(true);

⚠️ 注意:每个 TransactionTemplate 实例绑定一套配置。如需多种配置,应创建多个实例。


4. 使用 PlatformTransactionManager

PlatformTransactionManager 是 Spring 事务管理的底层接口,@TransactionalTransactionTemplate 都基于它实现。直接使用它可以获得最大控制力。

4.1. 定义事务属性

通过 TransactionDefinition 配置事务行为:

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
definition.setTimeout(3); // 3秒超时

✅ 优势:一个 PlatformTransactionManager 可配合多个不同的 TransactionDefinition

4.2. 手动管理事务生命周期

@Test
void givenAPayment_WhenUsingTxManager_ThenShouldCommit() {
    TransactionStatus status = transactionManager.getTransaction(definition);
    try {
        Payment payment = new Payment();
        payment.setReferenceNumber("Ref-1");
        payment.setState(Payment.State.SUCCESSFUL);

        entityManager.persist(payment);
        transactionManager.commit(status);
    } catch (Exception ex) {
        transactionManager.rollback(status);
    }

    assertThat(entityManager.createQuery("select p from Payment p").getResultList()).hasSize(1);
}

⚠️ 必须手动处理 try-catch,确保异常时正确回滚,否则可能造成事务悬挂(unclosed transaction)


5. 总结

  • ✅ 声明式事务(@Transactional)适用于简单、纯粹的数据库操作
  • ⚠️ 混合外部 I/O 时,避免在事务中执行远程调用,防止连接池耗尽
  • ✅ 编程式事务(TransactionTemplate)适合需要灵活控制事务边界的场景
  • PlatformTransactionManager 提供最细粒度的控制,适合底层框架或复杂流程

示例代码已上传至 GitHub:https://github.com/yourname/tutorials/tree/master/persistence-modules/spring-data-jpa-annotations


原始标题:Programmatic Transaction Management in Spring