公司动态
Spring @Transactional 完整避坑指南 + 生产级真实业务事务设计
一、Transactional 说明Transactional是 Spring 提供的声明式事务注解底层基于 AOP 动态代理实现用来保证一组数据库操作的原子性要么全部成功要么全部失败。核心工作流程方法执行前开启事务正常执行完成提交事务抛出指定异常自动回滚事务常用核心属性rollbackFor指定哪些异常触发回滚propagation事务传播行为isolation事务隔离级别readOnly标记为只读事务用于优化查询异常回滚规则重点Spring 默认行为对RuntimeException和Error自动回滚对普通Exception受检异常不回滚因此生产环境统一推荐写法Transactional(rollbackFor Exception.class)Exception 与 RuntimeException 区别Exception所有异常的父类包含受检异常和运行时异常RuntimeException属于Exception的子类无需强制捕获只写rollbackFor RuntimeException.class等价于默认行为风险极高业务异常可能不回滚二、Transactional 常见不回滚坑附示例1. 异常被 try-catch 捕获没有抛出Transactional(rollbackFor Exception.class) public void test() { try { aMapper.insert(); throw new RuntimeException(); } catch (Exception e) { // 异常被吞事务不回滚 } }2. 抛出受检异常但未配置 rollbackForTransactional public void test() throws Exception { aMapper.insert(); throw new Exception(); // 不回滚 }3. 事务方法不是 publicTransactional private void test() { aMapper.insert(); throw new RuntimeException(); }4. 同类内部使用 this 调用事务方法public void outer() { this.inner(); } Transactional public void inner() { aMapper.insert(); throw new RuntimeException(); }5. 外层方法捕获内层事务异常// 外层 public void outer() { try { innerService.test(); } catch (Exception e) { // 吞异常导致内层事务不回滚 } } // 内层 Transactional public void test() { aMapper.insert(); throw new RuntimeException(); }6. 数据表引擎为 MyISAMCREATE TABLE a (id BIGINT) ENGINE MyISAM;7. 在新线程中执行数据库操作Transactional public void test() { new Thread(() - { aMapper.insert(); throw new RuntimeException(); }).start(); }8. 传播行为设置为 NOT_SUPPORTED / NEVERTransactional(propagation Propagation.NOT_SUPPORTED) public void test() { aMapper.insert(); throw new RuntimeException(); }9. 类未被 Spring 管理无 Service/Componentpublic class AService { Transactional public void test() { aMapper.insert(); } }10. 多数据源未指定事务管理器Transactional public void test() { // 操作非默认数据源 bMapper.update(); }三、实战场景插入 → 更新 → 写日志 → 发通知业务流程插入 A 表返回主键 ID根据 A 的 ID 更新 B 表记录操作日志发送消息通知设计原则A、B 操作必须原子一致放在同一事务日志独立事务无论成功失败都要记录消息在事务提交后发送避免数据回滚但消息已发出完整实现1. 主业务 ServiceService public class BizService { Autowired private AMapper aMapper; Autowired private BMapper bMapper; Autowired private LogService logService; Autowired private MsgService msgService; public void process(BizDTO dto) { Long aId null; try { aId insertAAndUpdateB(dto); logService.saveLog(aId, 执行成功, 1); msgService.sendMsg(aId); } catch (Exception e) { logService.saveLog(aId, 执行失败 e.getMessage(), 0); throw e; } } Transactional(rollbackFor Exception.class) public Long insertAAndUpdateB(BizDTO dto) { // 插入 A 表 A a new A(); a.setName(dto.getName()); aMapper.insert(a); Long aId a.getId(); // 更新 B 表 B b new B(); b.setAId(aId); bMapper.updateById(b); return aId; } }2. 日志 Service独立事务Service public class LogService { Autowired private LogMapper logMapper; Transactional(propagation Propagation.REQUIRES_NEW, rollbackFor Exception.class) public void saveLog(Long aId, String content, Integer status) { Log log new Log(); log.setAId(aId); log.setContent(content); log.setStatus(status); logMapper.insert(log); } }3. 消息 ServiceService public class MsgService { public void sendMsg(Long aId) { System.out.println(消息推送成功关联ID aId); } }场景效果A、B 任意一步异常 → 主事务回滚无脏数据主事务回滚不影响日志记录仅事务完全成功后才推送消息保证业务一致性四、Transactional 用法总结核心必记生效前提必须加在public方法上类必须被 Spring 管理Service/Component必须通过代理对象调用避免同类 this 直接调用回滚配置生产统一使用rollbackFor Exception.class不要依赖默认行为避免受检异常不回滚事务范围控制只把核心数据库操作放入事务不要包太多逻辑避免在事务内做远程调用、复杂计算、消息发送、文件 IO 等慢操作防止大事务导致锁等待、连接占用、性能下降异常处理不要随意吞异常catch 后要么重新抛出要么手动回滚手动回滚可用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();传播行为使用场景默认REQUIRED适用于绝大多数业务日志、监控记录等使用REQUIRES_NEW新开独立事务查询方法可使用SUPPORTS或不加事务其他规范数据库引擎必须使用 InnoDB多数据源场景必须指定对应的事务管理器只读查询建议加readOnly true便于优化复杂业务事务设计核心增删改同一事务保证原子性日志记录独立事务不影响主流程消息 / 通知 / 第三方调用放在事务提交后执行保证数据与消息最终一致