4

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

我不确定OptimisticLockException在使用@Version.

由于我的应用程序的工作方式,一些关系有CascadeType.PERSIST,而CascadeType.REFRESH另一些关系也有CascadeType.MERGE

  1. 在哪里处理OptimisticLockException

据我所知,在服务层上处理这个问题不会特别有效,CascadeType.MERGE因为有问题的实体可能是需要由另一个服务处理的实体(我每个实体类都有一个服务)。

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

  1. 确定有问题的实体和已更改的字段

如果发生 OptimisticLockException,如何获取导致问题的实体以及更改了哪些字段?

是的,我可以打电话getEntity(),但我如何将其转换为正确的类型,尤其是在使用 CascadeType.MERGE 的情况下?实体可能有多种类型,所以instanceof会想到一个 if/switch ,但这看起来像地狱一样丑陋。

一旦我有了正确的类型,我就需要获取版本之间的所有差异,不包括某些字段,如版本本身或 lastModifiedDate。

在我的脑海里还有 HTTP 409,它指出在发生冲突的情况下响应应该包含冲突的字段。

这一切是否有“最佳实践模式”?

4

2 回答 2

5

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

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

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

于 2013-04-18T13:49:24.233 回答
1

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

我创建了一个新异常,其中包含对未能更新的实体的最新版本的引用:

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 个服务,因此服务必须保留对所有其他服务的引用,依此类推。

于 2013-05-15T06:19:29.503 回答