utsav anand
utsav anand

Reputation: 385

object references an unsaved transient instance - save the transient instance before flushing : Spring Data JPA

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

Answers (5)

OrangeDog
OrangeDog

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

O.Badr
O.Badr

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

user8826113
user8826113

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

Tom
Tom

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

LenglBoy
LenglBoy

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

Related Questions