怎么解决数据库事务居然没生效问题

70次阅读
没有评论

共计 3488 个字符,预计需要花费 9 分钟才能阅读完成。

本篇内容介绍了“怎么解决数据库事务居然没生效问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Spring 声明式事务提供给 Javaer 们方便的事务配置方式,再搭配 Spring  Boot 自动配置,基本只需在方法上添加 @Transactional 注解,即可瞬间开启方法的事务性配置。

但仅为方法添加 @Transactional 注解

你就以为这就够了吗?

事务未被正确处理,一般不会导致停止服务,更不易在测试阶段复现。但随系统业务越来越复杂,就会带来大量数据不一致问题,随后就是大量线上问题而后人工排查检修数据。

1 你的 Spring 事务怎么才算生效?

使用 @Transactional 开启声明式事务时,灵魂发问:事务生效了吗?

案例

用户表实体类

 

DAO 层

根据 username 查询所有数据

@Repository public interface UserRepository extends JpaRepository UserEntity, Long  { List UserEntity  findByName(String name); }

Service 层

UserService 类

负责业务逻辑处理,包括如下方法:

createUserWrong1 调用 private 方法:

createUserPrivate,被 @Transactional 注解。当传入的用户名包含 test 则抛异常,让用户的创建操作失败,期望事务回滚:

getUserCount

 

Controller 层

调用一下刚才定义的 UserService 中的入口方法 createUserWrong1。

测试结果

即便用户名不合法,用户也能创建成功。刷新浏览器,多次发现非法用户注册。

2 @Transactional 怎么确保生效?

除非特殊配置 (比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。

Spring 默认通过动态代理实现 AOP,对目标方法增强,private 方法无法代理到,自然也无法动态增强事务处理逻辑。

那简单,把 createUserPrivate 方法改为 public 即可。

在 UserService 中再建一个入口方法 createUserWrong2,来调用这个 public 方法再次尝试:

public int createUserWrong2(String name) { try { this.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error( create user failed because {} , ex.getMessage()); } return userRepository.findByName(name).size(); } // 标记了 @Transactional 的 public 方法  @Transactional public void createUserPublic(UserEntity entity) { userRepository.save(entity); if (entity.getName().contains(test)) throw new RuntimeException(invalid username!  }

新的 createUserWrong2 方法事务同样不生效。

必须通过代理过的类从外部调用目标方法

要调用增强过的方法必然是调用代理后的对象。

尝试修改 UserService,注入一个 self,然后再通过 self 实例调用标记有 @Transactional 注解的 createUserPublic 方法。设置断点可以看到,self 是由 Spring 通过 CGLIB 方式增强过的类。

CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强;

this 指针代表对象自己,Spring 不可能注入 this,所以通过 this 访问方法必然不是代理。

把 this 改为 self,在 Controller 中调用 createUserRight 方法可以验证事务生效了:非法的用户注册操作可以回滚。

虽然在 UserService 内部注入自己调用自己的 createUserPublic 可以正确实现事务,但这不符合习惯用法。更合理的实现方式是,让 Controller 直接调用之前定义的 UserService 的 createUserPublic 方法。

@GetMapping(right2) public int right2(@RequestParam( name) String name) { try { userService.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error( create user failed because {} , ex.getMessage()); } return userService.getUserCount(name); }

this 自调用 /self 调用 /Controller 调用 UserService

this 自调用

        无法走到 Spring 代理类

后两种

        调用的 Spring 注入的 UserService,通过代理调用才有机会对 createUserPublic 方法进行动态增强。

推荐在开发时打开相关 Debug 日志,以了解 Spring 事务实现的细节。

比如 JPA 数据库访问,可以这么开启 Debug 日志:

logging.level.org.springframework.orm.jpa=DEBUG

开启日志后再比较下在 UserService 中 this 调用、Controller 中通过注入的 UserService  Bean 调用 createUserPublic 的区别。

很明显,this 调用因没走代理,事务没有在 createUserPublic 生效,只在 Repository 的 save 生效:

//  在 UserService 中通过 this 调用 public 的 createUserPublic [23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT // 在 Controller 中通过注入的 UserService Bean 调用 createUserPublic [10:10:47.750] [http-nio-45678-exec-6] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

这种实现在 Controller 里处理异常显得繁琐,还不如直接把 createUserWrong2 加 @Transactional 注解,然后在 Controller 中直接调用该方法。

“怎么解决数据库事务居然没生效问题”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-27发表,共计3488字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)