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