26

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?

giannis christofakis
  • 8,201
  • 4
  • 54
  • 65
ALincoln
  • 431
  • 1
  • 4
  • 12

2 Answers2

31

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)

Ole
  • 41,793
  • 59
  • 191
  • 359
DannyMo
  • 11,344
  • 4
  • 31
  • 37
24

I would not dream to challenge the uber answer of DannyMo, but I wanted to make an addition:

Persist and merge are designed as a way to keep one managed instance of certain object.

If you use persist it means the object does not exist yet, so making it a unique managed instance doesn't hurt.

When you use merge, you take into consideration that a managed instance of the object may already exist. You don't wanna replace that unique managed instance, because some other object might reference it, believing it is the managed object.

If you want to make operations on the object after the merge, the correct merge would look like this:

managedObject = em.merge(object); // or just object = em.merge(object) //You cannot do it with persist since it returns null

If you tried to check if managedObject and object points to the same object instance checking if(managedObject == object) you will get false (true is possible when you use merge on already managed object and the operation is ignored).

If you use merge on outdated version of object, which you passed as argument to the previous merge, jpa doesn't know how to find the right object since it has yet no id. It is assumed that it is a new object and new managed instance will be created.

I'm quite a rookie. Please correct me if I am wrong anywhere.

Ole
  • 41,793
  • 59
  • 191
  • 359
Atom
  • 616
  • 1
  • 5
  • 14