Search code examples
javahibernatejpamany-to-manyjpa-2.1

detached entity when using ManyToMany in jpa 2.1


I'm creating an app and I have a ManyToMany relation between Recipe and Ingredients, but when I'm trying to insert new row in RecipeIngredient using JPA 2.1 i got :

Caused by: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist.

This is my entities : `

@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@CrossCheck
public class Ingredient implements ValidEntity {

static final String PREFIX = "com.architech.sales.ingredient.entity.";
public static final String FIND_ALL = PREFIX + "FIND_ALL";
public static final String FIND_BY_ID = PREFIX + "FIND_BY_ID";

@Id
@GeneratedValue
private long id;

@NotNull
private String label;
private String description;

// added just to reply to @Maciej-Kowalski
@OneToMany(targetEntity = RecetteIngredient.class, mappedBy = "ingredient")
private List<RecetteIngredient> recetteIngredients;

@Version
private long version;
// Getter / Setter

}

@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@CrossCheck
public class Recette implements ValidEntity{

static final String PREFIX = "sales.recette.entity.Recette.";
public static final String FIND_ALL = PREFIX + "FIND_ALL";
public static final String FIND_BY_ID = PREFIX + "FIND_BY_ID";

@Id
@GeneratedValue
private long id;

@Size(min = 1, max = 256)
private String label;
private String description;

@OneToMany(targetEntity = RecetteIngredient.class, mappedBy = "recette")
private List<RecetteIngredient> recetteIngredients;

@Version
private long version;

// Getter/Setter
}
`

`

@Entity
@IdClass(RecetteIngredientPK.class)
public class RecetteIngredient implements Serializable{

@Id
private long id;
private double qte;

@Id
@ManyToOne(targetEntity = Ingredient.class)
private Ingredient ingredient;

@Id
@ManyToOne(targetEntity = Recette.class)
private Recette recette;

// Getter/Setter
}
`

and my persistence unit

<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="prod" transaction-type="JTA"> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <!--<property name="hibernate.enable_lazy_load_no_trans" value="true" />--> </properties> </persistence-unit> </persistence>

This is the stacktrace :

`

    Caused by: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.architech.sales.ingredient.entity.Ingredient
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1602)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1608)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1171)
    at org.jboss.as.jpa.container.AbstractEntityManager.merge(AbstractEntityManager.java:565)
    at com.architech.sales.recette_ingredient.boundary.RecetteIngredientManager.save(RecetteIngredientManager.java:47)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
`

and this is how i'm trying to persiste RecetteIngredient :

`
@Stateless
@Interceptors(BoundaryLogger.class)
public class RecetteIngredientManager {

@PersistenceContext
EntityManager em;

public RecetteIngredient findById(long id) {
    return this.em.find(RecetteIngredient.class, id);
}

public void delete(long id) {
    try {
        RecetteIngredient reference = this.em.getReference(RecetteIngredient.class, id);
        this.em.remove(reference);
    } catch (EntityNotFoundException e) {
        //we want to remove it...
    }
}

public RecetteIngredient save(RecetteIngredient recetteIngredient) {
    return this.em.merge(recetteIngredient);
}

}

`


Solution

  • In your linking class add merge cascade option on the @ManyToOne mappings:

    @Entity
    @IdClass(RecetteIngredientPK.class)
    public class RecetteIngredient implements Serializable{
    
    @Id
    private long id;
    private double qte;
    
    @Id
    @ManyToOne(targetEntity = Ingredient.class, cascade = CascadeType.MERGE)
    private Ingredient ingredient;
    
    @Id
    @ManyToOne(targetEntity = Recette.class, cascade = CascadeType.MERGE)
    private Recette recette;
    

    This will advise the PersistenceContext to merge also the Ingredient and Recette entities if they happen to be detached at the time.

    If you are not allowed to change the annotations then you would have to persist / merge Ingredient and Recette entities manually before persising the linking table:

    public RecetteIngredient save(RecetteIngredient recette) {
      this.em.merge(recette.getIngredient());
      this.em.merge(recette.getRecette());
    
      return this.em.merge(recette);
    }
    

    Of course this is a workaround and the first approach is definately more elegant and advisable.