经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
事务提交之后再执行某些操作 → 引发对 TransactionSynchronizationManager 的探究
来源:cnblogs  作者:青石路  时间:2024/1/29 10:53:25  对本文有异议

开心一刻

  昨晚,小妹跟我妈聊天

  小妹:妈,跟你商量个事,我想换车,资助我点呀

  妈:哎呀,你那分扣的攒一堆都够考清华的,还换车资助点,有车开就不错了

  小妹:你要是这么逼我,别说哪天我去学人家傍大款啊

  妈:哎呀妈,你脸上那褶子比你人生规划都清晰,咋地,大款缺地图呀,找你?

  小妹:让我回到我18岁,大个、水灵、白,你再看看

  妈:你18长的像黑鱼棒似的,还水灵白,消防栓水灵,也没见谁娶它呀,女人呐,你得有内涵

前情回顾

  在记一次线上问题 → 偶尔的热情真的难顶呀!

  我们知道了女神偶尔的消息可能是借钱

  那你到底是借还是不借?

  不好意思,貌似抓错重点了

  重点应该是:把消息发送从事务中拎出来就好了,也就是等事务提交后,再发消息

  什么,没看记一次线上问题 → 偶尔的热情真的难顶呀!,不知道重点,那还不赶紧去看?

  我光提了重点,但是没给你们具体实现,就问你们气不气?

  本着认真负责的态度,我还是提供几种实现,谁让我太宠你们了

事务拎出来

  说起来很简单,做起来其实也很简单

  犯病拎

  为了更接近真实案例,我把

  调整一下

   User更新 和 插入操作日志 在一个事务中, 发消息 需要拎出去

  拎出去还不简单,看我表演

  相信大家都能看懂如上代码,上游调用 update 的地方也不用改,简直完美!

  大家看仔细了, update 上的 @Transactional(rollbackFor = Exception.class) 被拿掉了,不是漏写了!

  如果 update 上继续保留 @Transactional(rollbackFor = Exception.class) 

  是什么情况?

  那不是和没拎出来一样了吗?特么的还多写了几行代码!

  回到刚拎出来的情况, update 和 updateUser 在同一个类中,非事务方法 update 调用了事务方法 updateUser ,事务会怎么样?

  如果你还没反应过来,八股文需要再背一背了:在同一个类中,一个非事务方法调用另一个事务方法,事务不会生效

  恭喜你,解决一个 bug 的同时,成功引入了另一个 bug 

  你懵的同时,你老大也懵

  你们肯定会问:非事务方法 update 调用事务方法 updateUser ,事务为什么会失效了?

  巧了,正好我有答案:记一次线上问题 → 事务去哪了

  别扭拎

  同一个类中,非事务方法调用事务方法,事务不生效的解决方案中,是不是有这样一种解决方案:自己注册自己

  我们 debug 一下,看下堆栈情况

  我们先看 update 

  调用链中没有事务相关内容

  我们再看 updateUser 

  调用链中有事务相关内容

  从结果来看,确实能够满足要求,上游调用 update 的地方也不用调整,并且还自给自足,感觉是个好方案呀

  但 自己注册自己 这种情况,你们见得多吗,甚至见过吗

  反正我看着好别扭,不知道你们有这种感觉没有?

  要不将就着这么用?

  常规拎

   自己注册自己 是非常不推荐的!

  为什么不推荐? 来来来,把脸伸过来

  怎么这么多问题,非要把我榨干?

  那我就说几点

  1、违反了单一职责原则,一个类应该只负责一件事情,如果它开始依赖自己,那么它的职责就不够清晰,这可能会导致代码难以维护和扩展

  2、循环依赖,自己依赖自己就是最简单版的循环依赖,虽说 Spring 能解决部分循环依赖,但 Spring 是不推荐循环依赖写法的

  3、导致一些莫名其妙的问题,还非常难以排查,大家可以 Google 一下,关键字类似: Spring 自己注入自己 有什么问题 

  推荐的做法是新建一个 UserManager ,类似如下

  此时,上游调用的地方也需要调整,改调用 com.qsl.manager.UserManager#update ,如下所示:

  同样 debug 下,来看看堆栈信息

   com.qsl.manager.UserManager#update 调用栈情况如下

  非常简单,没有任何的代理

  我们再看下 com.qsl.service.impl.UserServiceImpl#updateUser 

  此时,调用链中是有事务相关内容的

  是不是很完美的将消息发送从事务中抽出来了?

  这确实也是我们最常用的方式,没有之一!

  惊喜拎

  既不想新增 UserManager ,又想把消息发送从事务中抽离出来,还要保证事务生效,并且不能用 自己注册自己 ,有什么办法吗

  好处全都要,坏处往外撂,求求你,做个人吧


  但是,注意转折来了!

  最近我还真学了一个新知识: TransactionSynchronizationManager ,发现它完美契合上述的既要、又要、还要、并且要

  我们先回到最初的版本

  接下来看我表演,稍微调整下代码

  什么,调整了哪些,看的不够直观?

  我真是服了你们这群老六,那我就再爱你们一次,让你们看的更直观,直接 beyond compare 下

  就调整这么一点,上游调用 update 的地方也不用调整,你们的既要、又要、还要、并且要就满足了!

  是不是很简单?

  为了严谨,我们来验证一下

  如何验证了?

  最简单的办法就是在发送消息的地方打个断点,如下所示

  当 debug 执行到此的时候,消息是未发送的,这个没问题吧?

  那么我们只需要验证:此时事务是否已经提交

  问题又来了,如何验证事务已经提交了呢?

  很简单,我们直接去数据库查对应的记录,是不是修改之后的数据,如果是,那就说明事务已经提交,否则说明事务没提交,能理解吧?

  我们以修改 张三 的密码为例, bebug 未开始,此时 张三 的密码是 zhangsan1 

  我们把 张三 的密码改成 zhangsan2 

  开始 bebug 

  此时,消息还未发送,我们去数据库查下 张三 的密码

  此时 张三 的密码已经是 zhangsan2 了,是修改之后的数据,说明了什么?

  说明事务已经提交了,而此时消息还未发送!
  是不是很优雅的实现了最初的重点:把消息发送从事务中拎出来就好了,也就是等事务提交后,再发消息

TransactionSynchronizationManager

  从字面意思来看,就是一个事务同步管理器

  概况

   TransactionSynchronizationManager 是 Spring 框架中提供的一个工具类,主要用于管理事务的同步操作

  通过 TransactionSynchronizationManager ,开发者可以自定义实现 TransactionSynchronization 接口或继承 TransactionSynchronizationAdapter 

  从而在事务的不同阶段(如提交前、提交后、回滚后等)执行特定的操作(如发送消息)

   TransactionSynchronizationManager 提供了很多静态方法, registerSynchronization 就是其中之一(其他的大家自行去学习)

  入参类型是 TransactionSynchronization ,该接口定义了几个事务同步方法(命名很好,见名知意)

  分别代表着在事务的不同阶段,会被执行的操作,比如 afterCommit 会在事务提交后执行

  底层原理

  为什么事务提交后一定会执行 org.springframework.transaction.support.TransactionSynchronization#afterCommit ?

  幕后一定有操盘手,我们来揪一揪它

  怎么揪?

  正所谓: 源码之下无密码 ,我们直捣黄龙干源码

  问题又来了, Spring 源码那么多,我们怎么知道哪一部分跟 TransactionSynchronization 有关?

  很简单,去 bebug 的堆栈中找,很容易就能找到切入点

  切入点是不是很明显了: org.springframework.transaction.support.AbstractPlatformTransactionManager#commit 

  1. /**
  2. * This implementation of commit handles participating in existing
  3. * transactions and programmatic rollback requests.
  4. * Delegates to {@code isRollbackOnly}, {@code doCommit}
  5. * and {@code rollback}.
  6. * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
  7. * @see #doCommit
  8. * @see #rollback
  9. */
  10. @Override
  11. public final void commit(TransactionStatus status) throws TransactionException {
  12. if (status.isCompleted()) {
  13. throw new IllegalTransactionStateException(
  14. "Transaction is already completed - do not call commit or rollback more than once per transaction");
  15. }
  16. DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
  17. if (defStatus.isLocalRollbackOnly()) {
  18. if (defStatus.isDebug()) {
  19. logger.debug("Transactional code has requested rollback");
  20. }
  21. processRollback(defStatus, false);
  22. return;
  23. }
  24. if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
  25. if (defStatus.isDebug()) {
  26. logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
  27. }
  28. processRollback(defStatus, true);
  29. return;
  30. }
  31. processCommit(defStatus);
  32. }
View Code

  通过 commit 的源码,或者上图的调用链,我们会继续来到 org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit 

  1. /**
  2. * Process an actual commit.
  3. * Rollback-only flags have already been checked and applied.
  4. * @param status object representing the transaction
  5. * @throws TransactionException in case of commit failure
  6. */
  7. private void processCommit(DefaultTransactionStatus status) throws TransactionException {
  8. try {
  9. boolean beforeCompletionInvoked = false;
  10. try {
  11. boolean unexpectedRollback = false;
  12. prepareForCommit(status);
  13. triggerBeforeCommit(status);
  14. triggerBeforeCompletion(status);
  15. beforeCompletionInvoked = true;
  16. if (status.hasSavepoint()) {
  17. if (status.isDebug()) {
  18. logger.debug("Releasing transaction savepoint");
  19. }
  20. unexpectedRollback = status.isGlobalRollbackOnly();
  21. status.releaseHeldSavepoint();
  22. }
  23. else if (status.isNewTransaction()) {
  24. if (status.isDebug()) {
  25. logger.debug("Initiating transaction commit");
  26. }
  27. unexpectedRollback = status.isGlobalRollbackOnly();
  28. doCommit(status);
  29. }
  30. else if (isFailEarlyOnGlobalRollbackOnly()) {
  31. unexpectedRollback = status.isGlobalRollbackOnly();
  32. }
  33. // Throw UnexpectedRollbackException if we have a global rollback-only
  34. // marker but still didn't get a corresponding exception from commit.
  35. if (unexpectedRollback) {
  36. throw new UnexpectedRollbackException(
  37. "Transaction silently rolled back because it has been marked as rollback-only");
  38. }
  39. }
  40. catch (UnexpectedRollbackException ex) {
  41. // can only be caused by doCommit
  42. triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
  43. throw ex;
  44. }
  45. catch (TransactionException ex) {
  46. // can only be caused by doCommit
  47. if (isRollbackOnCommitFailure()) {
  48. doRollbackOnCommitException(status, ex);
  49. }
  50. else {
  51. triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
  52. }
  53. throw ex;
  54. }
  55. catch (RuntimeException | Error ex) {
  56. if (!beforeCompletionInvoked) {
  57. triggerBeforeCompletion(status);
  58. }
  59. doRollbackOnCommitException(status, ex);
  60. throw ex;
  61. }
  62. // Trigger afterCommit callbacks, with an exception thrown there
  63. // propagated to callers but the transaction still considered as committed.
  64. try {
  65. triggerAfterCommit(status);
  66. }
  67. finally {
  68. triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
  69. }
  70. }
  71. finally {
  72. cleanupAfterCompletion(status);
  73. }
  74. }
View Code

  大家仔细看这个方法,在 doCommit(status) 之前有 triggerBeforeCommit(status) 、 triggerBeforeCompletion(status) 

   doCommit(status) 之后有 triggerAfterCommit(status) 、 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED) 

  这几个方法的作用很明显了吧( trigger 是触发的意思)

  接下来我们跟哪个方法?

  很明显,我们要跟 triggerAfterCommit(status) ,因为我们要找的是 afterCommit 的操盘手

  内容很简单,下一步跟的对象也很明确

  这里要分两步说明下

  1、 TransactionSynchronizationManager.getSynchronizations() 

  先获取所有的事务同步器,然后进行排序

  排序先撇开,我们先看看获取到了哪些事务同步器

  第一个不眼熟,我们先不管

  第二个眼不眼熟?是不是就是 com.qsl.service.impl.UserServiceImpl#update 中的匿名内部类?(如果想看的更明显,就不要用匿名内部类)

  是不是就对应上了:先注册,再获取,最后被调用

  被调用就是下面的第 2 步

  2、 invokeAfterCommit 

  逻辑很简单,遍历所有事务同步器,逐个调用事务同步器的 afterCommit 方法

  我们案例中的 发消息 就是在此处被执行了

  至此,相信大家都没疑惑了吧

总结

  1、关于 Spring 循环依赖,大家可以翻阅下我之前的博客

    Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

    再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

    三探循环依赖 → 记一次线上偶现的循环依赖问题

    四探循环依赖 → 当循环依赖遇上 BeanPostProcessor,爱情可能就产生了!

    总之一句话:一定要杜绝循环依赖!

  2、事务提交之后再执行某些操作的实现方式

    事务失效的方式,大家一定要警惕,这坑很容易掉进去

    自己注册自己的方式,直接杜绝,就当没有这种方式

     Manager 方式很常规,可以使用

     TransactionSynchronizationManager 方式很优雅,推荐使用

    看了这篇博客后,该用哪种方式,大家心里有数了吧

  3、TransactionSynchronizationManager 使用有限制条件

    具体看其注释说明,就当给你们留的家庭作业了

    一定要去看,不然使用出了问题可别怪我没提醒你们

原文链接:https://www.cnblogs.com/youzhibing/p/17985069

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号