Mil*_*nov 5 hibernate jpa spring-data spring-data-jpa
我有一个非常简单的域模型,一个实体具有一个version字段,以便使用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 = GenerationType.SEQUENCE, generator = "gen")
private Long id;
@Column
private String state;
@Version
private int version;
}
Run Code Online (Sandbox Code Playgroud)
然后对该实体进行简单的操作,例如临时更改其阶段:
@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);
}
Run Code Online (Sandbox Code Playgroud)
我想要实现的结果是,有一种更新实体的方法,并且该更新还应该增加版本。如果存在不匹配,我希望会抛出a ObjectOptimisticLockingFailureException或OptimisticLockingException。我还希望version在对象中具有该字段的更新值,因为我要将其返回给客户端。
我尝试过的几种选择:
save-版本字段将被更新,但不会返回新值,并且客户端将获取旧值,这会使下一个请求到达锁定异常saveAndFlush-在这种情况下,我在客户端执行了两次更新语句,这奇怪地返回了版本X,在数据库中版本为X + 1。然后,下一个客户端请求再次遇到锁定异常。@Modifying查询,将其标记为自动清除(以自动刷新更改)并使用hql create versioned语法。然后,我执行以下查询:update activities set version=version+1, state=? where id=?,但这似乎并没有进行乐观锁检查(where version = :version_from_entity)。我也不认为这会引发适当的例外。因此,最后,我想实现的目标非常简单,并且我认为我不必自己编写它-有一种方法可以更新版本化实体上的一个或多个字段,依靠JPA来保持乐观锁定并获取最新版本,以便客户端可以对实体进行进一步的操作。我读了很多类似的问题,但是大多数都直接使用实体管理器,这不是我所追求的。
你的代码看起来很正确,所以很难说问题出在哪里......
我使用 Spring Data JPA 和乐观锁定创建了一个简单的工作示例。希望对您解决问题有所帮助。
创建一个实体:
@Transactional
public Model create(Model model) {
return modelRepo.save(model);
}
Run Code Online (Sandbox Code Playgroud)
更新实体:
@Transactional
public Optional<Model> update(int id, Model source) {
return modelRepo
.findById(id)
.map(model -> modelMapper.apply(model, source));
}
Run Code Online (Sandbox Code Playgroud)
获取实体:
@Transactional(readOnly = true)
public List<Model> getAll() {
return modelRepo.findAll();
}
Run Code Online (Sandbox Code Playgroud)
只需克隆项目,运行它(例如,使用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}Run Code Online (Sandbox Code Playgroud)
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}Run Code Online (Sandbox Code Playgroud)
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 |
|---|--------------|--------|Run Code Online (Sandbox Code Playgroud)
一些建议,如果你不介意的话)
使用对象而不是version属性的简单类型(例如Integer、Long等)。例如,当您自己创建实体标识符时,它很有用。在这种情况下,Spring Data/Hibernate 检查是否version对null数据库执行额外的选择查询。
当您更新实体时,您不必显式调用save存储库的方法 - 因为您处于事务中,Hibernate 会更新已更改的实体本身。