手动设置版本字段

时间:2015-06-17 01:52:40

标签: hibernate jpa spring-data-jpa

我有一个使用Spring Data JPA的Spring Boot 1.3.M1 Web应用程序。对于乐观锁定,我正在执行以下操作:

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

    User user = userRepository.findOne(id);
    user.setName(updatedUser.getName());
    user.setVersion(updatedUser.getVersion());
    userRepository.save(user);
    
  5. 我希望在版本不匹配时抛出异常。但它并没有。谷歌搜索,我发现一些帖子说我们无法设置附加实体的@Vesion属性,就像我在上面的第三个语句中所做的那样。

    所以,我猜我必须手动检查版本不匹配并自行抛出异常。这是正确的方法,还是我错过了什么?

4 个答案:

答案 0 :(得分:13)

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

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

有一些方法可以解决这个问题。最直接的方法可能是通过自己实施乐观并发检查。我曾经有一个工具来做“DTO到模型”数据填充,我已经把那个版本检查逻辑放在那里。另一种方法是将逻辑放在setVersion()中,而不是真正设置版本,它进行版本检查:

class User {
    private int version = 0;
    //.....

    public void setVersion(int version) {
        if (this.version != version) {
            throw new YourOwnOptimisticConcurrencyException();
        }
    }

    //.....
}

答案 1 :(得分:1)

您还可以在从db中读取实体后分离实体,这也将导致版本检查。

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);
   }
...
}

答案 2 :(得分:0)

@AdrianShum答案的一部分是正确的。

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

  1. 检索带有版本号的版本化实体,名为V1。
  2. 假设你修改了某个实体的属性,那么Hibernate会将版本号增加到V2&#34;在内存中#34;。它没有触及数据库。
  3. 您提交更改或者它们由环境自动提交,然后Hibernate将尝试更新实体,包括其V2值的版本号。 Hibernate生成的更新查询只有在与ID和先前版本号(V1)匹配时才会修改实体的注册表。
  4. 成功修改实体注册表后,实体将V2作为其实际版本值。
  5. 现在假设在步骤1和3之间,实体被另一个事务修改,因此它在步骤3的版本号不是V1。然后,由于版本号不同,更新查询不会修改任何注册表,hibernate意识到并抛出异常。

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

    修改 不知道你在使用Spring Data JPA时使用哪个JPA持久性提供程序,但是有关使用JPA + Hibernate进行乐观锁定的更多详细信息,我建议你阅读第10章,控制并发访问部分。预订使用Hibernate的Java持久性(Hibernate in Action)

答案 3 :(得分:0)

除了@Adrian Shum回答,我想说明我是如何解决这个问题的。如果要手动更改实体版本并执行更新以导致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