Spring data JPA and hibernate detached entity passed to persist on ManyToMany relationship

Solution 1:

I had the same problem and solved it by removing the cascade = CascadeType.PERSIST.

In your case you use CascadeType.ALL, which is equivalent to also using the PERSIST, according to the documentation:

Defines the set of cascadable operations that are propagated to the associated entity. The value cascade=ALL is equivalent to cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}.

It means when you try to save the reservation on reservationDAO.save(reservation) it will also try to persist the associated Product object. But this object is not attached to this session. So the error occur.

Solution 2:

The exception comes as hibernate trying to persist associated products when you save reservation. Persisting the products is only success if they have no id because id of Product is annotated

@GeneratedValue(strategy=GenerationType.AUTO)

But you got products from repository and ids are not null.

There 2 options to resolve your issue:

  1. remove (cascade = CascadeType.ALL) on products of Reservation
  2. or remove @GeneratedValue(strategy=GenerationType.AUTO) on id of Product

Solution 3:

You need to ensure both side of the relationship are properly maintained in your code.

Update Reservation as below and then add the corresponding methods to Product.

@Entity
@Table(name = "RESERVATION")
public class Reservation {

    private int reservationId;

    private Set<Product> products = new HashSet<Product>(0);

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getReservationId() {
        return reservationId;
    }

    public void setReservationId(int reservationId) {
        this.reservationId = reservationId;
    }

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "product_reservation", joinColumns = { @JoinColumn(name = "reservationId", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "productId", 
            nullable = false, updatable = false) })
    public Set<Product> getProducts() {
        
        //force clients through our add and remove methods
        return Collections.unmodifiableSet(products);
    }
    
    public void addProduct(Product product){
    
        //avoid circular calls : assumes equals and hashcode implemented
        if(! products.contains(product){
            products.add(product);
            
            //add method to Product : sets 'other side' of association
            product.addReservation(this);
        }
    }
    
    public void removeProduct(Product product){
        
        //avoid circular calls: assumes equals and hashcode implemented: 
        if(product.contains(product){
            products.remove(product);
            
            //add method to Product: set 'other side' of association: 
            product.removeReservation(this);
        }
    }
}

And in Products:

public void addReservation(Reservation reservation){

    //assumes equals and hashcode implemented: avoid circular calls
    if(! reservations.contains(reservation){
        reservations.add(reservation);

        //add method to Product : sets 'other side' of association
        reservation.addProduct(this);
    }
}

public void removeReservation(Reservation reservation){

    //assumes equals and hashcode implemented: avoid circular calls
    if(reservations.contains(reservation){
        reservations.remove(reservation);

        //add method to Product : sets 'other side' of association
        reservation.reomveProduct(this);
    }
}

Now you should be able to call save on either Product or Reservation and everything should work as expected, so you would by happy.

Solution 4:

Remove @ CascadeType.ALL in the @ManytoMany relation, this worked for me.

Solution 5:

entityManager.merge() is a good option. It will merge the detached objects in the session. No need to change any cascadeType.