我在尝试在 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 字段,该字段使用基类 FlyWellModel 的 onCreate 方法中的 @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 上的我的问题
检查下面标记的以下代码行
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);
完全没有必要并且怀疑存在此类问题,我会将其从您的代码中删除,这可能会解决您的问题。