Spring程序化事务管理:初学者实用指南
Spring 的 @Transactional 注解做声明式事务很方便,但不是万能的。这篇 Jotting 聊聊什么时候该用程序化事务,怎么用。
为什么需要程序化事务?
连接池耗尽问题
一个很常见的场景:方法里同时有数据库操作和外部 API 调用。
@Transactional
public void processPayment(PaymentRequest request) {
savePaymentRequest(request); // 数据库
callPaymentProviderApi(request); // 外部API(慢)
updatePaymentState(request); // 数据库
saveAuditHistory(request); // 数据库
}问题在哪?@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());
}
}第一笔 payment 也会跟着回滚,原子性有保证。
示例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();
});
}业务校验不过的时候,status.setRollbackOnly() 标记回滚,事务撤销。
示例4:无返回值的事务
public void saveAuditLogEntry(AuditEntry entry) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
auditRepository.save(entry);
// 不返回任何内容,仅执行操作
}
});
}不需要返回值时用 TransactionCallbackWithoutResult。
示例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");
});
}
}注意看:数据库操作分两个短事务完成,中间的外部 API 调用不占用连接。这才是正确的写法。
何时使用每种方法
@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,最大化连接重用。 - 事务方法应在执行流程的尽可能晚的阶段调用,设计服务层时注意这一点。