Hello 大家好, 这里是Anyin。
给大家说一个笑话:我被ChatGPT忽悠了。
引言
前两天在编码的时候遇到一个事务未生效的问题,当时感觉比较诡异。因为我一个方法注解了@Transactional
注解,但是事务未生效,而且这个方法是kafka监听之后调用的。
我出于对kafka的不熟悉,第一反应就是问下ChatGPT,结果竟然被它忽悠了!!!
实例代码如下:
这里的active就是false,说明事务未开始。
ChatGPT忽悠之路
问题1 :
@KafkaListener注解的方法中调用someService.foo()方法,foo()方法注解@Transactional,事务无法生效为什么
回答1 :
根据描述,您在使用Spring Framework中的@KafkaListener注解来监听Kafka消息,并且在处理消息的方法中调用了someService.foo()方法,该方法被@Transactional注解标记。然而,您发现事务并未生效。
通常,@Transactional注解只对在同一个线程中的方法调用才会生效。在您的情况下,@KafkaListener注解会在一个新的线程中启动消息监听器,因此该方法调用someService.foo()将在一个新的线程中执行。这意味着事务管理器无法自动地控制这个新线程中的事务。
为了解决这个问题,您可以尝试在@KafkaListener方法中使用Spring的TransactionTemplate或者PlatformTransactionManager手动开启一个新的事务。
问题2:
可是注解了@ Transactional 不会自动新开一个事务吗
回答2:
是的,使用@Transactional注解的方法应该会自动创建一个新的事务,但是在您的情况下,由于@KafkaListener方法被注解标记,它会在另一个线程中异步执行,因此会导致@Transactional注解失效。
这是因为Spring事务管理器默认使用ThreadLocal机制来管理事务,每个线程都有自己的事务状态。在@KafkaListener方法中调用的someService.foo()方法实际上是在另一个线程中执行,而不是在发起消息的线程中执行,因此事务无法跨越线程进行管理。
为了解决这个问题,您可以在@KafkaListener方法中手动开启一个新的事务。这样可以确保在处理Kafka消息时创建新的事务并管理事务的生命周期。您可以使用TransactionTemplate或者PlatformTransactionManager手动开启一个新的事务,具体实现可以参考上面的代码示例。
通过以上的2轮回答,我当时也没多想,认为是@KafkaListener
这个注解的内部机制,导致的事务失效,并且当时也随手把这两个问答发到了微头条,没想到微头条竟然小爆了下,给我带来了6块钱的收益。
问题排查
被ChatGPT忽悠之后,我也是跟着它这个思路,认为是@KafkaListener
内部机制导致的问题。那么kafka作为一个主流的MQ,不可能忽视这个问题。
所以,通过ChatGPT问答和Google,我了解到了一个KafkaTransactionManager
这么一个类,认为这是解决问题的关键(其实已经被带偏了),通过一通的操作,最后还是没解决问题。
到这里,已经陷入深深的自我怀疑当中,这么多年的Java代码都白写了吗?
接着,不信邪的我,干脆写4个用例,自测下。
- 正常的Controller,
同步调用
Service,Service添加@Transactional注解,Service内部同步调用
Update方法 - 正常的Controller,
同步调用
Service,Service添加@Transactional注解,Service内部异步
执行Update方法 - 正常的Controller,
异步调用
Service,Service添加@Transactional注解,Service内部同步
调用Update方法 - 正常的Controller,
异步发送
Kafka,Kafka消费者调用Service,Service添加@Transactional注解,Service内部同步调用
Update方法
Controller 代码如下:
Service 代码如下:
作为一个多年的老Java程序猿,我当然知道@Transactional
只会在当前线程生效,所以在我的预想当中,肯定只有innerAsyncUpdate
方法不会进行事务回滚,另外两个肯定会回滚。
然后,事情出乎我的意料,4个场景,都没有回滚!!!
这个时候我才意识到,这个不是
@KafkaListener
注解机制的问题了,应该是整个项目配置问题,从而导致整个事务都没有启动!!!
问题解决
本次问题知道原因了,那就好解决了。
理论上,SpringBoot项目应该是默认启用事务管理器的,但是项目配置没有,所以我就手动启用事务管理器,在启动类上添加上@EnableTransactionManagement
注解。
接着再使用测试用例1测试下,发现报异常了。
嗯? 没有事务管理器? 理论上,应该也是默认初始化的呀!!
好吧,那我继续手动初始化一个事务管理器就好。
继续测试,OK!! 问题解决了,4个测试用例,都能正常工作,符合预期。
好了,问题解决了,但是为什么不会自动启用事务管理,为什么没有初始化事务管理器实例?
带着这2个问题,我根据DataSourceTransactionManager
找到了自动初始化它的配置类。
到这里,看了下各个Condition条件,都是符合的,为什么没有初始化?
这个时候反应过来,该不会像DataSourceAutoConfiguration
一样,被exclude了吧?
果然,在启动类上被exclude了。
至此,诡异的问题解决了:就是项目初始化的时候把事务给去掉了。(谁干的!!!)
而我也被ChatGPT忽悠了一次,闹了一个笑话。
最后
现在ChatGPT很火,并且最近还开放了插件生态,以后可能就是所有流量的入口了,可能会带来一大批职业失业的危机,但是也带来机遇。
作为一个普通人,改变不了什么,只能拥抱变化,尽量去体验下。当然现在ChatGPT还不是那么成熟,还需要自己甄别下,因为它经常会一本正经的胡说八道。
另外到3月26日为止,我再问ChatGPT一样的问题,我发现它已经不忽悠我了,已经能正确解答问题(虽然解决不了我的问题)。