spring-kafka导致spring事务失效的解决办法
spring-boot版本:2.1.16.RELEASE
spring-kafka版本:2.2.14.RELEASE
数据库:mybatis-plus + oracle
发现问题
一个spring-boot工程,已经通过配置spring.kafka.producer.transactionIdPrefix
开启KafkaTemplate的executeInTransaction
事务功能。因为业务上需要发送kafka消息后同时更新数据库记录状态,结果发现单元测试跑完后数据库并不会回滚。
调试过程
- 测试类已经使用
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
进行标注,@Rollback标注在类上或者测试方法上均无效。
- 数据库操作的service方法已经标注@Transactional,在此方法内下断点,通过Evaluate Expression
TransactionSynchronizationManager#isSynchronizationActive()
发现结果是false - 定位到
TransactionAspectSupport#determineTransactionManager
方法,发现此方法返回了一个类型为KafkaTransactionManager
的对象,至此问题初步定位为spring没有拿到正确的transcationManager
源码分析
查看KafkaAutoConfiguration
发现注册了KafkaTransactionManager
对象,这个类还继承了AbstractPlatformTransactionManager
因为项目使用的mybaits-plus,并配置了spring.datasource
,没有使用hibernate。打开DataSourceTransactionManagerAutoConfiguration
发现默认的transactionManager
注册条件是没有PlatformTransactionManager
,而KafkaTransactionManager
又正好是这个类型,所以这里根本不会注册。在注册transactionManager的方法里面下断点,果然不会进去。
Google搜索
搜索一下类似的问题,找到这个帖子https://stackoverflow.com/questions/47354521/transaction-synchronization-in-spring-kafka
解决问题
需要增加一个Configuration,
- 是要解决
DataSourceTransactionManagerAutoConfiguration
默认不注册transactionManager
的问题 - 是让
transactionManager
变成默认的事务管理器,免得在所有@Transactional
指明事务管理器名称 - 是要增加一个
chainedTransactionManager
使得kafka和数据库操作同时在事务中
@Configuration
public class TransactionConfig {
private final DataSource dataSource;
private final TransactionManagerCustomizers transactionManagerCustomizers;
TransactionConfig(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = transactionManagerCustomizers.getIfAvailable();
}
@Bean //去掉了ConditionalOnMissingBean解决问题1
@Primary //解决问题2
public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
@Bean //解决问题3
public ChainedKafkaTransactionManager chainedKafkaTransactionManager(DataSourceTransactionManager transactionManager,
KafkaTransactionManager<?, ?> kafkaTransactionManager){
return new ChainedKafkaTransactionManager<>(transactionManager, kafkaTransactionManager);
}
}
最后修改单元测试类(junit4)上的注解为:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional(transactionManager = "chainedKafkaTransactionManager")
问题解决