【问题标题】:Spring Data JPA: Delete Optimistic Locking semanticsSpring Data JPA:删除乐观锁定语义
【发布时间】:2017-10-02 10:05:31
【问题描述】:

有一个实体 Foo 带有一个 @Version 列。如果我想删除它,我希望 Spring Data JPA 和/或 Hibernate 检查 @Version 列的当前值是否与数据库中的值匹配。如果不是,则应拒绝删除。这对分离的实体按预期工作:

@Transactional
public void delete(Foo foo) {
    fooRepository.delete(foo); // throws ObjectOptimisticLockingFailureException
}

但是,如果我首先从存储库中加载实体,然后使用不同版本在同一事务中将其删除,则无论@Version 列的值如何,删除都会通过:

@Transactional
public void delete(int fooId, long version) {
    Foo foo = fooRepository.findOne(fooId);
    foo.setVersion(version);
    fooRepository.delete(foo); // passes regardless of value of version
}

当我查看 Hibernate 调试输出时,执行了版本比较 (delete from foo where id=? and version=?),但没有达到我期望的效果。

我错过了什么?

【问题讨论】:

    标签: java hibernate jpa spring-data-jpa


    【解决方案1】:

    来自JPA specification3.4.2部分:

    实体可以访问其版本字段或属性的状态,或者 导出一个方法供应用程序使用以访问版本,但是 不得修改版本值。除了部分中提到的例外 4.10,仅允许持久化提供者设置或更新对象中版本属性的值。

    version 属性的目的是防止我们在当前持久化上下文中加载对象后可能发生的并发更新,Hibernate 通过忽略您手动设置的任何值来实现它,而是使用从加载对象时的数据库。为了验证这一点,enable 也打印了绑定的变量值,您会注意到使用了数据库中的值。

    例如,实际使用 DTO 时使用的标准解决方案是在从 DTO 更新实体状态时手动执行检查:

    if (entity.getVersion() != dto.getVersion()) {
        throw new OptimisticLockException("...");
    }
    

    当然,您可以通过从为所有可版本化实体提供此检查的基类或在某些 util 方法中扩展来使其更通用。例如,有些作者直接在版本设置器中这样做:

    public void setVersion(long version) {
        if (this.version != version) {
          throw new OptimisticLockException("...");
        }
    } 
    

    Hibernate 自动为分离的实体执行此检查,如 DefaultMergeEventListener 的实现所示:

    else if (isVersionChanged(entity, source, persister, target)) {
        if (source.getFactory().getStatistics().isStatisticsEnabled()) {
            source.getFactory().getStatisticsImplementor()
                .optimisticFailure(entityName);
        }
        throw new StaleObjectStateException(entityName, id);
    }
    

    【讨论】:

      【解决方案2】:

      根据JPA spec(部分11.1.54,强调我的):

      Version 注释指定实体类的版本字段或属性,作为其乐观锁值。 版本用于确保执行合并操作时的完整性和乐观并发控制。

      在非托管实例上执行存储库delete 操作首先会执行merge,因此会按预期抛出ObjectOptimisticLockingFailureException

      在托管实例上执行存储库delete 操作,但是直接在底层EntityManager 上调用delete,因此也不例外。


      总之,规范要求merge 使用@Version 字段,而不是为托管实例调用,因此在第二种情况下不会出错。

      【讨论】:

      • 确切地说,您只能删除托管实体,因此需要合并,这是执行版本检查的地方。我查看了 Hibernate 源代码,如果您查看 DefaultDeleteEventListener.onDelete() 内部,您会发现它在调用内部删除方法时不查看版本。
      • @KlausGroenbaek,我不明白你的评论。发帖人问为什么版本不匹配会导致错误在一种情况下而不是另一种情况下,我已将他们指向规范,该规范指定仅在一种情况下需要版本检查(在它工作的情况下)而不是另一种情况因此观察到的行为符合规范。当您说 Hibernate onDelete 不查看版本时,根据不需要 delete 检查版本的规范,这是预期的。我没有看到与规范有任何偏差。
      • 我提高了你的答案,因为它完全正确。我只是指出如果他阅读了代码,他可以看到删除时没有检查版本。许多开发人员提出问题并等待其他人回答,通常是更快地阅读/调试代码。在这种情况下,我不到 15 分钟就找到了。当然,对于 JPA,了解规范也很好,但有时您需要查看代码/文档,因为供应商实际上并未实现规范。例如,在 EclipseLink 中从未遇到过 LazyInitializationException 的等价物。
      猜你喜欢
      • 2014-03-07
      • 2017-08-11
      • 2018-04-27
      • 1970-01-01
      • 1970-01-01
      • 2019-07-20
      • 2012-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多