Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Hibernate 手动设置版本字段时乐观锁定不引发异常_Hibernate_Jpa_Spring Data Jpa - Fatal编程技术网

Hibernate 手动设置版本字段时乐观锁定不引发异常

Hibernate 手动设置版本字段时乐观锁定不引发异常,hibernate,jpa,spring-data-jpa,Hibernate,Jpa,Spring Data Jpa,我有一个使用SpringDataJPA的SpringBoot1.3.M1 web应用程序。对于乐观锁定,我将执行以下操作: 注释实体中的版本列:@version private long version。通过查看数据库表,我确认该字段正确递增 当用户请求实体进行编辑时,同时发送version字段 当用户在编辑后按submit时,会将版本字段作为隐藏字段或其他内容接收 服务器端,获取实体的新副本,然后更新所需字段以及版本字段。像这样: User user = userRepository.find

我有一个使用SpringDataJPA的SpringBoot1.3.M1 web应用程序。对于乐观锁定,我将执行以下操作:

  • 注释实体中的版本列:
    @version private long version。通过查看数据库表,我确认该字段正确递增
  • 当用户请求实体进行编辑时,同时发送
    version
    字段
  • 当用户在编辑后按submit时,会将
    版本
    字段作为隐藏字段或其他内容接收
  • 服务器端,获取实体的新副本,然后更新所需字段以及
    版本
    字段。像这样:

    User user = userRepository.findOne(id);
    user.setName(updatedUser.getName());
    user.setVersion(updatedUser.getVersion());
    userRepository.save(user);
    
  • 当版本不匹配时,我希望这会引发异常。但事实并非如此。在谷歌上,我发现一些帖子说我们不能设置附加实体的
    @Vesion
    属性,就像我在上面的第三条语句中所做的那样

    所以,我猜我必须手动检查版本不匹配,然后自己抛出异常。这是正确的方法,还是我遗漏了什么?

    不幸的是,(至少对于Hibernate来说)手动更改
    @Version
    字段不会使其成为另一个“版本”。i、 e.乐观并发检查是针对读取实体时检索到的版本值而不是更新实体时的版本字段进行的

    e、 g

    这会奏效的

    Foo foo = fooRepo.findOne(id);  // assume version is 2 here
    foo.setSomeField(....);
    
    // Assume at this point of time someone else change the record in DB, 
    // and incrementing version in DB to 3
    
    fooRepo.flush();  // forcing an update, then Optimistic Concurrency exception will be thrown
    
    然而,这是行不通的

    Foo foo = fooRepo.findOne(id);  // assume version is 2 here
    foo.setSomeField(....);
    foo.setVersion(1);
    fooRepo.flush();  // forcing an update, no optimistic concurrency exception
                      // Coz Hibernate is "smart" enough to use the original 2 for comparison
    
    有一些方法可以解决这个问题。最直接的方法可能是自己实现乐观并发检查。我曾经有一个util来完成“DTO-to-Model”数据填充,我把版本检查逻辑放在那里。另一种方法是将逻辑放入
    setVersion()
    中,它不真正设置版本,而是执行版本检查:

    class User {
        private int version = 0;
        //.....
    
        public void setVersion(int version) {
            if (this.version != version) {
                throw new YourOwnOptimisticConcurrencyException();
            }
        }
    
        //.....
    }
    

    @AdrianShum的部分答案是正确的

    版本比较行为基本上遵循以下步骤:

  • 检索版本化实体及其版本号,让我们称为V1
  • 假设您修改了某个实体的属性,然后Hibernate将版本号增加到V2“内存中”。它不涉及数据库
  • 提交更改或环境自动提交更改,然后Hibernate将尝试用V2值更新实体,包括其版本号。Hibernate生成的更新查询只有在与ID和以前的版本号(V1)匹配时才会修改实体的注册表
  • 成功修改实体注册表后,实体将V2作为其实际版本值
  • 现在假设在第1步和第3步之间,实体被另一个事务修改,因此第3步的版本号不是V1。然后,由于版本号不同,更新查询不会修改任何注册表,hibernate意识到这一点并引发异常

    您可以简单地测试此行为,并检查是否在步骤1和步骤3之间通过直接更改数据库上的版本号引发异常

    编辑。
    不知道您在Spring Data JPA中使用的是哪一个JPA持久性提供程序,但有关JPA+Hibernate的乐观锁定的更多详细信息,我建议您阅读《Java持久性与Hibernate(Hibernate in Action)》一书的第10章“控制并发访问”一节,我想展示一下我是如何解决这个问题的。如果要手动更改实体的版本并执行更新以引起
    OptimisticConcurrencyException
    ,只需复制实体及其所有字段,从而使实体离开其上下文(与
    EntityManager.detach()
    相同)。这样,它的行为方式是正确的

    Entity entityCopy = new Entity();
    entityCopy.setId(id);
    ... //copy fields
    entityCopy.setVersion(0L); //set invalid version
    repository.saveAndFlush(entityCopy); //boom! OptimisticConcurrencyException
    
    编辑: 只有当hibernate缓存不包含具有相同id的实体时,组装的版本才起作用。这将不起作用:

    Entity entityCopy = new Entity();
    entityCopy.setId(repository.findOne(id).getId()); //instance loaded and cached 
    ... //copy fields
    entityCopy.setVersion(0L); //will be ignored due to cache
    repository.saveAndFlush(entityCopy); //no exception thrown  
    

    您还可以在从数据库读取实体后分离它,这也将导致版本检查

    User user = userRepository.findOne(id);
    userRepository.detach(user);
    user.setName(updatedUser.getName());
    user.setVersion(updatedUser.getVersion());
    userRepository.save(user);
    
    Spring存储库没有detach方法,您必须实现它。例如:

    public class BaseRepositoryImpl<T, PK extends Serializable> extends QuerydslJpaRepository<T, PK> {
    
       private final EntityManager entityManager;
    
       public BaseRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
           super(entityInformation, entityManager);
           this.entityManager = entityManager;
       }
    
       public void detach(T entity) {
           entityManager.detach(entity);
       }
    ...
    }
    
    公共类BaseRepositoryImpl扩展了QuerydslJpaRepository{
    私人最终实体管理人实体管理人;
    public BaseRepositoryImpl(JpaEntityInformation entityInformation,EntityManager EntityManager){
    超级(实体信息、实体管理器);
    this.entityManager=entityManager;
    }
    公共无效分离(T实体){
    entityManager.detach(实体);
    }
    ...
    }
    
    Hibernate允许您手动修改@version字段(与OpenJPA不同),但这不符合JPA规范(参见第11.1.54节)。如果您直接绑定到实体,那么您的方法应该有效。您是否将DTO传递给您的服务并在此处填充实体?是的,我上面代码中的UpdateUser就是DTO。您错过了我们正在讨论的内容。我们都清楚乐观并发是如何工作的。我们关注的是,对于Hibernate创建的更新查询,它没有使用实体中的
    @Version
    字段。相反,Hibernate将实体的版本存储在其他地方,并将其用于乐观并发检查。如何查找预先存在的实体,但不将其放在缓存中,以使第一个代码示例正常工作?@pgreen2在这种情况下,您查找现有实体,但要在数据库中持久化,则使用用户发送的实体来组装它。此时,分离实体的版本字段是可修改的。相反,获取的实体的版本字段是不可修改的。