在异常/回滚的情况下,JPA正确处理分离的实体状态的方法

时间:2016-07-05 08:16:48

标签: java hibernate jpa ejb jpa-2.1

我有这个类,在持久性异常(在其他地方处理)的情况下,我采用了三种方法来处理分离的实体状态:

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

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

  • 方法1.显然很幼稚,并不能保证连贯性。

  • 方法2.断言冲洗后没有任何问题。

  • 方法3.如果整个交易中存在例外,则阻止新实体分配

的问题:

  1. 方法3.是否比方法2更安全。?
  2. 是否存在flush [已排除]和commit [包含]之间发生异常的情况?
  3. 有没有一种标准方法可以解决这个常见问题?
  4. 谢谢

    请注意,我已经能够回滚事务并关闭EntityManager(PersistenceService#transact将优雅地执行它),但我需要解决数据库状态,并且业务对象确实不同步。通常这不是问题。在我的情况下,这是 问题,因为异常通常由BeanValidator生成(JPA方面,而不是JSF方面,计算取决于用户输入)我希望用户输入正确的值并重试,而不会丢失他之前输入的值

    旁注:我正在使用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点详细说明一点,并就真实用例提供一些建议。

      

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

    当您必须修改单个实体时,事务方法只会将分离的实体作为参数并返回更新的实体,这很容易。

    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. 该方法只会调用一次(只有一个调用者) - 是一次性“不可重复使用”的公共方法。难看。
    3. 我必须创建虚拟课程LinkTicketResult
    4. 该类只会被实例化一次,在那种方法中 - 是“一次性”
    5. 该方法可以有许多参数(或另一个虚拟类LinkTicketParameters
    6. 在大多数情况下,JSF控制器操作只调用EJB方法,从返回的容器中提取更新的实体并将它们重新分配给本地字段
    7. 我的代码会受到“一次性投手”的严重污染,对我来说太多了。
    8. 可能我没有看到一些在我面前的大事,如果你能指出我正确的方向,我将非常感激。

2 个答案:

答案 0 :(得分:3)

  1. 方法3.是否比方法2更安全。?

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

  2. 是否存在在flush [excluded]和commit [included]之间抛出异常的情况?

    是。例如:

    1. LockMode.OPTIMISTIC

        

      乐观地假设交易不会遭遇争用   对于实体。 将在交易附近验证实体版本   端。

      在单个事务中的每次刷新操作期间检查optimistick锁定违规将既不高效也不实用。

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

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

  3. 有没有一种标准方法可以解决这个常见问题?

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

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

答案 1 :(得分:0)

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

  

实体管理器抛出的异常意味着您必须回滚   您的数据库事务并立即关闭EntityManager   (稍后将详细讨论)。如果您的EntityManager绑定   在应用程序中,您必须停止该应用程序。回滚   数据库事务并没有将您的业务对象重新放入   说他们在交易的开始。这意味着   数据库状态和业务对象确实不同步。平时   这不是问题,因为例外是不可恢复的   无论如何,必须在回滚后重新开始你的工作单元。

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

ps:downvote不是我的。