JPA:OptimisticLockException和Cascading

时间:2013-04-18 12:50:53

标签: jpa optimistic-locking

在我目前的项目中,我使用Spring Data JPA和Hibernate,但认为这是一个更普遍的问题,也应该涵盖“普通”JPA。

我不确定在使用OptimisticLockException时我应该如何处理@Version

由于我的应用程序如何运作,某些关系有CascadeType.PERSISTCascadeType.REFRESH,其他关系也有CascadeType.MERGE

  1. 在哪里处理OptimisticLockException
  2. 据我所知,在服务层上处理这个问题对CascadeType.MERGE不起作用,因为那时违规实体可能是需要由另一个服务处理的实体(我每个实体类都有一个服务) )。

    问题是我正在创建一个框架,因此没有服务上面的层,所以我可以将其“委托”给我的框架用户,但这看起来“弱而且懒惰”。

    1. 确定违规实体和已更改的字段
    2. 如果发生OptimisticLockException,如何获取导致该问题的实体以及更改了哪些字段?

      是的,我可以调用getEntity()但是如何将其转换为正确的类型,尤其是在使用CascadeType.MERGE的情况下?实体可以是多种类型,因此可以想到带有instanceof的if / switch,但这看起来像地狱一样难看。

      一旦我有正确的类型,我需要获得版本之间的所有差异,不包括版本本身或lastModifiedDate等特定字段。

      在我的脑海中还有HTTP 409,其中声明如果冲突响应应包含冲突字段。

      所有这些都有“最佳实践模式”吗?

2 个答案:

答案 0 :(得分:5)

乐观锁定的全部意义在于能够告诉最终用户:嘿,你试图保存这条重要信息,但其他人将它保存在你的背后,所以你最好刷新信息,决定如果您仍想保存它并可能输入一些新值,然后重试。

就像SVN一样,如果您尝试提交文件而其他人之前提交了新版本,SVN会强制您更新您的工作副本并解决潜在的冲突。

所以我会像JPA那样做:它让调用者通过抛出异常来决定做什么。应在表示层中处理此异常。

答案 1 :(得分:1)

困扰我的是JPA(Hibernate)和Spring提供的异常实际上并没有返回失败对象的当前版本。因此,如果用户需要决定做什么,他显然需要查看更新的最新版本。只是迟到了一个错误他的电话似乎迟钝了我。我的意思是你已经处于事务中的数据库级别,因此直接获取新的当前值没有成本......

我创建了一个新的Exception,它包含对无法更新的实体的最新版本的引用:

public class EntityVersionConflictException {

    @Getter
    private final Object currentVersion;

    public EntityVersionConflictException(
            ObjectOptimisticLockingFailureException lockEx,
            Object currentVersion){
        super(lockEx);
        this.currentVersion = currentVersion;
    }

    public Object getConflictingVersion() {
        return ((OptimisticLockException)getCause().getCause()).getEntity();
    }

    public Class getEntityClass() {
        return getCause().getPersistentClass();
    }

    @Override
    public ObjectOptimisticLockingFailureException getCause(){
        return (ObjectOptimisticLockingFailureException)super.getCause();
    }
}

和相应的服务方法

try {
    return getRepository().save(entity);
} catch (ObjectOptimisticLockingFailureException lockEx) {
    // should only happen when updating existing entity (eg. merging)
    // and because entites do not use CascadeType.MERGE
    // the entity causing the issue will always be the of class
    // entity.getClass()
    // NOTE: for some reason lockEx.getPersistentClass() returns null!!!
    // hence comparing by class name...
    if (lockEx.getPersistentClassName().equals(entityClass.getName())) {
        T currentVersion = getById(entity.getId());
        throw new EntityVersionConflictException(lockEx, currentVersion);
    } else {
        throw lockEx;
    }
}

注意评论。在CascadeType.MERGE的情况下,这不会像这样工作,逻辑必须要复杂得多。我为每个实体类型提供1个服务,因此服务必须保持对所有其他服务的引用,等等。