Spring Boot项目业务代码中使用@Transactional事务失效踩坑点总结( 二 )


READ_UNCOMMITTED
A事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高
READ_COMMITTED
A事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录 。可以解决脏读问题,但是不能解决不可重复读和幻读
REPEATABLE_READ
A事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读
SERIALIZABLE
串行化,可以解决任何并发问题,安全性最高,但是性能最低
timeout :事务的超时时间,默认值为 -1 。如果超过该时间限制但事务还没有完成,则自动回滚事务 。
readOnly:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true 。
rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 。
noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理 。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔 。严格控制包依赖和统一版本管理,做到最少化依赖 。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记
交流探讨qun:Shepherd_126
3.@Transactional失效场景、原因及修正方式3.1 同一个类中的方法通过this调用导致失效public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);// 新增用户userDAO.insert(user);// 添加用户角色this.addUserRole(user.getId(), param.getRoleIds());log.info("执行结束了");}@Transactional(rollbackFor = Exception.class)public void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}执行#addUser()会发现事务控制失效,发生异常事务并没有回滚,用户和角色绑定都插入成功了 。
这里,我给出@Transactional 生效原则 1,必须通过代理过的类从外部调用目标方法才能生效.
【Spring Boot项目业务代码中使用@Transactional事务失效踩坑点总结】
Spring Boot项目业务代码中使用@Transactional事务失效踩坑点总结

文章插图
图片
Spring 是通过 AOP 技术对方法进行增强实现事务控制的,要调用增强过的方法必然是调用代理后的对象,而这里this是原生对象,并不是代理,自然就没有事务控制了 。
修正方式:①:将this换成代理的userService, 可以自己注入自己@Resource private UserService userService,当然也可以不用注入,直接在Spring容器中获取userService这个bean     ②将#addUser()方法开启事务即加上@Transactional(rollbackFor = Exception.class),这里本就该开启,只是为了演示失效情况没加上,因为在#addUser()里面有插入用户的操作涉及到事务的所以本要开启 。当然如果#addUser()只是做一些判断、逻辑处理不涉及到数据库事务操作,那么这样解决就显得有点不太合适,而且容易导致另一种事务失效的情况,即因为没有正确处理异常,导致事务即便生效也不一定能回滚 。
3.2 异常被catch“吃掉了”导致@Transactional失效如下所示:
@Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {try {User user = PtcBeanUtils.copy(param, User.class);// 完成一些逻辑处理.......// 添加用户角色this.addUserRole(user.getId(), param.getRoleIds());log.info("执行结束了");} catch (Exception e) {log.error(e.getMessage());}}@Transactional(rollbackFor = Exception.class)public void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}@Transactional生效原则2:只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚 。之前我们总结过 基于AOP事务控制实现原理说过在 Spring的 TransactionAspectSupport 里有个 invokeWithinTransaction 方法,里面就是处理事务的逻辑 。可以看到,只有捕获到异常才能进行后续事务处理:


推荐阅读