首页 文章

Spring Data JPA:删除乐观锁定语义

提问于
浏览
8

有一个实体 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=? ),但没有达到我期望的效果 .

我错过了什么?

2 回答

  • 6

    来自JPA specification,部分 3.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);
    }
    
  • 5

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

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

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

    但是,在托管实例上执行存储库 delete 操作会直接在底层 EntityManager 上调用 delete ,因此没有异常 .


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

相关问题