JPA事务回滚重试和恢复:使用自动递增的@Version合并实体

时间:2011-06-26 06:45:44

标签: java hibernate jpa merge rollback

我想在交易失败后恢复。

现在,当然,在任何回滚之后,所有实体都将分离,实体管理器将关闭。但是,UI仍然保留分离的实体。显然我们不能丢弃用户的更改,所以我们想让他们重试(修复突出显示的验证错误,然后再次点击按钮)。

关注Java Persistence WikiBook

  

错误处理的一种方法是在提交失败后调用每个托管对象的合并到新的EntityManager中,然后尝试提交新的EntityManager。一个问题可能是需要重置已分配的任何ID,或者已分配或增加的乐观锁定版本。此外,如果原始EntityManager是EXTENDED,则任何正在使用的对象仍将分离,并且需要重置。

这个选项起初看起来很简单,直到我们不可避免地遇到那些预期的问题。某些服务可能由于各种原因触发刷新,这会增加DB(已回滚)和Java实体(不是)的@Version个字段。下一次“保存”调用merge,会引发意外的OptimisticLockException

是否有可靠的方法在Java实体bean中“回滚”版本字段?

好的,接缝很难。我们有自己的@Versions级联实体,因此手动执行它似乎很脆弱。 (我们怎样才能可靠地知道原始(持久)版本?无法查询,因为其他一些用户可能会同时成功更新实体;查询当前版本可能会破坏oplock!)

  

另一个更复杂的错误处理方法是始终使用非事务性EntityManager。在提交时,创建一个新的EntityManager,将非事务对象合并到其中,并提交新的EntityManager。如果提交失败,则只有新EntityManager的状态可能不一致,原始EntityManager将不受影响。这可以允许纠正问题,并且EntityManager重新合并到另一个新的EntityManager中。如果提交成功,则任何提交更改都可以合并回原始的EntityManager,然后可以继续正常使用。这个解决方案需要相当大的开销,所以只有在真正需要错误处理的情况下才能使用,而JPA提供者不提供任何替代方案。

这似乎合乎逻辑。 有没有人有任何使用两个EntityManage(特别是Spring)实现这种恢复的经验?在尝试它之前我应该​​注意哪些陷阱?似乎每个服务和DAO现在都必须意识到两个实体管理器(使用Spring,今天它们几乎是持久层不可知的)。 DAO'发现'操作使用一个EM; 'update'使用另一个。或者有单独的“读”和“写”DAO。哎哟。

我考虑过的其他选项包括:

  • 在UI中使用DTO,因此自动递增不会影响任何内容。难看。
  • 将调用移至merge至任何撰写操作的结尾。只有在所有验证和状态更新成功后才会附加实体。似乎奇怪的是“保存”服务将不再merge(仅验证)。实际上,UI将负责调用DAO!这听起来有点不寻常吗?

建议?谢谢: - )

更新 我的架构包括:

  • 由UI(JSF)
  • 更新的分离实体
  • 实体ID不是自动生成的(预先指定的UUID和/或业务键)
  • 实体具有自动递增的@Version字段以进行oplocking
  • “保存”服务验证,调用em.merge(JPA over Hibernate)
  • “流程”服务验证,应用业务逻辑,更新实体状态
  • 服务可以组成。一个UI按钮可能会
    1. (关于UI控制器的Spring @Transactional建议:开始
    2. 保存
    3. 流程1
    4. 流程2
    5. @Transactional commit
  • 任何服务都可能抛出validation exception(JSR 303),它会按预期回滚(消息显示在UI中)

4 个答案:

答案 0 :(得分:1)

只能支持JB Nizet的评论:merge只复制分离实体的状态。

答案 1 :(得分:1)

你问两个不同的问题然后一般征求意见。我会接受这个建议(会在评论中提出这个问题,但是我不会让我发表评论......)

在UI中使用DTO(或任何仅UI的数据结构)并不难看 - 它在架构上是合理的。我同意JB Nizet的观点,即非事务EM方法实质上将您的实体转变为DTO。如果你必须在你的UI中使用实体(我意识到Spring鼓励这样做),这就是你要走的路。

根据我的经验,人们想要将实体用作UI临时存储的主要原因是可以访问其中的复杂验证逻辑。您可能还需要考虑重构,以便在不调用完整域模型的情况下访问该逻辑。

答案 2 :(得分:0)

  

显然,我们不能抛弃用户的更改

如果用户正在尝试更新,而其他人已经对其进行了更改,则用户可以更好地刷新副本并再次进行更改吗?

答案 3 :(得分:0)

由于EntityManager.merge()创建了传递的实体的副本,解决方案看起来非常简单:在UI(JSF bean)中维护对传递的实体的引用,直到事务成功完成,并且然后才用复制的实体更新引用:

@ManagedBean
@SomeScoped
public class EntityBean
{
    @EJB
    private PersistenceService service;

    private Object entity;

    public void update()
    {
        try
        {
            // service.update() is a CMT method
            Object updatedEntity = service.update(entity);

            // if we are here transaction has completed successfully, so update the reference 
            entity = updatedEntity;
        }
        catch(Exception e)
        {
            // if catched, entity has not been modified (just like transaction never happened)
            // now handle exception (log, add a FacesMessage, rethrow, ...)
        }
    }

    ...
}

您可以重新呈现用户界面,UIComponent将使用用户填写的同一实体,然后再提交上一个请求。

这样您就不必担心重置@Id@Version,也不必担心任何其他更改的字段或属性,包括级联关联,也不必使用多个EntityManager,也没有使用扩展的持久化上下文。

然而,这个简单的例子有一些限制:

  1. 永远不要在“已通过”实体上使用EntityManager.persist,因为持久性不会执行复制。您必须始终使用EntityManager.merge,但有时候这是不可能的。

  2. 传递的实体必须处于“TRANSIENT”或“DETACHED”状态,EntityManager.merge才能创建副本(否则返回传递的实体,不执行复制)。这意味着严格禁止使用扩展持久化上下文或OpenSessionInViewFilter和派生词。

  3. 在实施具有子事务的服务组合时应特别小心:当子事务成功完成但父回滚时,您可能会以搞砸实体结束。

  4. 请注意,我不知道Spring是如何工作的,因此所有这些都可能不适用。