Search code examples
javahibernatejpaejbjpa-2.1

JPA correct way to handle detached entity state in case of exceptions/rollback


I have this class and I tought three ways to handle detached entity state in case of persistence exceptions (which are handled elsewhere):

@ManagedBean
@ViewScoped
public class EntityBean implements Serializable
{
    @EJB
    private PersistenceService service;

    private Document entity;

    public void update()
    {
        // HANDLING 1. ignore errors
        service.transact(em -> 
        {
            entity = em.merge(entity);

            // some other code that modifies [entity] properties:
            // entity.setCode(...);
            // entity.setResposible(...);
            // entity.setSecurityLevel(...);

        });  // an exception may be thrown on method return (rollback), 
        // but [entity] has already been reassigned with a "dirty" one.

        //------------------------------------------------------------------

        // HANDLING 2. ensure entity is untouched before flush is ok
        service.transact(em -> 
        {
            Document managed = em.merge(entity);

            // some other code that modifies [managed] properties:
            // managed.setCode(...);
            // managed.setResposible(...);
            // managed.setSecurityLevel(...);

            em.flush(); // an exception may be thrown here (rollback)
            // forcing method exit without [entity] being reassigned.

            entity = managed;

        }); // an exception may be thrown on method return (rollback), 
        // but [entity] has already been reassigned with a "dirty" one.

        //------------------------------------------------------------------

        // HANDLING 3. ensure entity is untouched before whole transaction is ok
        AtomicReference<Document> reference = new AtomicReference<>();
        service.transact(em -> 
        {
            Document managed = em.merge(entity);

            // some other code that modifies [managed] properties:
            // managed.setCode(...);
            // managed.setResposible(...);
            // managed.setSecurityLevel(...);

            reference.set(managed);

        }); // an exception may be thrown on method return (rollback), 
        // and [entity] is safe, it's not been reassigned yet.

        entity = reference.get();
    }

    ...
}

PersistenceService#transact(Consumer<EntityManager> consumer) can throw unchecked exceptions.

The goal is to maintain the state of the entity aligned with the state of the database, even in case of exceptions (prevent entity to become "dirty" after transaction fail).

  • Method 1. is obviously naive and doesn't guarantee coherence.

  • Method 2. asserts that nothing can go wrong after flushing.

  • Method 3. prevents the new entity assigment if there's an exception in the whole transaction

Questions:

  1. Is method 3. really safer than method 2.?
  2. Are there cases where an exception is thrown between flush [excluded] and commit [included]?
  3. Is there a standard way to handle this common problem?

Thank you


Note that I'm already able to rollback the transaction and close the EntityManager (PersistenceService#transact will do it gracefully), but I need to solve database state and the business objects do get out of sync. Usually this is not a problem. In my case this is the problem, because exceptions are usually generated by BeanValidator (those on JPA side, not on JSF side, for computed values that depends on user inputs) and I want the user to input correct values and try again, without losing the values he entered before.

Side note: I'm using Hibernate 5.2.1


this is the PersistenceService (CMT)

@Stateless
@Local
public class PersistenceService implements Serializable
{
    @PersistenceContext
    private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void transact(Consumer<EntityManager> consumer)
    {
        consumer.accept(em);
    }
}

@DraganBozanovic

That's it! Great explanation for point 1. and 2.

I'd just love you to elaborate a little more on point 3. and give me some advice on real-world use case.

However, I would definitely not use AtomicReference or similar cumbersome constructs. Java EE, Spring and other frameworks and application containers support declaring transactional methods via annotations: Simply use the result returned from a transactional method.

When you have to modify a single entity, the transactional method would just take the detached entity as parameter and return the updated entity, easy.

public Document updateDocument(Document doc)
{
    Document managed = em.merge(doc);
    // managed.setXxx(...);
    // managed.setYyy(...);

    return managed;
}

But when you need to modify more than one in a single transaction, the method can become a real pain:

public LinkTicketResult linkTicket(Node node, Ticket ticket)
{
    LinkTicketResult result = new LinkTicketResult();

    Node managedNode = em.merge(node);
    result.setNode(managedNode);

    // modify managedNode

    Ticket managedTicket = em.merge(ticket);
    result.setTicket(managedTicket);

    // modify managedTicket

    Remark managedRemark = createRemark(...);
    result.setRemark(managedemark);

    return result;
}

In this case, my pain:

  1. I have to create a dedicated transactional method (maybe a dedicated @EJB too)
  2. That method will be called only once (will have just one caller) - is a "one-shot" non-reusable public method. Ugly.
  3. I have to create the dummy class LinkTicketResult
  4. That class will be instantiated only once, in that method - is "one-shot"
  5. The method could have many parameters (or another dummy class LinkTicketParameters)
  6. JSF controller actions, in most cases, will just call a EJB method, extract updated entities from returned container and reassign them to local fields
  7. My code will be steadily polluted with "one-shotters", too many for my taste.

Probably I'm not seeing something big that's just in front of me, I'll be very grateful if you can point me in the right direction.


Solution

    1. Is method 3. really safer than method 2.?

      Yes. Not only is it safer (see point 2), but it is conceptually more correct, as you change transaction-dependent state only when you proved that the related transaction has succeeded.

    2. Are there cases where an exception is thrown between flush [excluded] and commit [included]?

      Yes. For example:

      1. LockMode.OPTIMISTIC:

        Optimistically assume that transaction will not experience contention for entities. The entity version will be verified near the transaction end.

        It would be neither performant nor practically useful to check optimistick lock violation during each flush operation within a single transaction.

      2. Deferred integrity constraints (enforced at commit time in db). Not used often, but are an illustrative example for this case.

      3. Later maintenance and refactoring. You or somebody else may later introduce additional changes after the last explicit call to flush.

    3. Is there a standard way to handle this common problem?

      Yes, I would say that your third approach is the standard one: Use the results of a complete and successful transaction.

      However, I would definitely not use AtomicReference or similar cumbersome constructs. Java EE, Spring and other frameworks and application containers support declaring transactional methods via annotations: Simply use the result returned from a transactional method.