Reputation: 385
I have Below 3 models :
Model 1: Reservation
@Entity
public class Reservation {
public static final long NOT_FOUND = -1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
@OneToMany(mappedBy = "reservation", cascade = CascadeType.ALL, orphanRemoval = true)
public List<RoomReservation> roomReservations = new ArrayList<>();
}
Model 2: Room Reservation:
public class RoomReservation extends{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "RESERVATION_ID")
public Reservation reservation;
@OneToMany(mappedBy = "roomReservation", cascade = CascadeType.ALL, orphanRemoval = true)
public List<GuestDetails> guestDetails = new ArrayList<>();
}
Model 3 : Guest Details:
public class GuestDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public Long guestId;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ROOM_RESERVATION_ID")
public RoomReservation roomReservation;
public Boolean isPrimary;
@Transient
public Guest guest;
}
The Relationship between those three are as :
Reservation --One to Many on RESERVATION_ID--> Room Reservation --One to Many on ROOM_RESERVATION_ID--> Guest Details
I am getting the reservation object and trying to update guest details i get the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.model.GuestDetails.roomReservation -> com.model.RoomReservation
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1760)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:82)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
... 73 common frames omitted
I have changed cascadeType to ALL as suggested in common question still getting the same error.Please donot make it duplicate as i have tried all the solution realated to this kind of question already asked
Please Let me know what mistake i am doing. Thanks
Code to save Reservation Object by changing GuestDetails:
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
existingReservation.roomReservations.forEach(roomReservation -> {
RoomReservation updatedRoomReservation = reservation.roomReservations.stream().filter(newRoomReservation -> Objects.equals(roomReservation.id, newRoomReservation.savedReservationId)).findFirst().orElse(null);
if(updatedRoomReservation != null){
roomReservation.guestDetails = updatedRoomReservation.guestDetails;
}
});
reservationRepository.save(existingReservation);
Upvotes: 8
Views: 6880
Reputation: 38749
This can be caused by incorrect transaction semantics.
If the referenced instance was not fetched in the current transaction it counts as transient.
The easiest solution is to add @Transactional
to the method:
@Transactional
public void saveReservation(...) {
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
// ...
reservationRepository.save(existingReservation);
}
Upvotes: 1
Reputation: 3121
... save the transient instance before flushing :
com.model.GuestDetails.roomReservation -> com.model.RoomReservation
This exception states clearly that RoomReservation
contained in GuestDetails
, does not exist in the database (and most likely it's id
is null
).
In general, you can solve this exception either by :
Saving RoomReservation entity before saving GuestDetails
Or making cascade = CascadeType.ALL
(or at least {CascadeType.MERGE, CascadeType.PERSIST}
) for @ManyToOne
GuestDetail-->RoomReservation
But first, I have a couple of points to cover:
Do not use public fields in your class, this violates the encapsulation concept.
While you have a bidirectional association, you can set the other side of the association in your Setter
methods.
For your case, you should change RoomReservation
class :
public class RoomReservation{
//..... other lines of code
@OneToMany(mappedBy = "roomReservation", cascade = CascadeType.ALL, orphanRemoval = true)
private List<GuestDetails> guestDetails = new ArrayList<>();
public void setGuestDetails(List<GuestDetails> guestDetails) {
this.guestDetails.clear();
// Assuming that by passing null or empty arrays, means that you want to delete
// all GuestDetails from this RoomReservation entity
if (guestDetails == null || guestDetails.isEmpty()){
return;
}
guestDetails.forEach(g -> g.setRoomReservation(this));
this.guestDetails.addAll(guestDetails);
}
public List<GuestDetails> getGuestDetails() {
// Expose immutable collection to outside world
return Collections.unmodifiableList(guestDetails);
}
// You may add more methods to add/remove from [guestDetails] collection
}
Saving the Reservation:
Reservation existingReservation = reservationRepository.findOne(reservationId);
Reservation reservation = reservationParser.createFromJson(reservationNode);
existingReservation.roomReservations.forEach(roomReservation -> {
Optional<RoomReservation> updatedRoomReservation = reservation.roomReservations.stream().filter(newRoomReservation -> Objects.equals(roomReservation.id, newRoomReservation.savedReservationId)).findFirst();
if(updatedRoomReservation.isPresent()){
// roomReservation already exists in the database, so we don't need to save it or use `Cascade` property
roomReservation.setGuestDetails( updatedRoomReservation.get().getGuestDetails());
}
});
reservationRepository.save(existingReservation);
Hope it helps!
Upvotes: 1
Reputation: 129
If you are using JPA 2.0 then defaults fetch type for OneToMany is LAZY. If after your lambda, your updatedRoomReservation
is null (as you set in orElse) then existingReservation.roomReservation.guestDetails
will never be loaded and will be null.
Therefore when you save existingReservation
, you get the error.
Upvotes: 0
Reputation: 1027
You can save the reservation you get from the Json. JPA will update the rows with the same id's.
The error you get is because the guestDetails has still a reference to the updatedRoomReservation. If you don't want to save the whole reservation from the json you have to set the right RoomReservation.
e.g.:
if(updatedRoomReservation != null){
roomReservation.guestDetails = updatedRoomReservation.guestDetails;
guestDetails.forEach(guestDetail -> guestDetail.roomReservation = roomReservation);
}
Upvotes: 0
Reputation: 1441
GuestDetails
- add the needed CasadeType:
@ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name = "ROOM_RESERVATION_ID")
public RoomReservation roomReservation;
RoomReservation - add the nedded CascadeType:
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.AL)
@JoinColumn(name = "RESERVATION_ID")
public Reservation reservation;
And then you need to persist the data before/after using the for-each loop. Depends on you safe()
-Method.
Reservation reservation = reservationParser.createFromJson(reservationNode);
entityManager.persist(reservation);
And then safe it afterwards. Tell me your result. Maybe directly working without changing/adding the cascadetypes.
Upvotes: 0