我有一个使用Spring Data JPA的Spring Boot 1.3.M1 Web应用程序。对于乐观锁定,我正在执行以下操作:
@Version private long version;
。通过查看数据库表,我确认该字段正在递增。version
字段。version
字段作为隐藏字段或其他内容接收。服务器端,获取实体的新副本,然后更新所需字段以及version
字段。像这样:
User user = userRepository.findOne(id);
user.setName(updatedUser.getName());
user.setVersion(updatedUser.getVersion());
userRepository.save(user);
我希望在版本不匹配时抛出异常。但它并没有。谷歌搜索,我发现一些帖子说我们无法设置附加实体的@Vesion
属性,就像我在上面的第三个语句中所做的那样。
所以,我猜我必须手动检查版本不匹配并自行抛出异常。这是正确的方法,还是我错过了什么?
答案 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和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