RESTful 应用程序中的乐观锁定
Posted
技术标签:
【中文标题】RESTful 应用程序中的乐观锁定【英文标题】:Optimistic locking in a RESTful application 【发布时间】:2013-08-12 20:07:08 【问题描述】:在工作中,我们正在开发一个 RESTful 应用程序,其中数据层将由 Hibernate 处理。但我们不确定如何处理实体更新。
我们计划做以下事情:
1) 客户端通过 id 请求实体 2) Hibernate 加载实体,请求的字段(始终带有版本)被复制到 DTO 中,该 DTO 被转换为 JSON 并发送到客户端 3)客户端管理一些字段并将实体(带有版本号)发送回服务器。 4) 服务器接收到的 JSON 转换为 DTO。 5) 从 Hibernate 加载对应的实体,并将 DTO 的 props 复制到实体中。
=> 即使设置了客户端的版本号,实体也总是被覆盖。 这是否意味着我们总是需要自己检查客户端的版本号和加载的实例的版本号,而不是 Hibernate 这样做?
在带有会话的常规应用程序中,分离的实例保存在 HttpSession 中。每当客户端更新实体时,都会从 HttpSession 检索实例并更新一些属性。每当 Hibernate 提交更新时,如果版本号为
这里的问题是我们没有任何 Http 会话,因为我们试图成为 RESTful。
是否有通用的解决方案来处理 ,而不是自己检查版本号?
【问题讨论】:
【参考方案1】:从服务器应用程序的数据库层来看,用户在大型机绿屏、html 表单或 REST 客户端中编辑数据没有区别。
从数据库中读取数据,呈现给用户(编辑时可能会去吃午饭或度假),然后写回数据库。
在 REST 应用程序的上下文中,乐观锁定没有什么特别之处。对 3270 或 HTML 屏幕有效的乐观锁定模式对基于 REST 的 Angular 应用程序仍然有效。
【讨论】:
【参考方案2】:自己检查版本的替代方法是构造实体对象并调用entityManager.merge
,如果版本同时发生变化,这也应该触发乐观锁异常,但如果对象在与此同时。要正确处理,您必须自己检查。
您可以使用Blaze-Persistence Updatable Entity Views,而不是通过单独加载实体状态来实现这一点,这是一个用于在 JPA 之上开发 DTO 的库,它还实现了对乐观锁定的支持。 你的用例应该已经得到支持,虽然我还没有很好的 Spring WebMvc 集成,所以你现在必须自己做一些管道。不过,我对此有一些想法,这只是时间和相关方的问题,直到集成更加顺利。
可更新的实体视图允许映射实体的子集,并且只刷新该子集。由于使用了脏跟踪,它可以准确地知道发生了什么变化,从而可以进行细粒度的刷新。
因此,PATCH 支持 的想法似乎就是您想要的,只是通过对象的 id 获取空引用。为空意味着它没有数据,即所有空值。脏跟踪假设初始状态全部为空。您可以简单地将请求有效负载映射到该对象上,如果值为 null,它不会将其识别为已更改,因此忽略它。如果设置了任何非空值,则确定此类字段是脏的,并且在刷新时,仅刷新脏值。
我自己还没试过,但你可以这样做
// 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
刷新模式时执行的更新查询将只包含具有非空值的属性的集合子句。 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);
如果没有脏东西,它甚至不会执行查询。
【讨论】:
【参考方案3】:你的策略很好。只需将来自客户端的版本号复制到加载的实体中(或使用merge()
,也会这样做),当Hibernate刷新实体时,如果版本号已增加,您将有一个乐观锁异常.
您无需自己检查任何内容。 Hibernate 会为您进行检查。
【讨论】:
根据 Hibernate 规范,您不能更改已加载对象的版本号。所以我想我必须创建一个代表更改的实体(具有旧版本号)的新实例,然后将它与加载的实体合并?但是在部分更新的情况下,合并会清除一些字段......我仍然不确定如何处理这个。 这就是合并的作用:它将分离实体的状态复制到附加实体,包括版本。如果没有,乐观锁定将被完全破坏。显式设置版本或使用合并将具有相同的效果。文档的意思是,您不得更改版本号以人为地增加其值(因为这会在不应该导致异常时导致异常,或者在应该时不会导致异常)。 我也可以为此使用 saveOrUpdate() 吗? 是的,你可以。但我不喜欢 saveOrUpdate()。它可能导致附加实体链接到分离实体,从而使事情变得混乱。 @user2054927 你最终的目的是什么?当我尝试执行entityManager.save(myDto)
时,我得到一个OptimisticLockException
,因为从客户端收到的DTO 中没有版本(因为@Version
是一个DAO 问题)。如果我做Dto toBeSaved = objectMapper.map(queriedMatchingDaoUsingPrimaryKeyFromReceivedDto, Dto.class)
,那么我确实包含version
,但我必须手动浏览每个字段以根据收到的DTO更新它们:这很麻烦,感觉可能会导致长期错误.以上是关于RESTful 应用程序中的乐观锁定的主要内容,如果未能解决你的问题,请参考以下文章
Grails:乐观锁定,StaleObjectStateException 与 Spring Security 会话中的域,更新计数器