Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA OneToOne difference between cascade merge and persist

Tags:

I have the following question. I have 3 entities and I'm using OneToOne unidirectional:

Entity1

@Entity public class Entity1 implements Serializable{     @Id    @GeneratedValue(strategy= GenerationType.AUTO)    Long id;    String name;    String value; } 

Entity2

@Entity public class Entity2 implements Serializable {   @Id   @GeneratedValue(strategy=GenerationType.AUTO)   private Long id;    @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})   Entity1 entity1;   public Entity1 getEntity1() {       return entity1;   }    String name;   String value; } 

Entity3

@Entity public class Entity3 implements Serializable {   @Id   @GeneratedValue(strategy=GenerationType.AUTO)   Long id;    @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})   private Entity1 entity1;    public Entity1 getEntity1() {       return entity1;   }    public void setEntity1(Entity1 entity1) {       this.entity1 = entity1;   }    String name;   String value; } 

A small test:

public void testApp()    {     EntityManager em = TestHibernateUtil.getEntityManager();     em.getTransaction().begin();     Entity1 entity1 = new Entity1();     entity1.name = "Name1";     entity1.value = "Value1";      Entity2 entity2 = new Entity2();     entity2.name = "Name2";     entity2.value = "Value2";     entity2.setEntity1(entity1);     **em.merge(entity2);**// if change that to persist - I get one Entity1      Entity3 entity3 = new Entity3();     entity3.name = "Name3";     entity3.value = "Value3";     entity3.setEntity1(entity1);     **em.merge(entity3);** // if change that to persist - I get one Entity1     em.getTransaction().commit();  } 

So looking into the test above, if I use em.merge I do get 2 entities of Entity1 in persistence context after transaction commit, if I change it to em.persist then I get one entity of Entity1 in persistence context. Can anybody explain me why that happens or point to some documentation?

like image 400
ALincoln Avatar asked Aug 22 '13 06:08

ALincoln


People also ask

What is the difference between Merge and persist in JPA?

Persist should be called only on new entities, while merge is meant to reattach detached entities.

Why we use Cascade CascadeType all?

The meaning of CascadeType. ALL is that the persistence will propagate (cascade) all EntityManager operations ( PERSIST, REMOVE, REFRESH, MERGE, DETACH ) to the relating entities. It seems in your case to be a bad idea, as removing an Address would lead to removing the related User .

What is Cascade merge?

MERGE : cascade type merge means that related entities are merged when the owning entity is merged.

What does CascadeType persist mean?

CascadeType. PERSIST : It means that the save() and persist() operations in the hibernate cascade to the related entities. CascadeType. MERGE : It means that the related entities are joined when the owning entity is joined.


1 Answers

The behavior you are seeing is the result of two things:

  1. The semantics of the persist and merge operations (calling em.merge(x) will not make x a managed object, but calling em.persist(x) will)
  2. The fact that your entity's id is generated by the database

em.merge() rundown:

  1. em.merge(entity2); is invoked
    • The merge operation is cascaded to entity1
    • entity1 is copied into a new managed instance, but is not managed itself
  2. em.merge(entity3); is invoked
    • The merge operation is cascaded to entity1 again
    • Because entity1 is still unmanaged and doesn't have an identifier, it cannot be matched to the existing managed instance created by the previous merge. The result is that another new instance is created
  3. The transaction is committed
    • At this point, 3 instances of entity1 exist. Two managed instances created by the merge operations and the initial unmanaged instance
    • The two managed instances are saved in the database

Note that if your entity had an explicit id, then the second merge would not have created a new instance, but instead would have copied entity1 into the managed instance that already existed. Also, if you instead tried to merge the already managed instance, then the second merge operation would have been ignored.

em.persist() rundown:

  1. em.persist(entity2); is invoked
    • The persist operation is cascaded to entity1
    • entity1 is now a managed object
  2. em.persist(entity3); is invoked
    • The persist operation is cascaded to entity1 again
    • Since entity1 is already managed, the persist operation is ignored
  3. The transaction is committed
    • At this point, only 1 instance of entity1 exists and it is managed.
    • entity1 is saved in the database

This behavior is defined in the JPA 2.0 Specification section 3.2.7.1 Merging Detached Entity State:

The semantics of the merge operation applied to an entity X are as follows:

  • If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.
  • For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
  • [...]

and section 3.2.2 Persisting and Entity Instance:

The semantics of the persist operation, applied to an entity X are as follows:

  • If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation.
  • If X is a preexisting managed entity, it is ignored by the persist operation. [...]
  • [...]

See also: When does the JPA set a @GeneratedValue @Id)

like image 90
DannyMo Avatar answered Sep 19 '22 14:09

DannyMo