java吧 关注:1,258,502贴子:12,751,780

求问关于Spring事务管理器多线程时主线程产生connection closed

只看楼主收藏回复

问题背景:
项目组在使用Spring事务时,通过编程式事务的形式,手动在业务代码中注入DataSourceTransactionManager,创建DefaultTransactionDefinition后开启事务,等待业务代码处理完成后再手动进行commit or rollback。
正常使用的时候没有问题,但是由于项目组成员较多,部分粗心的组员在使用编程式事务时,会忘记提交或者在事务提交之前就return了,导致事务挂起,业务高并发下甚至产生连接池占满,新业务无法获取到数据库连接的情况,导致生产事故。
为了避免组员忘记关闭事务,我对编程式事务的开启做了一定的封装,并且在事务开始后,声明一个Timer计时器,约定计时器结束时如果事务没有关闭,则将事务强制进行回滚。这样一定程度上防止了事务忘记关闭的问题,当程序员发现业务数据经常被回滚时,就可能注意到是他忘了提交事务,数据库的连接也得到了释放。
通过计时器已经实现了上述功能,但是出现了一个问题。计时器相当于开启了一个子线程,我将事务传递到子线程中,计时器触发后,在子线程中进行了事务回滚。之后回到主线程,如果此时我希望登记一条系统错误日志在数据库中,在插入日志表时,主线程数据库就会报错Cause: java.sql.SQLException: connection closed。经过多次调试,发现确实是子线程完成强制回滚后,主线程的数据库连接就会断开,导致我的日志无法插入。
在网上搜了一些资料,无法解决该问题。部分代码我截图粘在楼下


IP属地:云南1楼2024-10-16 17:26回复
    开启事务代码,通过创建DefaultTransactionDefinition事务声明,从dataSourceTransactionManager中获取事务状态,然后将事务状态和spring事务管理器都传到我自定义的TransactionManager类中


    IP属地:云南2楼2024-10-16 17:29
    收起回复
      广告
      立即查看
      TransactionManager类构造函数,为了解决事务从主线程传递到子线程的问题,我参考了知乎的一篇文章,将TransactionSynchronizationManager中的ResourceMap复制到了子线程中


      IP属地:云南3楼2024-10-16 17:31
      回复
        创建计时器,计时器触发时调用spring事务管理器的回滚方法


        IP属地:云南4楼2024-10-16 17:33
        回复
          测试类:
          开启事务,设置5秒后事务超时,进行回滚
          业务场景模拟堵塞10秒,以达到事务超时
          阻塞结束后插入日志表


          IP属地:云南5楼2024-10-16 17:35
          回复
            运行结果:
            事务正常开启并插入一条记录
            由于阻塞导致事务超时

            事务回滚成功,之后主线程堵塞结束,开始插入错误日志:

            主线程发生connection closed


            IP属地:云南6楼2024-10-16 17:39
            回复
              求问这个问题产生的根源,以及有没有什么解决办法?


              IP属地:云南7楼2024-10-16 17:40
              收起回复
                异步任务,同步等待完成,会有死锁问题。不知道你日志记录的是什么,也要在同一个事物中。记录本次操作失败完全可以不开事物,如果你日志要操作多个表,那就新开一个事物。


                IP属地:北京来自Android客户端9楼2024-10-16 19:19
                收起回复
                  广告
                  立即查看
                  感觉你想复杂了,DefaultTD里面不是能setTimeout吗,日志考虑新起一个事务做就行,是否异步看你需求


                  IP属地:四川来自Android客户端10楼2024-10-16 21:39
                  收起回复
                    从设计上来说,你的计时器就是一个额外的检测功能,原则上就不应该使用共享被监测者的事物连接,你这样的耦合就是会完成这样矛盾的结果,所以最好的解决办法就是你的检测者自己另起一个事物


                    IP属地:广东来自Android客户端11楼2024-10-16 23:16
                    收起回复
                      走多一个异步线程,new一个新事务保存数据?


                      IP属地:广东来自Android客户端12楼2024-10-17 07:05
                      收起回复
                        有意思,我看看源码这里咋实现的


                        IP属地:广东来自Android客户端13楼2024-10-17 08:50
                        收起回复
                          分析完了,先说结论:可以解决,但可能不是你想要的实现方式。
                          再说下执行流程:
                          1、开启事务的时候会创建一个 Connection 连接
                          2、将主线程的 TransactionSynchronizationManager 事务信息拷贝给子线程,其实是在共用一个事务的 Connection 连接
                          3、当子线程回滚或提交事务时,会关闭 Connection 连接,同时清除 TransactionSynchronizationManager 中线程独有的变量信息
                          4、由于主线程中的 TransactionSynchronizationManager 不与子线程共享,当子线程清除数据时并不会影响到主线程的 TransactionSynchronizationManager 线程变量,所以在子线程回滚后主线程继续执行数据库操作时会用旧的事务信息(此时对应的 Connection 已关闭)去执行操作,所以此时就会报出“连接已关闭”的错误信息。


                          IP属地:广东14楼2024-10-17 10:08
                          回复
                            我想到的解决方法有两个:
                            1、在主线程中当子线程回滚事务后手动清除 TransactionSynchronizationManager 残留的事务信息
                            2、设置 savepoint 保存点,创建事务时就创建保存点,然后子线程回滚时直接回滚到保存点即可,这样事务既不会终止,后面对应的写日志请求也还能正常运行,即还在事务的范围内。
                            但这两种方法都有弊端。


                            IP属地:广东15楼2024-10-17 10:11
                            收起回复
                              广告
                              立即查看
                              方案一关键配置



                              IP属地:广东17楼2024-10-17 10:24
                              收起回复