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

调用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;
}

问题原因

  1. 事务配置导致实体脱离持久化上下文:测试类标注了@Transactional(propagation = NOT_SUPPORTED),意味着setup方法不在事务中执行。passengerRepository.save执行后,实体仅被持久化到数据库,不会留在持久化上下文,执行完后Passenger就变成detached状态。
  2. 级联类型不匹配: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:15:12