Hibernate 手动设置版本字段时乐观锁定不引发异常
我有一个使用SpringDataJPA的SpringBoot1.3.M1 web应用程序。对于乐观锁定,我将执行以下操作: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
@version private long version代码>。通过查看数据库表,我确认该字段正确递增
version
字段版本
字段作为隐藏字段或其他内容接收版本
字段。像这样:
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的部分答案是正确的 版本比较行为基本上遵循以下步骤:
不知道您在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在这种情况下,您查找现有实体,但要在数据库中持久化,则使用用户发送的实体来组装它。此时,分离实体的版本字段是可修改的。相反,获取的实体的版本字段是不可修改的。