3
votes

I have a method that receives a JPA Entityand its related EntityManager as parameters. The Entity instance is not created inside the class, and it might very well be shared by other classes (like GUIs and such).

The method starts a transaction, carries out some changes on the entity, and finally commits the transaction.

In case the commit fails, EntityTransaction.rollback() is called: in accordance with JPA specifications, the entity is then detached from the manager.

In case of failure the application needs to discard the pending changes, restore the original values inside the entity e and re-attach it to the EntityManager, so that the various scattered references to the e object would remain valid. The problem raises here: what I understood is that this is not a straightforward operation using the EntityManager's APIs:

  • calling EntityManager.refresh(e) is not possible since e is detached.
  • doing e = EntityManager.merge(e) would create a new instance for e: all the other references to the original e in the program at runtime would not be updated to the new instance. This is the main issue.
  • moreover (actually not quite sure about this), EntityManager.merge(e) would update the new managed instance's values with the values currently held by e (i.e., the values that probably caused the commit to fail). Instead, what I need is to reset them.

Sample code:

public void method(EntityManager em, Entity e) {
    EntityTransaction et = em.getTransaction();
    et.begin();
    ...
    // apply some modifications to the entity's fields
    ...
    try {
        et.commit();
    } catch (Exception e) {
        et.rollback();

        // now that 'e' is detached from the EntityManager, how can I:
        // - refresh 'e', discarding all pending changes
        // - without instantiating a copy (i.e. without using merge())
        // - reattach it
    }
}

What is the best approach in this case?

1
Does the given Entity e have any changes, which are not commited to the database yet?Uooo
What further operations are required on the entity such that you need to follow references to it? One basic approach to such a situation is to create a dry-run to ensure that the rollback won't occur. Another basic approach is to create a sub-transaction, essentially cloning the entity network for commitment in anticipation of a roll-back.Bob Dalgleish
@Uooo: No, the method assumes e's state to be synchronized with the database row when passed as argument. @BobDalgleish: The e object itself maybe referenced by other (unknown) classes which may separately modify other fields of it in distinct transactions (not concurrent though). I was hoping some non complex reset & re-attach strategies existed. Your approach is useful too.Max The Pax

1 Answers

0
votes

A possible solution would be like:

public class YourClass {
    private EntityManager em = ...; // local entity manager

    public void method(Entity e) { // only entity given here
        Entity localEntity = em.find(Entity.class, e.getId());
        EntityTransaction et = em.getTransaction();
        et.begin();
        ...
        // apply some modifications to the local entity's fields
        applyChanges(localEntity);
        ...
        try {
            et.commit();
            // Changes were successfully commited to the database. Also apply
            // the changes on the original entity, so they are visible in GUI.
            applyChanges(e);
        } catch (Exception ex) {
            et.rollback();
            // original entity e remains unchanged
        }
    }

    private void applyChanges(Entity e) { 
        ... 
    }
}