调用vehicleRepository.saveAll抛出Detached entity异常的原因排查
问题:执行
vehicleRepository.saveAll时抛出detached entity异常 执行vehicleRepository.saveAll调用时,抛出org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.domain.Passenger异常。明明Passenger实体刚在几行代码前完成持久化,理论上应该处于附着(attached)状态,而且Property的保存及关联到Passenger的操作完全正常,对此感到困惑。
关联关系定义
Vehicle(继承自Conveyance)与Passenger的关联:
@OneToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "passenger_id", updatable = false) @JsonManagedReference(value = "conveyance-passenger") private Passenger passenger;
单元测试初始化代码
@DataJpaTest @Transactional(propagation = NOT_SUPPORTED) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class Tests { @BeforeAll void setup() { Vehicle veh1 = build.fromFile(Vehicle.class, "json/Vehicle"); Vehicle veh2 = build.fromFile(Vehicle.class, "json/Vehicle"); Vehicle veh3 = build.fromFile(Vehicle.class, "json/Vehicle"); Vehicle veh4 = build.fromFile(Vehicle.class, "json/Vehicle"); // 持久化一个Property Property property = build.fromFile(Property.class, "json/Property"); property.setPropertyId(1L); propertyRepository.save(property); Passenger passenger1 = build.fromFile(Passenger.class, "json/Passenger"); Passenger passenger2 = build.fromFile(Passenger.class, "json/Passenger"); // 给乘客关联Property passenger1.setProperty(property); passenger2.setProperty(property); passenger1 = passengerRepository.save(passenger1); passenger2 = passengerRepository.save(passenger2); // 给车辆关联乘客 veh1.setPassenger(passenger1); veh2.setPassenger(passenger2); vehicleRepository.saveAll(List.of( veh1, veh2, veh3, veh4 )); } .....
相关实体类代码
Vehicle类
@Data @Entity @DiscriminatorValue("VEHICLE") @FieldNameConstants @SuperBuilder @RequiredArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) public class Vehicle extends Conveyance { .. 省略无关属性 ... }
Conveyance抽象类
@Data @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "CONVEYANCE_TYPE", discriminatorType = DiscriminatorType.STRING) @FieldNameConstants @SuperBuilder @RequiredArgsConstructor @AllArgsConstructor public abstract class Conveyance { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "conveyance_id_seq") @SequenceGenerator(name = "conveyance_id_seq", allocationSize = 1) @Setter(AccessLevel.NONE) private Long id; @OneToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "passenger_id", updatable = false) @JsonManagedReference(value = "conveyance-passenger") private Passenger passenger; }
Passenger类
@Data @Entity @FieldNameConstants @Builder @NoArgsConstructor @AllArgsConstructor public class Passenger { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "passenger_id_seq") @SequenceGenerator(name = "passenger_id_seq", allocationSize = 1) @Setter(AccessLevel.NONE) private Long id; @Column( unique = true, updatable = false, columnDefinition = "bigint GENERATED ALWAYS AS IDENTITY (START WITH 10)" ) @Generated(event = EventType.INSERT) private Long passengerId; @ManyToOne @JoinColumn(name = "property_id") @JsonBackReference private Property property; @OneToOne(cascade = CascadeType.PERSIST, mappedBy = Conveyance.Fields.passenger) @EqualsAndHashCode.Exclude @JsonBackReference(value = "conveyance-passenger") private Conveyance conveyance; }
Property类
@Data @Entity @Builder @NoArgsConstructor @AllArgsConstructor @FieldNameConstants public class Property { public Property(Long id) { this.id = id; } @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "property_id_seq") @SequenceGenerator(name = "property_id_seq", allocationSize = 1) @Setter(AccessLevel.NONE) private Long id; @Column(unique = true) private Long propertyId; }
问题原因
- 事务配置导致实体脱离持久化上下文:测试类标注了
@Transactional(propagation = NOT_SUPPORTED),意味着setup方法不在事务中执行。passengerRepository.save执行后,实体仅被持久化到数据库,不会留在持久化上下文,执行完后Passenger就变成detached状态。 - 级联类型不匹配:
Conveyance中Passenger关联用了CascadeType.PERSIST,保存Vehicle时JPA会尝试对关联的Passenger执行persist操作,但persist仅支持新实体或附着状态实体,detached状态的实体触发异常。
解决方案
方案一:调整事务传播属性
将测试类的@Transactional(propagation = NOT_SUPPORTED)改为默认的@Transactional(或直接去掉,因为@DataJpaTest默认给测试方法加事务),这样整个setup在同一事务中执行,Passenger始终处于附着状态,保存Vehicle时不会触发异常。
方案二:修改级联类型
将Conveyance中Passenger关联的CascadeType.PERSIST改为CascadeType.MERGE,或添加MERGE类型:
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(name = "passenger_id", updatable = false) @JsonManagedReference(value = "conveyance-passenger") private Passenger passenger;
保存Vehicle时,JPA会对detached状态的Passenger执行merge操作,而非persist,避免异常。
方案三:保存Vehicle前重新加载Passenger
关联Passenger到Vehicle前,通过passengerRepository.findById重新加载实体,确保其处于附着状态:
passenger1 = passengerRepository.findById(passenger1.getId()).orElseThrow(); passenger2 = passengerRepository.findById(passenger2.getId()).orElseThrow(); veh1.setPassenger(passenger1); veh2.setPassenger(passenger2);
内容的提问来源于stack exchange,提问作者Goulash
相关产品推荐
相关产品推荐

