Spring 编程式事务(Programmatic Transaction)详细使用指南
这份文档讲的是“不用 @Transactional,而是你在代码里手动开事务/提交/回滚”的玩法。适合:需要更细粒度控制、需要在循环/批处理里分段提交、需要按条件决定是否回滚、需要跨多个调用点拼装事务边界等场景。
1. 先把结论说清楚:什么时候用编程式事务
更适合用编程式事务的场景
- 一个方法里有多段业务:某些段失败要回滚,某些段失败不回滚(或只回滚部分)。
- 循环批处理:比如 1 万条数据,想每 500 条提交一次,失败只回滚当前批次。
- 按条件决定提交/回滚:比如校验不通过就回滚;或某些异常允许提交。
- 需要显式指定事务属性:传播行为、隔离级别、超时时间、只读等在运行时动态决定。
- 多数据源/多事务管理器:你要手动选用哪个
PlatformTransactionManager。
不建议用编程式事务的场景
- 业务简单、只要“进方法 -> 开事务 -> 结束提交/异常回滚”:直接
@Transactional更省心。 - 你不想承担“忘记回滚/提交”的心智负担:声明式事务更不容易出错。
2. Spring 事务模型速览(理解几个核心对象)
Spring 编程式事务围绕三个核心东西:
- PlatformTransactionManager:事务管理器(真正干活的,开启/提交/回滚)。
- JDBC/MyBatis 常用:
DataSourceTransactionManager - JPA 常用:
JpaTransactionManager
- JDBC/MyBatis 常用:
- TransactionDefinition:事务定义(传播行为、隔离级别、超时、只读等配置)。
- 常用实现:
DefaultTransactionDefinition
- 常用实现:
- TransactionStatus:事务状态(当前事务是否新建、是否回滚标记、savepoint 等)。
编程式事务主要有两种写法:
- TransactionTemplate(推荐):更安全、更短、更不容易漏提交/回滚
- PlatformTransactionManager 手动 getTransaction/commit/rollback:更底层、更灵活(但更容易写错)
3. 写法一:TransactionTemplate(推荐)
3.1 基本用法(最常见)
@Service
public class OrderService {
private final TransactionTemplate transactionTemplate;
private final OrderMapper orderMapper;
private final StockMapper stockMapper;
public OrderService(PlatformTransactionManager txManager,
OrderMapper orderMapper,
StockMapper stockMapper) {
this.transactionTemplate = new TransactionTemplate(txManager);
this.orderMapper = orderMapper;
this.stockMapper = stockMapper;
}
public Long createOrder() {
return transactionTemplate.execute(status -> {
// 1) 写订单
orderMapper.insert(...);
// 2) 扣库存
stockMapper.decrease(...);
// 3) 返回结果(会在 execute 结束时自动 commit)
return 123L;
});
}
}
execute(...)内部抛出运行时异常:默认会回滚。execute(...)正常返回:默认提交。
3.2 捕获异常但仍然回滚(容易踩坑点)
很多人会写成这样:
transactionTemplate.execute(status -> {
try {
doBiz();
} catch (Exception e) {
// 你 catch 了异常,Spring 看不到异常了 -> 会提交!
}
return null;
});
正确写法:catch 了异常也要显式标记回滚:
transactionTemplate.execute(status -> {
try {
doBiz();
} catch (Exception e) {
status.setRollbackOnly(); // 显式回滚标记
// 你也可以记录日志/转换异常
}
return null;
});
或者更直接:catch 后重新抛出运行时异常,让 Spring 自动回滚。
3.3 动态设置事务属性(传播/隔离/超时/只读)
TransactionTemplate tt = new TransactionTemplate(txManager);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
tt.setTimeout(5); // 秒
tt.setReadOnly(false);
tt.execute(status -> {
// ...
return null;
});
注意:
readOnly=true并不是“禁止写”,更多是提示数据库/驱动做优化;是否强制看数据库实现。
3.4 无返回值写法(TransactionCallbackWithoutResult)
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// ...
}
});
4. 写法二:直接用 PlatformTransactionManager(更底层)
4.1 标准模板
public void doWorkManually() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// 业务逻辑
doBiz();
txManager.commit(status);
} catch (Exception ex) {
txManager.rollback(status);
throw ex;
}
}
这种写法的特点:你完全掌控 commit/rollback,但也意味着:
- 你必须保证所有路径都能正确回滚/提交
- 代码更长,重复更多
4.2 需要 Savepoint(局部回滚)
Spring TransactionStatus 支持 savepoint(底层要支持,比如 JDBC)。
TransactionStatus status = txManager.getTransaction(def);
Object sp = null;
try {
step1();
sp = status.createSavepoint(); // 创建保存点
step2(); // 这里可能失败
step3();
txManager.commit(status);
} catch (Exception e) {
if (sp != null) {
status.rollbackToSavepoint(sp); // 仅回滚到保存点
status.releaseSavepoint(sp);
// 你也可以继续执行后续逻辑
txManager.commit(status);
} else {
txManager.rollback(status);
}
}
注意:Savepoint 不是“嵌套事务”的万能替代,它只是当前事务里的局部回滚点。
5. 事务传播行为(Propagation)怎么选(非常关键)
常用传播行为:
- REQUIRED(默认):有事务就加入,没有就新建。适合大多数业务。
- REQUIRES_NEW:不管外面有没有事务,都新开一个(外部事务挂起)。适合:记录日志/写审计/发通知等“我不想被外部回滚影响”的操作。
- NESTED:嵌套事务(基于 savepoint)。外部事务存在时创建 savepoint;外部没事务就等同 REQUIRED。适合:希望“外部整体回滚”,但内部可以局部回滚。
一个典型例子:批处理分段提交
需求:循环处理 1000 条,每条失败不影响其他条。
for (Long id : ids) {
transactionTemplate.execute(status -> {
try {
handleOne(id);
} catch (Exception e) {
status.setRollbackOnly(); // 回滚当前条
}
return null;
});
}
如果你希望“外部还有大事务,但每条独立提交”,就用 REQUIRES_NEW:
TransactionTemplate tt = new TransactionTemplate(txManager);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
for (Long id : ids) {
tt.execute(status -> {
handleOne(id);
return null;
});
}
6. 隔离级别(Isolation)怎么理解(别乱调)
Spring 的隔离级别基本就是数据库隔离级别的映射:
- READ_UNCOMMITTED:可能脏读(一般别用)
- READ_COMMITTED:避免脏读(很多库默认)
- REPEATABLE_READ:避免不可重复读(MySQL InnoDB 常见默认)
- SERIALIZABLE:最严格,性能最差
要点:
- 隔离级别是数据库层能力,Spring 只是把参数传下去。
- 如果你的数据源/数据库不支持你设置的隔离级别,可能会被忽略或抛异常(取决于驱动实现)。
7. 回滚规则(Rollback Rules)在编程式事务里怎么做
声明式事务里你会写:@Transactional(rollbackFor = Exception.class)
编程式事务里,核心逻辑是:
- 抛运行时异常(RuntimeException/Error):默认回滚
- 捕获异常:默认不会回滚(因为异常被吞了)
- 想回滚但不想抛异常:
status.setRollbackOnly()
示例:遇到业务异常也要回滚,但你想返回错误码而不是抛异常:
Result r = transactionTemplate.execute(status -> {
try {
doBiz();
return Result.ok();
} catch (BizException e) {
status.setRollbackOnly();
return Result.fail(e.getCode(), e.getMessage());
}
});
8. Spring Boot + MyBatis 下的典型配置要点
如果你是 Spring Boot + MyBatis(常见组合):
- 默认会自动装配
DataSource - 事务管理通常是
DataSourceTransactionManager - 你只要注入
PlatformTransactionManager或TransactionTemplate即可
8.1 直接注入 TransactionTemplate
@Service
public class PayService {
@Resource
private TransactionTemplate transactionTemplate;
public void pay() {
transactionTemplate.execute(status -> {
// MyBatis Mapper 调用在同一事务里
// ...
return null;
});
}
}
8.2 如果你有多数据源(多个事务管理器)
你需要指定你要用哪个事务管理器:
@Bean
public TransactionTemplate orderTxTemplate(@Qualifier("orderTxManager") PlatformTransactionManager tm) {
return new TransactionTemplate(tm);
}
然后在业务里注入 @Qualifier("orderTxTemplate") 使用。
多数据源“跨库一致性”别指望一个本地事务解决;要么上分布式事务/最终一致性,要么做业务补偿。
9. 编程式事务的常见坑(踩一次就长记性)
-
catch 了异常没回滚
- 解决:要么抛运行时异常,要么
status.setRollbackOnly()
- 解决:要么抛运行时异常,要么
-
事务没生效(尤其是你以为“调用了 @Transactional 就行”)
- 编程式事务通常不会遇到“自调用失效”问题,因为它不是靠代理拦截
- 但如果你混用声明式事务,要注意 AOP 代理规则
-
REQUIRES_NEW 被外部连接池/线程模型影响
- 它会挂起外部事务并新开事务,底层需要再拿一个连接
- 连接池太小可能导致等待甚至死锁式卡住
-
在事务里做慢 IO(HTTP 调用、发 MQ、写大文件)
- 事务时间越长,锁持有越久,性能越差
- 通常做法:事务里只做 DB 关键写;IO 用 outbox/事件/消息异步做
-
只读事务里写数据
- 不同数据库表现不一致;别赌实现,读写分开最省心
10. 和 @Transactional 的对比(你怎么选)
| 维度 | @Transactional(声明式) | 编程式事务 |
|---|---|---|
| 代码量 | 少 | 多 |
| 事务边界可读性 | 强(注解就是边界) | 取决于你写的位置 |
| 动态控制(运行时决定传播/隔离等) | 不方便 | 很方便 |
| 分段提交/循环批处理 | 麻烦 | 天生适合 |
| 出错概率 | 相对低 | 相对高(忘记回滚等) |
经验法则:默认用 @Transactional,遇到复杂边界/批处理/动态策略再上编程式事务(优先 TransactionTemplate)。
11. 一个“真实感”更强的例子:下单 + 库存 + 失败降级
需求:
- 写订单、扣库存必须同一个事务
- 发送“站内信/通知”失败不影响下单成功(独立事务)
@Service
public class TradeService {
private final TransactionTemplate mainTx;
private final TransactionTemplate newTx; // REQUIRES_NEW
private final OrderMapper orderMapper;
private final StockMapper stockMapper;
private final NoticeMapper noticeMapper;
public TradeService(PlatformTransactionManager tm,
OrderMapper orderMapper,
StockMapper stockMapper,
NoticeMapper noticeMapper) {
this.mainTx = new TransactionTemplate(tm);
this.newTx = new TransactionTemplate(tm);
this.newTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
this.orderMapper = orderMapper;
this.stockMapper = stockMapper;
this.noticeMapper = noticeMapper;
}
public void placeOrder(Long userId, Long skuId) {
mainTx.execute(status -> {
orderMapper.insertOrder(userId, skuId);
stockMapper.decrease(skuId);
// 通知:失败也不影响主事务
try {
newTx.execute(s2 -> {
noticeMapper.insertNotice(userId, "下单成功");
return null;
});
} catch (Exception ignore) {
// 记录日志即可,不影响主流程
}
return null;
});
}
}
12. 排查事务问题的实用手段
- 打开 Spring 事务日志:
org.springframework.transaction/org.springframework.jdbc.datasource调到 DEBUG
- 打印当前是否在事务中:
TransactionSynchronizationManager.isActualTransactionActive()
- MyBatis 场景关注:同一事务内是否复用同一个 Connection(日志可看)
13. 速记小抄
- 推荐:
TransactionTemplate.execute(...) - catch 了异常要回滚:
status.setRollbackOnly() - 想让一段逻辑不受外部事务影响:
PROPAGATION_REQUIRES_NEW - 想在大事务里局部回滚:
PROPAGATION_NESTED(底层 savepoint 支持很关键) - 事务里别做慢 IO
14. 参考类/接口(你看源码时会遇到)
PlatformTransactionManagerAbstractPlatformTransactionManagerDataSourceTransactionManagerTransactionTemplateDefaultTransactionDefinitionTransactionDefinitionTransactionStatusTransactionSynchronizationManager
1019

被折叠的 条评论
为什么被折叠?



