Spring Boot:org.hibernate.StaleObjectStateException - 行已被另一个事务更新或删除

问题描述 投票:0回答:1

我在尝试在 Spring Boot 应用程序中保存 FlightInstance 实体时遇到 ObjectOptimisticLockingFailureException,这是由 StaleObjectStateException 引起的。完整的堆栈跟踪如下:

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#79e7f6b2-9364-49f4-97f6-cf5afa537b6b]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:325)
    at app.bola.flywell.services.flightservice.FlyWellFlightInstanceService.createNew(FlyWellFlightInstanceService.java:59)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    ...
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#79e7f6b2-9364-49f4-97f6-cf5afa537b6b]
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:426)
        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)
    ...

我的设置

我使用 Spring Boot 并具有以下依赖项:

spring-boot-starter-data-jpa
hibernate-core

FlightInstance实体继承自名为FlyWellModel的父类。相关代码如下:

父类(FlyWellModel):系统中所有模型的基类

public class FlyWellModel { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; @Column(nullable = false, unique = true) private String publicId; @CreatedDate private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; @CreatedBy @Builder.Default private String createdBy = "SYSTEM"; private String createdByRole; @LastModifiedBy private String lastModifiedBy; @PrePersist protected void onCreate() { if (publicId == null || publicId.isEmpty()) { publicId = UUID.randomUUID().toString(); } } }

子类 (FlightInstance)FlightInstance 实体具有一个 publicId 字段,该字段使用基类 FlyWellModelonCreate 方法中的 @PrePersist 注释进行填充

@Entity @Table(name = "flight_instance") public class FlightInstance extends FlyWellModel { @Column(nullable = false) private String flightNumber; @ManyToOne(fetch = FetchType.LAZY) @ToString.Exclude private Flight flight; // Other specific fields }
调用 save 方法时,

@PrePersist 挂钩会为 publicId 生成一个值,从而在事务完成之前修改实体。我怀疑此修改可能会导致 Hibernate 将实体视为“脏”,从而导致异常。

有趣的是,类似的设置非常适合

Flight 实体,其结构如下:

@Entity public class Flight extends FlyWellModel { private long duration; private String arrivalCity; private String departureCity; private String displayImage; @OneToOne(cascade = CascadeType.ALL) private Airport departureAirport; @OneToOne(cascade = CascadeType.ALL) private Airport arrivalAirport; @OneToMany(mappedBy = "flight", cascade = CascadeType.ALL, fetch = FetchType.EAGER) private List<FlightInstance> flightInstances; }

问题

服务层

FlightInstanceRepository调用save方法出现异常:

服务方式:

@Service public class FlyWellFlightInstanceService { @Autowired private FlightInstanceRepository flightInstanceRepository; @Override @Transactional @Retryable(retryFor = ObjectOptimisticLockingFailureException.class, maxAttempts = 3) public FlightInstanceResponse createNew(FlightInstanceRequest request) { Flight flight = flightRepository.findByPublicId(request.getFlightId()) .orElseThrow(() -> new EntityNotFoundException(Constants.ENTITY_NOT_FOUND.formatted("Flight"))); FlightInstance mappedFlightInstance = mapper.map(request, FlightInstance.class); mappedFlightInstance.setFlight(flight); mappedFlightInstance.setStatus(SCHEDULED); mappedFlightInstance.setFlightSeat(new ArrayList<>()); FlightInstance savedInstance = flightInstanceRepository.save(mappedFlightInstance); flight.getFlightInstances().add(savedInstance); flightRepository.save(flight); return flightInstanceResponse(savedInstance); } }

观察

FlightInstance 上的
  1. @PrePersist:在持久化时填充 publicId 字段,这会在保存操作期间修改实体。
  2. 线程干扰:我怀疑可能有多个线程在起作用。当一个线程修改 Flight 实体时,另一个线程可能会同时修改其关联的 FlightInstance 实体,从而导致冲突。

我的问题

    @PrePersist 对 publicId 字段的修改是否会导致 StaleObjectStateException?如果是这样,处理这个问题的最佳方法是什么?
  1. 多个线程是否可以同时修改 FlightInstance 实体,如何在 Spring Boot + Hibernate 设置中验证或缓解这种情况?
  2. 为什么 Flight 实体可以正常工作,而 FlightInstance 实体在类似情况下会抛出此异常? 4.如何解决这个问题,以便正确保存FlightInstance实体而不遇到StaleObjectStateException
任何指导或见解将不胜感激!谢谢!

java spring spring-boot hibernate
1个回答
0
投票
除了可能存在的更深层次的技术复杂性之外,我认为您的逻辑中存在一些缺陷,可能会造成问题。

检查下面标记的以下代码行

public FlightInstanceResponse createNew(FlightInstanceRequest request) { Flight flight = flightRepository.findByPublicId(request.getFlightId()) .orElseThrow(() -> new EntityNotFoundException(Constants.ENTITY_NOT_FOUND.formatted("Flight"))); FlightInstance mappedFlightInstance = mapper.map(request, FlightInstance.class); mappedFlightInstance.setFlight(flight); mappedFlightInstance.setStatus(SCHEDULED); mappedFlightInstance.setFlightSeat(new ArrayList<>()); FlightInstance savedInstance = flightInstanceRepository.save(mappedFlightInstance); flight.getFlightInstances().add(savedInstance); ----> flightRepository.save(flight); return flightInstanceResponse(savedInstance); }
本质上,您在 Flight 实体中有一个 OneToMany 映射,在 FlightInstance 实体中有一个 ManyToOne 映射。对于您当前的注释,默认用途是在数据库中,FlightInstance 表将包含一个附加列,该列将每个 FlightInstance 行与特定的 Flight 实体进行映射。

您不需要仅仅因为更新了映射就保留 Flight 实体。这已经在持久层中自动发生,因为映射是由 FlightInstance 实体拥有的,并且已经通过

flightInstanceRepository.save(mappedFlightInstance);

 进行了持久化。

由于当前在 2 个相关实体的同一个事务方法中有 2 个 .save 调用(

flightRepository.save

flightInstanceRepository.save
),并且实际的保存过程通常在方法完成后开始,恕我直言,这可能会产生这样的问题。

考虑到行

flightInstanceRepository.save(mappedFlightInstance);

完全没有必要并且怀疑存在此类问题,我会将其从您的代码中删除,这可能会解决您的问题。

© www.soinside.com 2019 - 2024. All rights reserved.