springboot ApplicationEvent调用(或者@Async方法调用)导致 JPA 的save方法未执行

问题发现

之前某个功能一直正常的,直到一天突然发现,有个操作未正常生成数据。通过debug发现,问题关联功能执行的save方法未执行成功,且未出现异常,save方法执行了,只是数据未保存。
同时发现,此方法还有一个controller方法入口,通过测试,发现此方法调用是正常的,数据可以正常保存。
但出现问题的地方,是从ApplicationEvent事件进来的,猜测是因为异步方法(新的子线程)造成的。因此模拟@Async异步调用,也能复现这个bug。

寻找解决

通过debug源码一直未能找到问题原因。
网上搜索,发现有类似解决:用JpaTransactionManager替换DataSourceTransactionManager作为系统的事务管理器。

问题验证

通过debug发现当前事务管理器确实是DataSourceTransactionManager,按理说应该是默认的JpaTransactionManager才对,因为配置springboot的JPA时,会默认注入JpaTransactionManager的bean。(详见:JpaBaseConfiguration类)

问题解决

全局搜索DataSourceTransactionManager时,发现近期有手动注入DataSourceTransactionManager的bean对象,原来是引入了mybatis组件,就同时定义了事务管理器。可是DataSourceTransactionManager与JpaTransactionManager都是TransactionManager的实现类,所以JpaTransactionManager就不会生成(JpaBaseConfiguration类中:@ConditionalOnMissingBean({TransactionManager.class})),所以DataSourceTransactionManager成为了系统的默认事务管理器。
解决:从配置类中,将DataSourceTransactionManager的Bean去掉即可

思考

为什么DataSourceTransactionManager可以支持同步调用(单线程)的JPA,而异步调用的就不行?
而JpaTransactionManager则可以?
通过读JpaTransactionManager源码时,发现doBegin(事务开始)有一段值得注意的代码

	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		JpaTransactionObject txObject = (JpaTransactionObject) transaction;

		if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
			throw new IllegalTransactionStateException(
					"Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
					"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
					"It is recommended to use a single JpaTransactionManager for all transactions " +
					"on a single DataSource, no matter whether JPA or JDBC access.");
		}

		try {
			// 事务对象的entityManagerHolder是否为空?且 资源是否与事务同步?
			if (!txObject.hasEntityManagerHolder() ||
					txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
				// 则创建新的EntityManager
				EntityManager newEm = createEntityManagerForTransaction();
				if (logger.isDebugEnabled()) {
					logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
				}
				// 设置EntityManagerHolder
				txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
			}
			...省略后续代码

所以我认为是,DataSourceTransactionManager没有针对异步调用时,对事务的处理。而JpaTransactionManager有

posted @   daibiao123  阅读(455)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示