问题描述
今天将项目中spring boot版本升级到3.3.4 后发现原来entityManager.merge()方法抛出异常
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [xx.modules.org.models.Department#0000000000000000000001]
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:426)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:214)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:152)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:136)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:89)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:854)
[2025-04-21 11:27:55] [1]-[xx.core.SystemOutPrint]-[INFO] … 61 more
排查思路
从报错信息中看是多事务提交产生的问题,但是我的代码,是在项目启动时做初始化,应该不存在这个问题。
既然没有解决思路,于是去google错误,经过一番搜索大部分结果都是指向了 @GeneratedValue 主键策略,最后在一篇文章中找到了蛛丝马迹。
大概意思是说hiberante 6.6之后如果是新实体的话必须将主键设置为null才能保存。
随后我将ID设置成了null,果然数据成功保存了。
原因分析
升级spring boot 3.4.x之后,hiberante会升级到6.6,6.6之后的hiberante对merge逻辑进行了修改从之前的主键数据库中不存在就插入新数据改为了主键数据库中不存在就抛出异常,导致了上述问题。
解决方案
解决方案有三个方向:
1.主键设置null;
2.降低spring boot(hiberante)版本;
3.想办法覆盖hiberante中merge代码逻辑;
1.主键设置null
这个方案是最简单的方案,让hiberante根据主键策略去自动生成,但是项目中有些数据确实是需要手动设置id的,所以该方案并不能解决需求。
2.降低spring boot(hiberante)版本
既然hiberante6.6版本不行,那么换种思路我不用6.6版本到是不是就可以了,由于使用的是spring boot随意降低hiberante版本的话可能会产生问题。使用hiberante6.5的版本对应的是spring boot 3.3.x版本。经测试该版本可行。
3.想办法覆盖hiberante中merge代码逻辑
既然方案2就可以完美解决问题为什么还会有方案3,那就不得不说spring 官方维护了, 3.3版本的社区支持马上就要结束了。后续除非购买企业支持否则是没有维护的,所以就出现了方案三。
分析hiberante 源码
类 org.hibernate.event.internal.DefaultMergeEventListener 是关于Merge的处理逻辑
6.5的源码
if ( result == null ) {
//TODO: we should throw an exception if we really *know* for sure
// that this is a detached instance, rather than just assuming
//throw new StaleObjectStateException(entityName, id);
// we got here because we assumed that an instance
// with an assigned id was detached, when it was
// really persistent
entityIsTransient( event, clonedIdentifier, copyCache );
}
else {
// before cascade!
copyCache.put( entity, result, true );
final Object target = targetEntity( event, entity, persister, id, result );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
cascadeOnMer