You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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等操作,均未解决。

问题列表

  1. @PrePersist修改publicId是否会引发该异常?如何处理?
  2. 是否存在多线程同时修改FlightInstance的情况?如何验证与缓解?
  3. 为何Flight实体无异常而FlightInstance出现问题?
  4. 如何解决该异常,确保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正常保存?

结合前面的分析,按以下步骤排查解决:

  1. 检查版本字段配置:确认FlightInstance和基类中的@Version字段正确,没有被@PrePersist或其他逻辑修改。
  2. 修复级联操作:查看FlightInstance的关联关系(比如@OneToMany、@ManyToOne),如果级联类型设置为CascadeType.MERGE或ALL,检查关联的实体是否存在版本冲突,或者是否不需要级联合并,改为CascadeType.PERSIST或移除不必要的级联。
  3. 确保实体状态正确:在调用save方法前,确保FlightInstance是新实体(未被持久化),或者是从当前EntityManager加载的托管实体,避免传入游离状态的实体导致Hibernate执行merge操作(堆栈中显示异常来自merge流程)。如果是新实体,可改用saveAndFlush明确触发持久化。
  4. 隔离测试数据:测试用例中,每个测试方法创建独立的FlightInstance,避免共享数据导致的冲突。
  5. 添加重试机制:在服务层方法上添加乐观锁重试,示例代码:
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);
}
  1. 检查事务配置:确保服务层的事务边界正确,避免同一实体在多个事务中被修改。比如,是否存在嵌套事务导致的实体状态不一致。

内容的提问来源于stack exchange,提问作者AbdulmalikAlayande

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:15:58