Entitymanager.merge() persisting instead of updating

Asked

Viewed 13,323 times

6

Update method:

public void update(T u) {
    EntityManagerFactory f = Persistence.createEntityManagerFactory("crud");
    EntityManager em = f.createEntityManager();
    em.getTransaction().begin();
    em.merge(u);
    em.getTransaction().commit();
    f.close();
}

I’ve been using that same method and it was working perfectly. Now instead of updating the state of the entity it persists a new record every time I try to update. I have tried update() and got the same result.

  • 1

    The entity has set PK?

  • Yes, I gave a syso before calling merge() and PK is != null

  • My suspicion is that when I give Ubmit in my edit form it calls setId() somehow. As I did not put an inputText for my attribute id it arrow to 0. I say this because after Ubmit the id goes to 0. I am forgetting something ?

  • As suspected, includes an inputHidden in the form with value of the id and gave normal update. This guy gave me the tip post

2 answers

22


Object vs. Entity

When working with JPA/Hibernate it is important to keep in mind that any object, even if it is an instance of a class annotated with @Entity or mapped to an XML, not automatically a JPA entity.

This means that no matter if the PK is properly filled, an object is not a entity if you’re not managed in a persistence context (persistent context).

JPA states

Let’s look at a simplified JPA state diagram (source):

diagrama de estados JPA

When we create an instance of the object it is in the state new (new). When we call the method persist our object becomes a managed entity (Managed), although the record is probably not entered in the database until the commit.

After the JPA context closes (commit of the transaction, close of EntityManager or method detach), our entity will enter the state Detached. Later we can bring it back to the JPA persistence context with the method merge.

If we exclude an entity with the method remove she will enter the state removed (re-moved) and until the end of the transaction a command DELETE will be sent to the database. If we want to persist the entity again, we can call the method persist.

How does the merge

Now that we understand the states of the entities, let us now see how the method works merge. Here’s a very interesting diagram (source):

algoritmo do merge

  • The first step of merge is to verify that the entity is in the state managed (Managed), because if so the entity itself is returned.
  • If the entity has been removed (re-moved) an error is thrown.
  • If the persistence context already has a version of this entity, it will be updated with the state of the object passed by parameter and then will be returned.
  • If there is no equivalent entity, it is verified that this is a new entity:
    • If it is, a new instance is created (equivalent to persist).
    • If nay is, the entity is loaded in the persistence context from the database.

A common error when using merge

Note that the main purpose of the method merge is to return a JPA entity managed by the persistence context.

If the object passed by parameter is not a JPA entity, the same nay will be included in the persistence context, but a copy will be made and returned by the method merge.

As described in this famous OS issue, a common error is to expect the object passed to the method to become an entity.

Incorrect

MyEntity e = new MyEntity();
em.merge(e);
e.setAtributo(novoValor); 

In the above example, changing the value of the original object does not affect the state of the JPA entity created by merge.

Correct

MyEntity e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setAtributo(novoValor); 

Already in this example, the value will be updated in the database because the managed entity returned by merge was modified.

Back to the question

With all the theory in mind we can conclude that the method merge will include a new entity and, consequently, a new record in the database, when the object passed to the method is considered a new entity.

In what scenarios is this possible? I can’t predict them all, but here are the most common:

  • PK is a sequential value (@GeneratedValue) and is not properly filled in, thus generating a new entry in the database.
  • There’s a version column (@Version) which is not properly populated in the database (see this issue).
  • There is a relationship with another entity and, for some reason, JPA is generating a query with INNER JOIN between the two tables and does not find results due to the absence of records in the other table, reaching the conclusion that the main entity does not exist. This article deals with the subject in relation to an Eclipselink annotation, but this can occur in Hibernate in some ways, for example via inheritance.

An alternative

Another way to use the merge is to recover the managed entity and change it as needed.

Example:

MyEntity e = //entidade criada em algum outro lugar (JSF, Spring, etc.)
MyEntity e2 = em.find(MyEntity.class, idEntidade);
e.setAtributo(e.getAtributo); 

While updating the attributes individually is more "laborious" and makes the code get bigger, this prevents the "magic" of frameworks or even the improper manipulation of attributes end up generating unexpected effects in the database.

0

Removing the old entity and persisting the new one also works.

public void update(T u) {
    EntityManagerFactory f = Persistence.createEntityManagerFactory("crud");
    EntityManager em = f.createEntityManager();
    em.getTransaction().begin();
    em.remove(u.getID())
    em.persist(u);
    em.getTransaction().commit();
    f.close();
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.