1. 概述
Spring 的 @Transactional
注解为我们提供了一种简洁的声明式方式来定义事务边界,背后由 AOP 切面自动处理事务的开启、提交或回滚。这种方式让业务逻辑和事务管理这类横切关注点(cross-cutting concerns)解耦,代码更干净。
但现实往往没那么理想。在某些场景下,声明式事务反而会带来问题。本文将探讨何时应避免使用 @Transactional
,并介绍 Spring 提供的编程式事务管理方案,比如 TransactionTemplate
和 PlatformTransactionManager
,以及它们的适用场景。
✅ 声明式事务:简洁,适合大多数 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
方法时,实际流程如下:
- 事务切面获取一个数据库连接(从连接池中借出),开启事务
- 执行第一个 DB 操作后,进入远程 API 调用
- 此时事务未结束,连接仍被占用
- 等待 API 响应期间,该连接一直无法归还给连接池
- 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 事务管理的底层接口,@Transactional
和 TransactionTemplate
都基于它实现。直接使用它可以获得最大控制力。
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