Appearance
编程式事务
一、什么是编程式事务
编程式事务是通过代码手动控制事务的开启、提交和回滚,与声明式事务(@Transactional)不同,它不依赖 AOP 代理,而是直接操作 Spring 的事务 API。
声明式事务:加个注解,Spring 帮你管理事务(你只管写业务逻辑)
编程式事务:自己写代码,手动开启、提交、回滚事务(你说了算)二、为什么需要编程式事务
声明式事务虽然方便,但有一个致命缺陷:事务边界是整个方法。当你需要更细粒度的控制时,声明式事务就无能为力了。
java
// 场景:批量处理 1000 条数据,希望每 100 条提交一次
// 声明式事务无法做到——要么全提交,要么全回滚
// 编程式事务可以:
for (int i = 0; i < 1000; i++) {
transactionTemplate.execute(status -> {
orderMapper.insert(orders.get(i));
return null;
}); // 每次循环独立提交
}编程式事务的适用场景:
- 需要在同一个方法内分段提交/回滚
- 事务的提交/回滚取决于复杂的运行时条件
- 需要精确控制事务边界(循环中每条记录独立事务)
- 声明式事务因代理问题失效,又不想重构代码
- 需要部分提交的能力(如批量导入跳过失败的行)
三、两种实现方式
3.1 TransactionTemplate(推荐)
TransactionTemplate 是 Spring 对 PlatformTransactionManager 的封装,简化了事务管理代码。
java
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
// 成功自动提交
return null;
});
}
}带条件回滚:
java
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
orderMapper.insert(order);
// 库存不足,主动回滚
int stock = inventoryMapper.getStock(order.getProductId());
if (stock < order.getQuantity()) {
status.setRollbackOnly(); // 标记回滚
return null;
}
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
return null;
});
}自定义传播行为:
java
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
// 设置 REQUIRES_NEW:每次都开新事务
transactionTemplate.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(status -> {
orderMapper.insert(order);
return null;
});
}
// 或创建新实例
public void createOrderWithIsolation(Order order) {
TransactionTemplate template = new TransactionTemplate(
transactionManager,
new DefaultTransactionDefinition(
TransactionDefinition.PROPAGATION_REQUIRES_NEW
)
);
template.execute(status -> {
orderMapper.insert(order);
return null;
});
}
}完整配置:
java
@Configuration
public class TransactionConfig {
@Bean
public TransactionTemplate transactionTemplate(
PlatformTransactionManager transactionManager) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
// 设置传播行为
template.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRED);
// 设置隔离级别
template.setIsolationLevel(
TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置超时时间(秒)
template.setTimeout(30);
// 设置只读
template.setReadOnly(false);
return template;
}
}3.2 PlatformTransactionManager(底层 API)
更底层,完全手动控制,适合需要精确控制事务生命周期的场景。
java
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
public void createOrder(Order order) {
// ① 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setTimeout(30);
def.setReadOnly(false);
// ② 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// ③ 执行业务逻辑
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
// ④ 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// ⑤ 回滚事务
transactionManager.rollback(status);
throw e;
}
}
}TransactionStatus 常用方法:
java
// 判断是否是新事务
status.isNewTransaction();
// 判断是否有 savepoint
status.hasSavepoint();
// 标记回滚(事务不会立即回滚,会在 finally 回滚)
status.setRollbackOnly();
// 判断是否已被标记为回滚
status.isRollbackOnly();
// 判断事务是否已完成
status.isCompleted();
// 创建 savepoint
Object savepoint = status.createSavepoint();
// 回滚到 savepoint
status.rollbackToSavepoint(savepoint);
// 释放 savepoint
status.releaseSavepoint(savepoint);四、两种方式对比
| TransactionTemplate | PlatformTransactionManager | |
|---|---|---|
| 复杂度 | 低 | 高 |
| 代码量 | 少 | 多 |
| 提交/回滚 | 自动(返回值/异常) | 手动调用 commit/rollback |
| 异常处理 | 自动回滚(RuntimeException) | 需手动 try-catch + rollback |
| 事务状态 | 函数式,状态不暴露 | 可获取 TransactionStatus |
| 适用场景 | 90% 的编程式事务场景 | 需要 savepoint 或精确控制 |
五、实战场景
场景1:批量处理,每条独立事务
java
@Service
public class BatchService {
@Autowired
private TransactionTemplate transactionTemplate;
public void batchImport(List<Order> orders) {
int successCount = 0;
int failCount = 0;
for (Order order : orders) {
try {
transactionTemplate.execute(status -> {
orderMapper.insert(order);
return null;
});
successCount++;
} catch (Exception e) {
log.error("订单 {} 导入失败", order.getId(), e);
failCount++; // 失败的不影响其他
}
}
log.info("批量导入完成:成功 {},失败 {}", successCount, failCount);
}
}场景2:每 100 条提交一次
java
@Service
public class BatchService {
@Autowired
private TransactionTemplate transactionTemplate;
public void batchInsert(List<Order> orders) {
final int BATCH_SIZE = 100;
List<List<Order>> batches = Lists.partition(orders, BATCH_SIZE);
for (List<Order> batch : batches) {
transactionTemplate.execute(status -> {
for (Order order : batch) {
orderMapper.insert(order);
}
return null;
});
// 每 100 条提交一次,释放数据库资源
}
}
}场景3:部分提交 + 错误收集
java
@Service
public class BatchService {
@Autowired
private TransactionTemplate transactionTemplate;
public BatchResult batchImport(List<Order> orders) {
List<Order> failedOrders = new ArrayList<>();
int successCount = 0;
for (Order order : orders) {
try {
transactionTemplate.execute(status -> {
// 校验
if (order.getAmount() <= 0) {
status.setRollbackOnly();
return null;
}
orderMapper.insert(order);
return null;
});
successCount++;
} catch (Exception e) {
failedOrders.add(order);
}
}
return new BatchResult(successCount, failedOrders);
}
}场景4:事务内分段操作(savepoint)
java
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
public void createOrderWithSteps(Order order) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 步骤1:创建订单
orderMapper.insert(order);
// 步骤2:创建 savepoint(扣库存失败不影响订单)
Object savepoint = status.createSavepoint();
try {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
} catch (Exception e) {
// 库存不足,回滚到 savepoint
status.rollbackToSavepoint(savepoint);
// 订单还是会创建成功
}
// 步骤3:创建支付记录
paymentMapper.insert(order);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}六、声明式 vs 编程式 选择指南
┌─────────────────────────────────────────────────────┐
│ 事务管理方式选择 │
│ │
│ 你的事务场景是? │
│ │
│ ┌── 整个方法要么全成功要么全失败 │
│ │ → 声明式事务(@Transactional) │
│ │ │
│ ├── 方法内需要分段提交/回滚 │
│ │ → 编程式事务(TransactionTemplate) │
│ │ │
│ ├── 需要 savepoint 精细控制 │
│ │ → 编程式事务(PlatformTransactionManager) │
│ │ │
│ ├── 批量处理,部分失败不影响整体 │
│ │ → 编程式事务(TransactionTemplate) │
│ │ │
│ └── 声明式事务因代理问题失效 │
│ → 编程式事务(不依赖 AOP 代理) │
└─────────────────────────────────────────────────────┘七、面试要点
Q1:声明式事务和编程式事务的区别?
| 声明式 | 编程式 | |
|---|---|---|
| 实现 | @Transactional 注解 | TransactionTemplate / PlatformTransactionManager |
| 原理 | AOP 代理 | 直接调用 API |
| 事务边界 | 整个方法 | 代码块级别 |
| 侵入性 | 无 | 有 |
| 灵活性 | 低 | 高 |
Q2:为什么需要编程式事务?
声明式事务的粒度是整个方法,无法在方法内部实现分段提交/回滚。编程式事务可以精确控制事务边界,适用于批量处理、部分提交、savepoint 等场景。
Q3:TransactionTemplate 和 PlatformTransactionManager 怎么选?
TransactionTemplate:日常开发推荐,封装了提交/回滚逻辑,代码简洁PlatformTransactionManager:需要 savepoint 或完全手动控制事务生命周期时使用
