I have the following method which is throwing a .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 have the following method which is throwing a .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;
}
Share
Improve this question
edited Feb 4 at 14:47
Goulash
asked Feb 4 at 13:38
GoulashGoulash
3,8487 gold badges32 silver badges49 bronze badges
5
|
1 Answer
Reset to default 1You are executing your method without an active transaction. There are 2 reasons for that:
- you explicitly disabled transactions with
@Transactional(propagation = NOT_SUPPORTED)
- even if this was not the case, transactions are not activated in a
@BeforeAll
method.TransactionalTestExecutionListener
activates them only in@Test
methods. See How does @Transactional work on test methods?
After saving a passanger, it is in a detached state.
You specified to cascade persist of passanger when you persist a Vehicle: @OneToOne(cascade = CascadeType.PERSIST)
Thus, you are persisting a detached entity - which causes the exception.
The difference between Passenger.property
is that there is no cascade in this case - when persisting the Passenger the persist operation is not cascaded to property
.
See Overview of JPA/Hibernate Cascade Types
@Test
. Does it pass? – Lesiak Commented Feb 4 at 14:16Propagation.NOT_SUPPORTED
. Thus, even in case of@Test
your method executes non-transactionally - passengers are indeed detached. – Lesiak Commented Feb 4 at 14:30Passenger
entity and especiallyproperty
to compare why it passes in that case. – Lesiak Commented Feb 4 at 14:38