SpringBoot 事务管理完整详解
在后端开发中只要涉及多表联动操作新增员工新增工作经历、银行转账、下单扣库存就一定会用到事务。如果没有事务控制一旦中间步骤报错就会出现一半数据入库、一半丢失的数据错乱问题。一、基础认知事务是什么四大ACID特性1. 事务定义事务是一组数据库操作的集合是不可分割的最小工作单元这一组SQL要么全部执行成功提交要么全部失败回滚不存在中间状态。典型业务场景银行转账A扣钱、B加钱任意一步失败两边数据都恢复原样新增员工主表emp插入员工、子表emp_expr批量插入工作经历报错则两张表都不存数据下单流程创建订单、扣减库存、生成物流单2. 事务四大ACID特性特性全称核心解释原子性Atomicity事务不可拆分全成功/全失败事务最核心特性一致性Consistency事务执行前后数据库数据整体合法、状态统一转账前后总金额不变隔离性Isolation多个并发事务之间互相隔离互不干扰避免脏读、不可重复读等问题持久性Durability事务提交后数据修改永久保存到磁盘宕机也不会丢失二、底层基础MySQL原生手动事务控制在Spring封装事务之前数据库原生提供三条指令手动管控事务这是Spring事务的底层原理。1. 三条核心SQL-- 1. 开启事务starttransaction;-- 等价写法begin;-- 业务SQL多条增删改insertintoemp(...)values(...);insertintoemp_expr(...)values(...);-- 2. 全部执行成功提交事务数据永久生效commit;-- 3. 任意步骤异常回滚事务撤销所有操作rollback;2. 实战案例新增员工双表操作begin;-- 插入员工主表insertintoemp(username,name,gender,phone,dept_id)values(zhangsan,张三,1,13300001111,1);-- 插入员工工作经历子表insertintoemp_expr(emp_id,begin,end,company,job)values(39,2019-01-01,2022-01-01,百度,开发);-- 无异常提交两张表数据同时入库commit;-- 若第二条insert报错执行rollback主表数据也会消失rollback;原生事务痛点每次业务都要手动写开启、提交、回滚代码冗余异常捕获逻辑重复Spring提供声明式事务Transactional简化开发。三、SpringBoot核心Transactional 声明式事务1. 注解作用将被标注的方法交给Spring事务管理器自动管控完全替代手动写MySQL事务指令方法执行前自动开启事务方法正常执行完毕自动commit提交事务方法抛出异常自动rollback回滚事务2. 注解可以标注的位置推荐优先级方法 类 接口Service层方法生产环境首选精准控制只有当前业务方法开启事务灵活性最高。ServicepublicclassEmpServiceImplimplementsEmpService{OverrideTransactional// 仅save方法开启事务publicvoidsave(Empemp){// 新增员工批量新增经历}}Service实现类上当前类所有public方法都会开启事务适合整个类所有操作都需要事务的场景。Service接口上不推荐仅JDK动态代理生效CGLIB代理场景事务失效兼容性差企业开发极少使用。3. 基础实战新增员工多表事务案例需求新增员工时同时保存员工主表、批量保存员工多条工作经历中间报错全部回滚。① Mapper层铺垫插入后回填自增主键新增员工后需要拿到数据库自增的id作为子表emp_id外键使用Options注解自动回填主键MapperpublicinterfaceEmpMapper{// useGeneratedKeys开启自增主键获取// keyProperty主键值回填到Emp实体的id字段Options(useGeneratedKeystrue,keyPropertyid)Insert(insert into emp(username,name,gender,phone,job,salary,image,entry_date,dept_id,create_time,update_time) values(#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime}))voidinsert(Empemp);}调用empMapper.insert(emp)后直接emp.getId()即可拿到数据库生成的主键。② 批量插入动态SQL批量保存多条工作经历MyBatis动态SQLforeach遍历集合拼接SQLinsertidinsertBatchinsert into emp_expr(emp_id,begin,end,company,job) valuesforeachcollectionexprListitemitemseparator,(#{item.empId},#{item.begin},#{item.end},#{item.company},#{item.job})/foreach/insertforeach核心属性说明collection待遍历集合名item遍历循环中的单个对象separator每条数据之间的分隔符逗号open/close循环前后拼接片段批量in查询常用③ Service层事务完整代码ServicepublicclassEmpServiceImplimplementsEmpService{AutowiredprivateEmpMapperempMapper;AutowiredprivateEmpExprMapperempExprMapper;OverrideTransactional// 当前方法整体受事务管控publicvoidsave(Empemp){// 1. 补全创建、更新时间emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());// 2. 插入员工主表自动回填idempMapper.insert(emp);// 3. 获取自增主键给工作经历设置外键IntegerempIdemp.getId();ListEmpExprexprListemp.getExprList();if(!CollectionUtils.isEmpty(exprList)){exprList.forEach(item-item.setEmpId(empId));// 4. 批量插入工作经历子表empExprMapper.insertBatch(exprList);}// 模拟异常int i 1/0; 报错后两张表数据全部回滚}}测试代码中添加除零异常执行后emp、emp_expr两张表都不会新增任何数据事务生效。四、Transactional 进阶核心属性面试高频1. rollbackFor控制什么异常触发回滚默认规则Spring事务只会在运行时异常RuntimeException、错误Error发生时自动回滚编译时异常Exception子类不会触发回滚。业务痛点如果方法抛出文件不存在、IO异常等编译异常数据不会回滚造成脏数据。全局解决方案捕获所有Exception都回滚// 任意Exception都触发事务回滚Transactional(rollbackForException.class)publicvoidsave(Empemp){// 业务代码}2. propagation事务传播行为嵌套事务核心定义当一个加了Transactional的方法调用另一个带事务的方法时子方法该复用外层事务、还是新建独立事务由传播行为控制。最常用两种场景REQUIRED默认值绝大多数业务使用逻辑外层已有事务子方法加入同一个事务外层无事务新建事务案例新增员工、保存经历共用一个事务要么一起成功要么一起回滚REQUIRES_NEW独立事务互不干扰逻辑无论外层是否存在事务子方法新建完全独立的事务外层回滚不会影响子方法提交的数据经典业务场景操作日志永久保存需求新增员工无论成功/失败操作日志必须入库不能跟着员工事务回滚。日志落地完整代码日志Service使用REQUIRES_NEW新建独立事务ServicepublicclassEmpLogServiceImplimplementsEmpLogService{AutowiredprivateEmpLogMapperempLogMapper;// 独立事务不受外层员工事务影响OverrideTransactional(propagationPropagation.REQUIRES_NEW)publicvoidinsertLog(EmpLogempLog){empLogMapper.insert(empLog);}}新增员工主业务finally中强制记录日志OverrideTransactional(rollbackForException.class)publicvoidsave(Empemp){try{emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());empMapper.insert(emp);IntegerempIdemp.getId();ListEmpExprexprListemp.getExprList();if(!CollectionUtils.isEmpty(exprList)){exprList.forEach(item-item.setEmpId(empId));empExprMapper.insertBatch(exprList);}// int i 1/0; // 模拟业务异常}finally{// 无论try成功/报错finally一定执行日志独立入库EmpLoglognewEmpLog(null,LocalDateTime.now(),新增员工emp.getName());empLogService.insertLog(log);}}测试即使员工新增代码抛出异常员工数据回滚但操作日志依然保存到数据库两个事务完全隔离。其余传播行为简要说明传播属性核心逻辑使用场景SUPPORTS外层有事务就共用无事务则无事务执行查询类方法不需要强制事务NOT_SUPPORTED挂起外层事务子方法无事务执行纯查询、不需要事务的操作MANDATORY必须存在外层事务否则直接抛异常子方法依赖外层事务禁止单独调用NEVER禁止存在外层事务有外层事务直接报错明确要求无事务执行的方法五、事务调试开启事务日志打印开发排查事务提交/回滚异常时可在application.yml配置事务管理器debug日志控制台打印完整事务执行流程logging:level:# Spring事务管理器日志org.springframework.jdbc.support.JdbcTransactionManager:debug配置后可以清晰看到事务开启、SQL执行、事务提交/回滚完整日志快速定位事务失效问题。六、总结与面试核心考点梳理事务ACID四大特性原子性是核心一致性是最终目标隔离性解决并发冲突持久性保证数据落地。原生事务三步begin开启、commit提交、rollback回滚SpringTransactional自动封装这三步。Transactional基础推荐标注在Service层public方法默认仅RuntimeException回滚建议统一配置rollbackForException.class。事务传播行为REQUIRED默认多表联动新增、修改共用一个事务REQUIRES_NEW日志、消息记录等需要独立持久化不受外层事务回滚影响。实战重点场景多表CRUD必须加事务防止数据不一致操作日志、异步记录等场景使用REQUIRES_NEW隔离事务批量插入使用MyBatisforeach动态SQL简化代码自增主键回填依靠Options(useGeneratedKeys true)。Transactional只对public方法生效private/protected方法标注无效同类中方法自调用A方法调用本类带事务的B方法事务会失效需通过注入自身代理对象调用传播行为REQUIRES_NEW会创建独立连接频繁使用会消耗数据库连接池资源按需使用。