Spring Boot中FlightInstance保存时遇StaleObjectStateException求助
Spring Boot中FlightInstance保存时触发ObjectOptimisticLockingFailureException异常
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [app.bola.flywell.data.model.flight.FlightInstance#16f60701-d478-45ca-bbd4-59fc53aa4ca3] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:325) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) at jdk.proxy2/jdk.proxy2.$Proxy193.save(Unknown Source) at app.bola.flywell.services.flightservice.FlyWellFlightInstanceService.createNew(FlyWellFlightInstanceService.java:52) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) at app.bola.flywell.services.flightservice.FlyWellFlightInstanceService$$SpringCGLIB$$0.createNew(<generated>) at app.bola.flywell.services.flightservice.FlightInstanceServiceTest.createNewFlightInstance_NewFlightIsCreatedTest(FlightInstanceServiceTest.java:41) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [app.bola.flywell.data.model.flight.FlightInstance#16f60701-d478-45ca-bbd4-59fc53aa4ca3] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:426) at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:214) at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:152) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:136) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138) at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:875) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:846) at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:258) at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:248) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:570) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:492) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:253) at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:604) at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:534) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:495) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:253) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:192) at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:667) at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:285) at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:220) at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:152) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:136) at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:89) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:854) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) at jdk.proxy2/jdk.proxy2.$Proxy181.merge(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:630) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:752) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ... 20 more
背景信息
技术栈包含Spring Boot、spring-boot-starter-data-jpa与hibernate-core。FlightInstance实体继承自FlyWellModel基类,通过@PrePersist钩子生成publicId;Flight实体采用类似配置却无异常。
在服务层调用FlightInstanceRepository的save方法时触发异常,已尝试移除继承、重命名实体、禁用@PrePersist等操作,均未解决。
问题列表
- @PrePersist修改publicId是否会引发该异常?如何处理?
- 是否存在多线程同时修改FlightInstance的情况?如何验证与缓解?
- 为何Flight实体无异常而FlightInstance出现问题?
- 如何解决该异常,确保FlightInstance正常保存?
解决方案与解答
1. @PrePersist修改publicId是否会引发该异常?如何处理?
正常情况下,@PrePersist仅在实体首次持久化前执行,不会触发乐观锁异常。但如果publicId被错误配置为版本字段(比如加了@Version注解),或者@PrePersist逻辑中意外修改了版本号,才会导致异常。另外,如果基类FlyWellModel中存在版本字段,而FlightInstance的继承映射(比如@Inheritance策略)有问题,也可能干扰版本控制。
处理方式:
- 检查
publicId是否被标记为@Version,如果是,移除该注解,版本字段单独用一个属性(比如private Integer version)。 - 确认@PrePersist方法仅修改publicId,不触碰版本字段或其他持久化状态相关属性。
- 验证基类的继承映射配置,确保FlightInstance的版本字段正确继承或重写。
2. 是否存在多线程同时修改FlightInstance的情况?如何验证与缓解?
从堆栈看,异常发生在测试用例中,可能是测试代码本身存在多线程操作,或者Spring事务管理导致的重复提交。
验证方式:
- 在FlightInstance的save/update方法前后添加日志,打印当前线程ID和实体ID,查看是否有同一ID被多个线程操作。
- 检查测试代码,是否存在并行测试(比如用了
@ParallelTest)或者异步操作,导致同一FlightInstance被同时修改。 - 开启Hibernate的SQL日志(
spring.jpa.show-sql=true、spring.jpa.properties.hibernate.format_sql=true),查看是否有重复的UPDATE语句针对同一实体。
缓解方式:
- 如果是测试环境问题,确保测试用例之间隔离,每个测试用例使用独立的实体数据,避免共享。
- 生产环境中,对FlightInstance的修改操作添加乐观锁重试机制,比如用Spring的
@Retryable注解捕获ObjectOptimisticLockingFailureException并重试。 - 避免在多线程环境中共享同一个实体对象,每次修改都从数据库重新加载最新版本。
3. 为何Flight实体无异常而FlightInstance出现问题?
核心原因是两个实体的映射或使用场景存在差异:
- 关联关系不同:FlightInstance可能关联了其他实体(比如航班座位、时刻表),而关联的实体在保存时触发了级联更新,导致FlightInstance的版本号被意外修改。从堆栈的
Cascade.cascadeCollection可以看出,异常发生在级联合并操作中,说明FlightInstance存在需要级联处理的集合关联,而Flight没有。 - 继承映射差异:虽然都继承自FlyWellModel,但可能Flight使用了不同的继承策略(比如
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)),而FlightInstance用了JOINED或TABLE_PER_CLASS,导致版本字段的映射逻辑不同。 - 使用场景不同:Flight可能只做单线程的创建/查询,而FlightInstance在测试或业务中存在多线程修改、重复提交的场景,或者事务边界配置不同(比如Flight的save在单个事务中,而FlightInstance的save跨多个事务)。
4. 如何解决该异常,确保FlightInstance正常保存?
结合前面的分析,按以下步骤排查解决:
- 检查版本字段配置:确认FlightInstance和基类中的
@Version字段正确,没有被@PrePersist或其他逻辑修改。 - 修复级联操作:查看FlightInstance的关联关系(比如
@OneToMany、@ManyToOne),如果级联类型设置为CascadeType.MERGE或ALL,检查关联的实体是否存在版本冲突,或者是否不需要级联合并,改为CascadeType.PERSIST或移除不必要的级联。 - 确保实体状态正确:在调用save方法前,确保FlightInstance是新实体(未被持久化),或者是从当前EntityManager加载的托管实体,避免传入游离状态的实体导致Hibernate执行merge操作(堆栈中显示异常来自merge流程)。如果是新实体,可改用
saveAndFlush明确触发持久化。 - 隔离测试数据:测试用例中,每个测试方法创建独立的FlightInstance,避免共享数据导致的冲突。
- 添加重试机制:在服务层方法上添加乐观锁重试,示例代码:
import org.springframework.retry.annotation.Retryable; import org.springframework.orm.ObjectOptimisticLockingFailureException; @Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3) public FlightInstance createNew(FlightInstance flightInstance) { return flightInstanceRepository.save(flightInstance); }
- 检查事务配置:确保服务层的事务边界正确,避免同一实体在多个事务中被修改。比如,是否存在嵌套事务导致的实体状态不一致。
内容的提问来源于stack exchange,提问作者AbdulmalikAlayande
相关产品推荐
相关产品推荐

