javahibernate

"Detached entity passed to persist" immediately after saving


I have the following method which is throwing a org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.domain.Passenger on the vehicleRepository.saveAll call. I don't understand why this is happening since the Passenger is persisted just a few lines before, and therefore shouldn't it be an attached entity still? I'm especially confused because the Property save and passing that into Passenger works just fine.

On the Vehicle entity, the relationship with Passenger is:

@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "passenger_id", updatable = false)
@JsonManagedReference(value = "vehicle-passenger")
private Passenger passenger;

And here is the unit test set up method:

@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");

        // persist a property to use
        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");

        // add test properties to passengers
        passenger1.setProperty(property);
        passenger2.setProperty(property);
        passenger1 = passengerRepository.save(passenger1);
        passenger2 = passengerRepository.save(passenger2);

        // add test passengers to vehicles
        veh1.setPassenger(passenger1);
        veh2.setPassenger(passenger2);

        vehicleRepository.saveAll(List.of(
            veh1,
            veh2,
            veh3,
            veh4
        ));
    }

.....
@Data
@Entity
@DiscriminatorValue("VEHICLE")
@FieldNameConstants
@SuperBuilder
@RequiredArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Vehicle extends Conveyance {
   .. removed unrelated properties for brevity ...
}
@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;
}

@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;
}
@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;
}

Solution

  • I was able to get it working as desired by simply removing the passengerRepository.save calls and allowing the cascade persist to persist both the vehicle and passenger at the same time. Therefore, both were persisted at the same time and I am no longer passing a detached entity.