Hibernate 如何使用Spring数据正确处理JPA实体中的版本字段
我有一个非常简单的域模型,一个实体有一个版本字段,以便使用JPA(api v2.2)提供的乐观锁定功能。我使用的实现是Hibernate v5.3.10.FinalHibernate 如何使用Spring数据正确处理JPA实体中的版本字段,hibernate,jpa,spring-data-jpa,spring-data,Hibernate,Jpa,Spring Data Jpa,Spring Data,我有一个非常简单的域模型,一个实体有一个版本字段,以便使用JPA(api v2.2)提供的乐观锁定功能。我使用的实现是Hibernate v5.3.10.Final @Entity @Data public class Activity { @Id @SequenceGenerator(name = "gen", sequenceName = "gen_seq", allocationSize = 1) @GeneratedValue(strategy = Genera
@Entity
@Data
public class Activity {
@Id
@SequenceGenerator(name = "gen", sequenceName = "gen_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gen")
private Long id;
@Column
private String state;
@Version
private int version;
}
然后对该实体进行简单操作,例如临时更改其阶段:
@Transactional
public Activity startProgress(Long id) {
var entity = activityRepository.findById(id).orElseThrow(RuntimeException::new);
if (entity.getState() == "this") { // or that, or else, etc
// throw some exceptions in some cases
}
entity.setState("IN_PROGRESS");
return activityRepository.saveAndFlush(entity);
}
这样做的结果,我想实现的是,有一种方法来更新实体,并且更新还应该增加版本。如果存在不匹配,我希望抛出ObjectOptimisticLockingFailureException
或OptimisticLockingException
。当我将version
字段返回给客户机时,我还希望得到对象中version
字段的更新值
我尝试过的几个选项:
- 只需调用
-版本字段会得到更新,但不会返回新值,客户端会得到旧值,这会使下一个请求命中锁定异常save
- 调用
——在本例中,我在客户机上执行了两次update语句,奇怪的是,它返回的是版本X,在数据库中,版本是X+1。然后,下一个客户端请求再次命中锁定异常saveAndFlush
- 创建一个
查询,自动将其标记为clear(以自动刷新更改),并使用hql@Modifying
语法。然后执行以下查询:创建版本化的
,但这似乎没有进行乐观锁检查(updateactivitiessetversion=version+1,state=?其中id=?
)。我也不认为它会引发适当的例外其中version=:version\u from_entity
因此,最终我想要实现的是非常简单的,我不认为我必须自己编写它-有一种方法来更新一个版本化实体上的一个或多个字段,依靠JPA进行乐观锁定,并获得最新版本,以便客户端可以对该实体执行进一步的操作。我读过很多类似的问题,但大多数都直接使用实体管理器,这不是我想要的。您的代码看起来非常正确,所以很难说问题出在哪里 我创建了一个使用Spring数据JPA和乐观锁定的简单示例。我希望这将有助于你解决这个问题 创建实体:
@Transactional
公共模型创建(模型){
返回moderepo.save(model);
}
更新实体:
@Transactional
公共可选更新(int-id,模型源){
回归模型回购
.findById(id)
.map(model->modelMapper.apply(model,source));
}
获取实体:
@Transactional(readOnly=true)
公共列表getAll(){
返回modelRepo.findAll();
}
只需克隆项目,运行它(例如,使用mvn spring boot:run
),然后检查日志:
15:44:35.905 INFO 2800 --- [ main] i.g.c.d.Application : [i] Creating...
15:44:35.925 INFO 2800 --- [ main] jdbc.sqltiming : batching 1 statements:
1: insert into model (name, version, id) values ('model', 0, 1); {executed in 1 msec}
(如果你不介意的话,给你一些建议) 对于
version
属性(例如Integer
、Long
等),使用对象而不是简单类型。例如,当您自己创建实体标识符时,它非常有用。在这种情况下,Spring Data/Hibernate检查version
是否为null
,并且不会对数据库执行额外的select查询
当您更新实体时,您不必显式调用回购的
save
方法-因为您在事务中,Hibernate会更新更改的实体本身。版本应该自动更新。您是否使用了javax.persistence
中正确的@Version
注释?是的,javax.persistence
而不是spring data commons中的注释。我在本地尝试了saveAndFlush()
,无法重现您提到的问题。它按预期工作,不再发生双重更新……那么在您的情况下,这两个更新SQL是什么样子的?它们是一样的吗?好的,但在您的示例中,您更新了实体,然后在单独的事务中读取它。在公共可选更新(int-id,Model-source)
方法的末尾,您是否正确地将版本设置为1?@MilanMilanov再次检查日志-我已经更新了一点答案…好的,我正式没有想法了。我将方法更改为不调用saveAndFlush
,并让Hibernate负责更新。测试仍然是绿色的,但令人惊讶的是,它在真正的Oracle DB设置中不起作用。日志是这样写的:SQL-select。。。来自活动act0,其中act0.id=?BasicBinder-将[1]绑定为[BIGINT]-[117]SQL-更新活动集状态=,版本=?其中id=?和版本=?BasicBinder-binding[1]作为[BIGINT]-[117]BasicBinder-binding[2]作为[VARCHAR]-[“正在进行中”]BasicBinder-binding[3]作为[INTEGER]-[113]BasicBinder-binding[4]作为[BIGINT]-[117]BasicBinder-binding[5]作为[INTEGER]-[112]
显然,版本从112更新到113,但在方法末尾仍然返回112,然后到客户端,异常再次发生。唯一使用的Oracle特定的事情是使用创建数据源。除此之外,设置与测试中的设置相同。任何关于行为可能不同的想法都是受欢迎的。
15:44:35.930 INFO 2800 --- [ main] i.g.c.d.Application : [i] Updating...
15:44:35.934 INFO 2800 --- [ main] jdbc.sqltiming : select model0_.id as id1_0_0_, model0_.name as name2_0_0_, model0_.version as version3_0_0_ from model model0_ where model0_.id=1; {executed in 0 msec}
15:44:35.939 INFO 2800 --- [ main] jdbc.resultsettable :
|---------|------|--------|
|id |name |version |
|---------|------|--------|
|[unread] |model |0 |
|---------|------|--------|
15:44:35.944 INFO 2800 --- [ main] jdbc.sqltiming : batching 1 statements:
1: update model set name='model_updated', version=1 where id=1 and version=0; {executed in 1 msec}
15:44:36.010 INFO 2800 --- [ main] i.g.c.d.Application : [i] Getting...
15:44:36.015 INFO 2800 --- [ main] jdbc.sqltiming : select model0_.id as id1_0_, model0_.name as name2_0_, model0_.version as version3_0_ from model model0_; {executed in 0 msec}
15:44:36.017 INFO 2800 --- [ main] jdbc.resultsettable :
|---|--------------|--------|
|id |name |version |
|---|--------------|--------|
|1 |model_updated |1 |
|---|--------------|--------|