声明式事务(@Transactional)虽然简洁高效,但其作用范围通常是整个方法或整个类,颗粒度受限于 AOP 的切面规则。当需要在方法内部对事务边界进行更精细的控制——例如仅对某几行代码启用事务、根据中间结果动态决定提交或回滚、或者在循环中分批提交时,编程式事务便成为必备手段。
Spring 提供了两种编程式事务管理方式:TransactionTemplate(模板类) 和直接使用 PlatformTransactionManager(事务管理器接口)。前者封装了大量样板代码,适合绝大多数场景;后者暴露完整的底层控制能力,在特殊需求下使用。
10.5.1 TransactionTemplate:安全便捷的编程式事务
TransactionTemplate 采用模板方法模式,将“获取事务→执行逻辑→提交/回滚→释放资源”这一固定流程封装起来。你只需要通过回调接口提供核心业务代码,框架保证事务生命周期得到正确处理,不用担心忘记提交或释放连接。
1. 基本用法
首先,在 Spring 容器中,TransactionTemplate 可以直接注入(Spring Boot 会自动根据配置好的事务管理器创建一个 TransactionTemplate Bean):
@Service
public class OrderProcessingService {
private final TransactionTemplate transactionTemplate;
// 构造器注入
public OrderProcessingService(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
如果容器中没有自动配置,也可以手动创建并设置事务管理器:
@Configuration
public class TxConfig {
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
template.setTimeout(5); // 秒
return template;
}
}
执行事务分为有返回值和无返回值两种情况。
- 有返回值:使用
execute(TransactionCallback<T>) - 无返回值:使用
executeWithoutResult(Consumer<TransactionStatus>)或execute(TransactionCallbackWithoutResult)
有返回值示例:
public Order createOrder(Order order) {
// 事务范围内的操作,返回订单 ID
Integer orderId = transactionTemplate.execute(status -> {
// 保存订单主表
orderRepository.insertOrder(order);
// 保存订单明细
for (OrderItem item : order.getItems()) {
item.setOrderId(order.getId());
orderItemRepository.insert(item);
}
return order.getId();
});
// 事务已提交或回滚,这里可以继续执行非事务操作
return orderRepository.findById(orderId);
}
无返回值示例:
public void batchUpdateStatus(List<Integer> orderIds, String newStatus) {
transactionTemplate.executeWithoutResult(status -> {
for (Integer id : orderIds) {
orderRepository.updateStatus(id, newStatus);
}
// 如果中间发生异常,整个事务回滚
});
}
手动回滚:业务逻辑可以调用 status.setRollbackOnly() 强制回滚,即使没有抛出异常:
transactionTemplate.executeWithoutResult(status -> {
orderRepository.updateStatus(orderId, "PAID");
int updatedRows = orderRepository.updateStock(orderId);
if (updatedRows == 0) {
status.setRollbackOnly(); // 库存更新失败,主动回滚
}
});
2. 细粒度控制属性
TransactionTemplate 允许在实例级别设置事务属性,也可以在执行时动态指定。这非常适合需要在同一个类中根据不同场景使用不同传播行为或隔离级别的场合。
// 构造两个不同配置的模板
@Bean
public TransactionTemplate requiresNewTx(PlatformTransactionManager tm) {
TransactionTemplate template = new TransactionTemplate(tm);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return template;
}
@Bean
public TransactionTemplate readOnlyTx(PlatformTransactionManager tm) {
TransactionTemplate template = new TransactionTemplate(tm);
template.setReadOnly(true);
template.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
return template;
}
使用时注入对应的模板即可。如果希望在一次调用中临时覆盖属性,可以使用重载的 execute 方法,传入 TransactionDefinition:
transactionTemplate.execute(
new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
status -> {
// 新事务中的逻辑
return someOperation();
}
);
3. 与声明式事务的协同
TransactionTemplate 本身会参与现有的事务上下文。如果在 @Transactional 标注的方法内部调用 transactionTemplate.execute(),默认传播行为是 REQUIRED,它将加入外层事务。如果需要在某个步骤上挂起外部事务、开启新事务,只需将模板的传播行为设为 REQUIRES_NEW 即可。
这种组合常见于:主流程使用声明式事务保证数据一致性,而个别步骤(如写入审计日志)即使在主流程失败时也应独立提交。
@Transactional
public void processOrder(Order order) {
// 主事务
orderRepository.save(order);
// 审计日志需要独立提交,不受主事务影响
requiresNewTx.executeWithoutResult(status ->
auditLogRepository.save(new AuditLog("Order created"))
);
// 主事务继续...
}
10.5.2 PlatformTransactionManager:更底层的直接控制
TransactionTemplate 底层也正是通过 PlatformTransactionManager 来完成事务操作。如果遇到极其特殊的场景(例如需要手动管理保存点,或者代码段无法以回调形式组织),可以直接面对 PlatformTransactionManager 编程。
核心 API:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
使用时需要手动遵循“获取状态→try 业务逻辑→commit→catch 里 rollback→finally 中清理”的模式,任何遗漏都可能导致连接泄漏或事务悬挂。
示例:
@Service
public class ManualTransactionService {
private final PlatformTransactionManager txManager;
public ManualTransactionService(PlatformTransactionManager txManager) {
this.txManager = txManager;
}
public void complexBatchOperation(List<DataChunk> chunks) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setTimeout(10);
for (DataChunk chunk : chunks) {
TransactionStatus status = txManager.getTransaction(def);
try {
processChunk(chunk);
txManager.commit(status);
} catch (Exception ex) {
txManager.rollback(status);
// 记录失败,继续处理下一批(示例业务要求)
}
// 注意:finally 中不需要手动清理,commit/rollback 已处理
}
}
}
必须注意的点:
getTransaction返回的TransactionStatus是新事务还是现有事务,取决于传播行为和当前线程上下文。- 一定要在 finally 中确保资源被正确处理,但直接使用
PlatformTransactionManager时,没有 finally 自动清理;调用commit或rollback后事务即结束,重复调用会引发异常。 - 嵌套事务(保存点)场景下,可通过
status.hasSavepoint()和status.createSavepoint()等 API 进行管理,但实际开发中极少需要手动操作保存点,通常 TransactionTemplate 配合PROPAGATION_NESTED已经足够。
10.5.3 实践中的选择建议
| 方式 | 适用场景 | 优点 | 缺点 |
|------|----------|------|------|
| TransactionTemplate | 绝大多数需要编程式事务的场景:方法内部部分代码需要事务、循环中分批提交、非事务方法中临时启用事务等。 | 代码安全,模板自动处理 begin/commit/rollback,避免资源泄漏;与 Lambda 配合简洁自然;可通过配置定制传播行为。 | 必须用回调形式组织代码,对局部变量的修改需要 final 或使用容器类(如数组、AtomicReference)传递结果(返回值则无此烦恼)。 |
| PlatformTransactionManager | 无法用回调表达的流程(如跨方法多次手动控制)、需要操作保存点、或者自行封装更高级的抽象时。 | 完全控制,无任何抽象限制;可自由组织 try-catch 逻辑。 | 极易出错:忘记 commit/rollback 或重复调用会导致资源泄漏和数据库连接耗尽;代码冗长,与业务逻辑混杂。 |
总结一句话:默认使用 TransactionTemplate,只有当它确实“碍事”时才降级到直接使用 PlatformTransactionManager。
在实际项目中,将这两种编程式事务工具与声明式事务合理搭配,就能应对从最简单到最苛刻的各类业务场景,既保持了代码的整洁,又获得了精准的事务控制力。