JPA 在 exceptions/rollback 情况下处理分离实体状态的正确方法

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

我有这个 class 我想出了三种方法来处理持久性异常(在别处处理)的分离实体状态:

@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) 可以抛出未经检查的异常。

目标是保持实体的状态与数据库的状态一致,即使在出现异常的情况下(防止实体在事务失败后变为 "dirty")。

问题:

  1. 方法三真的比方法二安全吗?
  2. flush[排除]和commit[包含]之间有没有抛出异常的情况?
  3. 是否有处理这个常见问题的标准方法?

谢谢


请注意,我已经能够回滚事务并关闭 EntityManager(PersistenceService#transact 会优雅地完成),但我需要解决 数据库状态和业务对象确实得到不同步。通常这不是问题。在我的例子中,这是 问题,因为异常通常由 BeanValidator 生成(那些在 JPA 端,而不是在 JSF 端,对于 computed 取决于 用户输入),我希望用户输入正确的值并重试, 不会丢失他之前输入的值

旁注:我使用的是 Hibernate 5.2.1


这是 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

就是这样!对第 1 点和第 2 点的很好解释

我希望你能详细说明第 3 点,并就实际用例给我一些建议。

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.

当您必须修改单个实体时,事务方法只需将分离的实体作为参数并return更新的实体,很简单。

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

    return managed;
}

但是当您需要在单个事务中修改多个时,该方法会变得非常痛苦:

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;
}

在这种情况下,我的痛苦:

  1. 我必须创建一个专用的事务方法(也许也是一个专用的 @EJB
  2. 该方法只会被调用一次(只有一个调用者)- 是一种 "one-shot" 不可重复使用的 public 方法。丑。
  3. 我必须创建虚拟对象 class LinkTicketResult
  4. 那个 class 只会被实例化一次,在那个方法中 - 是 "one-shot"
  5. 该方法可以有很多参数(或另一个虚拟参数 class LinkTicketParameters
  6. 在大多数情况下,JSF 控制器操作只会调用 EJB 方法,从 returned 容器中提取更新的实体并将它们重新分配给本地字段
  7. 我的代码会逐渐被 "one-shotters" 污染,对我来说太多了。

可能我没有看到眼前的大东西,如果你能指出正确的方向,我将不胜感激。

不确定这是否完全切中要点,但只有一种方法可以在异常后恢复:回滚并关闭 EM。来自 https://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/transactions.html#transactions-basics-issues

An exception thrown by the Entity Manager means you have to rollback your database transaction and close the EntityManager immediately (discussed later in more detail). If your EntityManager is bound to the application, you have to stop the application. Rolling back the database transaction doesn't put your business objects back into the state they were at the start of the transaction. This means the database state and the business objects do get out of sync. Usually this is not a problem, because exceptions are not recoverable and you have to start over your unit of work after rollback anyway.

-- 编辑-- 另见 http://piotrnowicki.com/2013/03/jpa-and-cmt-why-catching-persistence-exception-is-not-enough/

ps: downvote 不是我的。

  1. 方法三真的比方法二安全吗?

    是的。它不仅更安全(见第 2 点),而且在概念上更正确,因为只有在证明相关事务已成功时才更改依赖于事务的状态。

  2. flush[excluded]和commit[included]之间有没有抛异常的情况?

    是的。例如:

    1. LockMode.OPTIMISTIC:

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

      在单个事务中的每个刷新操作期间检查 optimistic 锁冲突既不高效也不实用。

    2. 延迟完整性约束(在数据库中提交时强制执行)。不经常使用,但是是这种情况的说明性示例。

    3. 后期维护重构。您或其他人稍后可能会在最后一次显式调用 flush 之后引入其他更改。

  3. 有没有标准的方法来处理这个常见问题?

    是的,我会说您的第三种方法是标准方法:使用完整且成功的交易的结果。

    但是,我绝对不会使用 AtomicReference 或类似的繁琐结构。 Java EE、Spring 和其他框架和应用程序容器支持通过注释声明事务方法:只需使用从事务方法返回的结果。