编码方式

所谓编程式事务指的是通过编码方式实现事务,即类似于 JDBC 编程实现事务管理。

声明式事务管理方式

声明式事务管理又有两种实现方式:

  • 基于 xml 配置文件的方式;
  • 另一个实在业务方法上进行 @Transaction 注解,将事务规则应用到业务逻辑中;

三种方式的清单只回答了”有几种”,工程上真正需要的是:每种怎么写、何时选哪种、以及声明式事务最常踩的失效场景。本文补齐成可操作手册。

编程式事务:TransactionTemplate

@Autowired TransactionTemplate txTemplate;

public void transfer(long from, long to, BigDecimal amt) {
txTemplate.executeWithoutResult(status -> {
dao.debit(from, amt);
if (riskCheck(from)) { status.setRollbackOnly(); return; } // 显式回滚
dao.credit(to, amt);
});
}

或用更底层的 PlatformTransactionManager 手动 getTransaction/commit/rollback(不推荐,样板多易漏 rollback)。

适用场景:事务边界需要动态控制(循环中每批独立提交、条件回滚点复杂、长任务分段提交)。声明式以方法为边界,粒度不够时编程式是唯一选择。

声明式事务:注解方式(主流)

@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED, timeout = 5)
public void createOrder(OrderCmd cmd) { ... }

启用前提:配置类 @EnableTransactionManagement(Boot 自动开启)。本质是 AOP:事务切面拦截带注解的方法,围绕执行做 begin/commit/rollback。

关键属性:

  • rollbackFor:默认只回滚 RuntimeException/Error;业务检查异常不回滚是最大陷阱,规范写法永远 rollbackFor = Exception.class
  • propagation:REQUIRED(默认,加入或新建)、REQUIRES_NEW(挂起外层、独立提交,适合审计日志/独立流水)、NESTED(保存点,可局部回滚)、SUPPORTS/NOT_SUPPORTED/NEVER/MANDATORY
  • isolation:默认跟随数据库;并发写冲突/幻读问题时上调到 RC/RR
  • timeout / readOnly:超时保护与只读优化(只读事务可走从库/跳过脏检查)

XML 方式(tx:advice + aop:config) today 仅存于老项目维护,新项目一律注解。

声明式事务的失效场景(面试与事故双高频)

  1. 自调用:同类方法内部 this 调用带注解方法,绕过代理 → 事务不生效(AOP 代理模型通病)
  2. 非 public 方法:注解在 private/protected 上不生效(代理语义只承诺 public)
  3. 异常被吞:catch 后不抛出,切面看不到异常 → 照常提交
  4. rollbackFor 缺失:checked 异常默认不回滚
  5. 多线程:新线程不在事务线程的 Connection 上下文里(TransactionSynchronizationManager 基于 ThreadLocal),子线程操作不受事务管理
  6. 传播配置错误:内层 REQUIRES_NEW 已提交、外层回滚 → 内层数据残留,属设计预期但常被当 bug
  7. 数据库引擎不支持:MySQL MyISAM 无事务,注解再对也无效

选型决策

  • 边界=方法、规则静态 → @Transactional(90% 场景)
  • 边界动态/分段提交/循环批处理 → TransactionTemplate
  • 跨服务一致性 → 本地事务不够,上 Saga/TCC/事务消息(分布式事务范畴)
  • 只读查询密集接口 → @Transactional(readOnly=true) 或干脆不开事务(减少连接占用)

三种实现方式的关系:编程式是能力本体,声明式是它在 AOP 上的语法糖;选型不是”哪个先进”,而是事务边界是否与方法的静态边界吻合——吻合用注解,不吻合用模板。