在RESTful应用中的乐观锁定

10

我们正在开发一个RESTful应用程序,数据层将由Hibernate处理,但我们不确定如何处理实体的更新。

我们计划执行以下操作:

1)客户端通过id请求实体
2)Hibernate加载实体,将请求的字段(始终包括版本号)复制到DTO中,然后将其转换为JSON并发送给客户端
3)客户端管理一些字段,并将带有版本号的实体发送回服务器。
4)服务器接收转换为DTO的JSON。
5)从Hibernate加载相应的实体,并将DTO的属性复制到实体中。

=> 即使客户端的版本号设置了,实体也会被覆盖。 这是否意味着我们总是需要自己检查客户端的版本号与已加载实例的版本号,而不是Hibernate自动完成此项任务?

在常规应用程序中使用会话时,分离的实例保存在HttpSession中。每当客户端更新实体时,实例都会从HttpSession中检索并更新某些属性。每当Hibernate提交更新时,如果版本号小于当前版本号,则会抛出ObjectStaleException。

问题在于,由于我们试图成为RESTful,因此没有任何Http会话可用。

有没有一种通用解决方案来处理RESTful应用程序中的乐观锁定,而不是自己检查版本号?

3个回答

5

您的策略很好。只需将客户端传来的版本号复制到加载的实体中(或使用merge(),效果相同),当Hibernate刷新实体时,如果版本号已增加,则会出现乐观锁异常。

您不需要自己检查任何内容,Hibernate会为您检查。


4
根据Hibernate规范,你不能更改已加载对象的版本号。所以我想我必须创建一个新实例来表示已更改的实体(带有旧的版本号),然后将其与已加载的实体合并?但是在部分更新的情况下,合并将使一些字段为空...我仍然不确定如何处理这个问题。 - user2054927
这就是合并操作的作用:它将分离实体的状态与已连接实体的状态复制,包括版本。如果不这样做,乐观锁定将完全失效。明确设置版本或使用合并将产生相同的效果。文档的意思是您不能更改版本号以人为地增加其值(因为这样做要么会在不应该时引发异常,要么不会在应该时引发异常)。 - JB Nizet
我也可以用saveOrUpdate()吗? - user2054927
可以的,但我不喜欢saveOrUpdate()。它可能导致附加实体与分离实体链接在一起,这会让事情变得混乱。 - JB Nizet
@user2054927 你最终选择了什么?当我尝试执行 entityManager.save(myDto) 时,由于从客户端接收到的DTO中没有版本(因为 @Version 更像是DAO关注的内容),所以我会遇到 OptimisticLockException 异常。如果我执行 Dto toBeSaved = objectMapper.map(queriedMatchingDaoUsingPrimaryKeyFromReceivedDto, Dto.class),那么我就有了版本,但我必须手动更新每个字段以根据接收到的DTO进行更新:这很繁琐,并且感觉可能会在长期内导致错误。 - payne

1
检查版本的替代方法是构造实体对象并调用entityManager.merge,如果版本在此期间发生更改,它也应该触发乐观锁异常,但如果对象在此期间被删除,则不会抛出异常。要正确处理这个问题,你需要自己检查它。
与单独加载实体状态的实现不同,你可以使用Blaze-Persistence Updatable Entity Views,这是一个在JPA之上开发DTO的库,它还实现了对乐观锁的支持。你的使用案例应该已经得到支持,但我目前没有一个非常好的Spring WebMvc集成,所以你现在需要自己进行一些管道工作。不过我有一个想法,只是时间和感兴趣的人,直到集成变得更加流畅。
可更新的实体视图允许映射实体的子集,并仅将该子集刷新回去。由于使用了脏跟踪,它知道发生了什么变化,从而实现了细粒度刷新。

因此,您想要的PATCH支持的想法就是只通过对象的id获取一个空引用。 空引用意味着它没有数据,即所有值都为null。 脏数据跟踪假定初始状态全部为空。 如果请求有效载荷上的某个值为空,则它不会被识别为已更改,因此会忽略它。 如果设置了任何非null值,则确定该字段是脏的,在flush时仅刷新脏值。

我自己还没有尝试过,但您可以像这样做

// Create reference for the id, the object is empty i.e. all null except for the id
CustomerDTO dto = entityViewManager.getReference(CustomerDTO.class, someId);
// Map the payload on the DTO which will call setFoo(null) but that's ok, because that isn't considered being dirty
jsonMapper.map(requestPayload, dto);
// Flush dirty changes i.e. non-null values
entityViewManager.update(entityManager, dto);

使用PARTIAL刷新模式时执行的更新查询仅包含具有非空值属性的set子句。DTO应如下所示。
@EntityView(Customer.class)
@UpdatableEntityView(mode = FlushMode.PARTIAL)
public interface CustomerDTO {
  @IdMapping Integer getId();
  String getName();
  void setName(String name);
  String getDesc();
  void setDesc(String desc);
}

如果没有脏数据,它甚至不会执行查询。

0

从服务器应用程序的数据库层面来看,无论用户是在主机绿屏、HTML表单还是REST客户端上编辑数据,都没有区别。

数据从数据库中读取,呈现给用户(用户可能在编辑时去吃午饭或度假),然后写回到数据库中。

在REST应用程序的上下文中,乐观锁定并没有什么特别之处。对于3270或HTML屏幕有效的相同乐观锁定模式对于基于REST的Angular应用程序仍然有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接