Spring程序化事务管理:初学者实用指南
Spring的@Transactional注解在声明式事务管理方面表现出色,但并不总是最佳选择。在本篇Jotting中,您将了解何时以及如何在Spring中使用程序化事务控制,这对于需要精细管理事务生命周期的场景非常理想。
为什么需要程序化事务?
连接池耗尽问题
想象一下这个常见场景:一个将数据库调用与外部API相结合的方法:
@Transactional
public void processPayment(PaymentRequest request) {
savePaymentRequest(request); // 数据库
callPaymentProviderApi(request); // 外部API(慢)
updatePaymentState(request); // 数据库
saveAuditHistory(request); // 数据库
}这里的问题是什么?
当Spring使用@Transactional创建事务时:
- 从连接池中获取连接,并在整个方法期间保持
- 连接保持占用,等待外部API响应
- 如果API需要5-10秒,该连接在此期间被阻塞
- 在高负载下,等待慢速API会耗尽所有可用连接
黄金法则: 切勿在同一事务内混合数据库操作和外部API调用。
解决方案1:TransactionTemplate(推荐)
TransactionTemplate提供基于回调的API来手动管理事务。这是处理程序化事务最干净、最现代的方式。
基本配置
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.stereotype.Component;
@Component
public class PaymentService {
private final TransactionTemplate transactionTemplate;
public PaymentService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
}注意: Spring Boot自动配置
PlatformTransactionManager。您只需注入它即可。
示例1:带返回值的事务
public Long createSuccessfulPayment(PaymentRequest request) {
// 在事务内执行代码并返回ID
Long paymentId = transactionTemplate.execute(status -> {
Payment payment = new Payment();
payment.setAmount(request.getAmount());
payment.setReferenceNumber(request.getReference());
payment.setState(Payment.State.SUCCESSFUL);
entityManager.persist(payment);
// ID在persist后自动生成
return payment.getId();
});
return paymentId;
}这有什么作用?
- 自动创建事务
- 在事务内执行lambda代码
- 如果一切顺利,自动提交
- 如果出现异常,自动回滚
- 返回lambda的值
示例2:异常时的自动回滚
public void createTwoPaymentsWithRollback() {
try {
transactionTemplate.execute(status -> {
Payment first = new Payment();
first.setReferenceNumber("REF-001");
first.setAmount(1000L);
entityManager.persist(first); // 正常
Payment second = new Payment();
second.setReferenceNumber("REF-001"); // 重复!
second.setAmount(2000L);
entityManager.persist(second); // 抛出异常
return null;
});
} catch (Exception e) {
// 第一笔付款也会被回滚 - 保证原子性
System.out.println("事务已回滚:" + e.getMessage());
}
}示例3:显式手动回滚
public Long createPaymentWithValidation(PaymentRequest request) {
return transactionTemplate.execute(status -> {
Payment payment = new Payment();
payment.setReferenceNumber(request.getReference());
payment.setAmount(request.getAmount());
entityManager.persist(payment);
// 自定义业务验证
if (request.getAmount() > 100000) {
// 标记为回滚 - 事务将被撤销
status.setRollbackOnly();
return null; // 或抛出自定义异常
}
return payment.getId();
});
}示例4:无返回值的事务
public void saveAuditLogEntry(AuditEntry entry) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
auditRepository.save(entry);
// 不返回任何内容,仅执行操作
}
});
}示例5:每个实例的自定义配置
您可以创建具有不同配置的多个TransactionTemplate实例:
@Component
public class TransactionConfig {
@Bean
public TransactionTemplate readOnlyTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setReadOnly(true); // 查询优化
return template;
}
@Bean
public TransactionTemplate serializableTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
template.setTimeout(30); // 秒
return template;
}
@Bean
public TransactionTemplate requiresNewTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return template;
}
}可用配置
| 属性 | 值 | 描述 |
|---|---|---|
setIsolationLevel() | ISOLATION_READ_UNCOMMITTED | 读取未提交隔离级别 |
ISOLATION_READ_COMMITTED | 只读取已提交数据 | |
ISOLATION_REPEATABLE_READ | 防止不可重复读 | |
ISOLATION_SERIALIZABLE | 最大隔离级别 | |
setPropagationBehavior() | PROPAGATION_REQUIRED | 使用现有事务或创建新事务 |
PROPAGATION_REQUIRES_NEW | 始终创建新事务 | |
PROPAGATION_NESTED | 嵌套事务(保存点) | |
setTimeout() | 秒(int) | 最大执行时间 |
setReadOnly() | true/false | 只读优化 |
解决方案2:PlatformTransactionManager(底层)
对于完全控制,直接使用PlatformTransactionManager。这是@Transactional和TransactionTemplate内部使用的API。
示例:完全手动控制
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Component
public class ManualTransactionService {
private final PlatformTransactionManager transactionManager;
public ManualTransactionService(PlatformTransactionManager txManager) {
this.transactionManager = txManager;
}
public void processWithTotalControl() {
// 1. 定义事务配置
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
definition.setTimeout(5); // 5秒
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2. 启动事务
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 3. 执行业务操作
Payment payment = new Payment();
payment.setAmount(500L);
payment.setReferenceNumber("MANUAL-001");
entityManager.persist(payment);
// 4. 提交事务
transactionManager.commit(status);
} catch (Exception ex) {
// 5. 出错时回滚
transactionManager.rollback(status);
throw new RuntimeException("手动事务出错", ex);
}
}
}对比:TransactionTemplate vs PlatformTransactionManager
| 方面 | TransactionTemplate | PlatformTransactionManager |
|---|---|---|
| 抽象级别 | 高(回调) | 低(手动) |
| 错误处理 | 自动 | 手动(try-catch) |
| 自动回滚 | 是 | 否(必须调用) |
| 结果代码 | 更简洁 | 更冗长 |
| 灵活性 | 受回调限制 | 完全控制 |
| 推荐使用 | 大多数情况 | 需要精细控制时 |
实践案例:将数据库与外部API分离
原始问题(存在耗尽风险):
@Transactional
public void processOrder(Order order) {
orderRepository.save(order); // 数据库
shippingApi.createShipment(order); // 慢速API
notificationService.notifyCustomer(order); // 慢速API
}使用TransactionTemplate的解决方案:
@Component
public class OrderService {
private final TransactionTemplate txTemplate;
private final OrderRepository orderRepository;
private final ShippingApiService shippingApi;
private final NotificationService notificationService;
public void processOrderSafely(Order order) {
// 步骤1:仅在事务内进行数据库部分
Long orderId = txTemplate.execute(status -> {
order.setStatus("PROCESSING");
orderRepository.save(order);
return order.getId();
});
// 连接已释放!
// 步骤2:外部API(不占用连接)
String trackingNumber = shippingApi.createShipment(order);
// 步骤3:另一个外部API(不占用连接)
notificationService.notifyCustomer(order);
// 步骤4:更新最终状态(新的短事务)
txTemplate.executeWithoutResult(status -> {
Order updated = orderRepository.findById(orderId).orElseThrow();
updated.setTrackingNumber(trackingNumber);
updated.setStatus("SHIPPED");
});
}
}何时使用每种方法
使用@Transactional(声明式)当:
- 简单的CRUD操作
- 没有外部API调用
- 不需要复杂的条件控制
- 希望保持代码干净可读
使用TransactionTemplate(程序化)当:
- 需要混合数据库操作与外部I/O
- 条件逻辑决定回滚
- 同一服务中需要不同的事务配置
- 需要从事务返回值
使用PlatformTransactionManager当:
- 需要完全控制生命周期
- 一个方法中需要多个事务
- 与非标准事务系统集成
- 需要详细的事务日志或审计
结论
程序化事务提供了@Transactional无法提供的控制。对于大多数需要将数据库操作与外部调用分离的场景,TransactionTemplate是您防止连接池耗尽的最佳伙伴。
记住: 目标不是替换
@Transactional,而是在声明式方法的限制约束您的设计时对其进行补充。
参考文献和额外资源
官方文档
- Spring Framework - Programmatic Transaction Management: Spring团队关于程序化事务管理的完整指南。 docs.spring.io
- Spring Data Access Documentation: 关于数据访问和事务的官方文档。 docs.spring.io
推荐文章
- Vlad Mihalcea - Spring Transaction and Connection Management: 深入分析Spring如何处理数据库连接和事务。 vladmihalcea.com
- Baeldung - Programmatic Transaction Management in Spring: TransactionTemplate和PlatformTransactionManager的实用教程。 baeldung.com
- Marco Behler - Spring Transaction Management @Transactional In-Depth: 关于Spring事务内部运作的详细指南。 marcobehler.com
额外的最佳实践
- 在连接池中配置
auto-commit=false- 启用延迟连接获取 - 使用Hibernate时设置
hibernate.connection.provider_disables_autocommit=true - 考虑使用
DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION- 最大化连接重用 - 设计服务层 - 事务方法应在执行的尽可能晚的阶段调用