客户端/服务器 - 更新实体

时间:2014-11-06 14:59:56

标签: java spring hibernate jpa design-patterns

我们正在使用ORM框架(Hibernate)。我们的模型包含许多实体。我们使用spring framework与基于angularJS的客户端。整个通信是基于REST服务的ajax。我们主要使用客户端和服务器之间的DTO对象与Jackson(json-> Pojo)的编组。 Web应用程序适用于移动设备和PC(主要是PC)。

我们现在正在讨论更新实体的流程(客户端 - 服务器)。乍一看,这看起来很简单:

  1. 客户端将更新请求发送到服务器
  2. 服务器执行更新并以确认消息响应。
  3. 在更详细地讨论了流程后,我们提出了一些问题:

    1. 客户端究竟应该向服务器发送什么内容?

      • 它应该发送整个实体对象吗?
      • 应该发送(fieldName,NewValue)的地图吗?
      • 如果客户端确实发送了地图 - 客户端如何知道fieldNames?我们是否需要就预先定义的名字达成一致?
      • 如果我们同意预定义的名称 - 服务器如何解释它们?服务器是否保留了fieldName的地图 - >用于更新的实际DB fieldName?
    2. 服务器如何执行更新?

      • 服务器是否应为每个更改的字段执行不同的更新脚本?
      • 服务器是否应该执行根据客户端请求更改的单个巨大更新脚本?
    3. 我们一直在寻找关于这个简单概念的文章\帖子的一天,但找不到任何参考。

7 个答案:

答案 0 :(得分:1)

我自己也在努力解决这个问题,而且我花了更多的时间来承认找一个看似简单问题的干净解决方案。

在我工作的最后一个项目中,我们编写了自己的代码来处理自定义格式的传入HTTP PUT请求。自定义格式与对象类型无关,并且像地图一样工作。我们从服务层检索模型实体的实例,使用反射来应用补丁中包含的更改,然后保存实体。它奏效了,但很麻烦。

标准JSON PATCH format有一个RFC。 Spring最近发布了一个名为Spring Sync(在GitHub here上)的东西,似乎可以处理根据RFC格式化的PATCH请求。我还没有使用它,但看起来很漂亮。如果我开始新的事情,我会试一试。

根据我收集的内容,用法看起来像这样:

YourPojo pojo = yourDao.get(...)
Patch patch = JsonPatchMaker.fromJsonNode(yourJacksonJsonNode);
patch.apply(pojo, YourPojo.class);
yourDao.save(pojo);

答案 1 :(得分:1)

我们在项目中遵循以下模式

客户端究竟应该向服务器发送什么内容?

它应该发送整个实体对象吗?

=>不一定!您希望为消费者提供多大的灵活性,我们支持在项目中进行部分更新。

是否应该发送(fieldName,NewValue)的地图?

=>对于员工实体,如果他们只想更新名称,则在json下面放入/ employee / {id}

{
    name:'XYZ'
}

如果客户端确实发送了地图 - 客户端如何知道fieldNames?我们是否需要就预先定义的名称达成一致?

=>通过使用REST API,消费者签署合同以便您可以强制执行此操作,这是预先定义的。

如果我们同意预定义的名称 - 服务器如何解释它们?服务器是否保留了fieldName的地图 - >用于更新的实际DB fieldName?

=?这是数据库实体对象转换的标准DTO,它应该由业务逻辑处理。您可以使用Auto Mapper https://github.com/AutoMapper/AutoMapper

之类的内容

服务器如何执行更新?

=>这很棘手。我们这样做,我们从数据库中获取现有实体并与传入对象合并,然后调用update。当用户有意想要设置NULL时,它会变得更加棘手。我们使用特殊值解决了这个问题。

答案 2 :(得分:1)

首先,您需要考虑并发访问控制策略,否则您最终可能会使用losing updates

由于您正在处理multi-request conversation,因此服务器必须具有状态才能确保application-level repeatable reads。因此,Optimistic locking必须用于防止丢失的更新现象。

您不应该将您的域模型暴露给前端,因此最好使用中间DTO层,而像Dozer这样的映射工具可以减轻填充这些DTO的痛苦。有很多这样的mapper frameworks,所以你可以选择最适合你需求的那个。

当UI想要呈现视图时,后端获取实体并将DTO返回到前端。由于DTO比原始实体薄得多,因此可以节省带宽,并且只需要将DTO序列化为JSON(例如,需要@JsonIgnore用于循环依赖)。

DTO需要序列化到前端,因此您可以使用Jackson将DTOS转换为JSON对象。

前端使用关联的JSON对象,对这些对象进行更改并将其发回以保持持久性。

服务器端REST框架将JSON对象解组回DTO,中间件服务需要使用当前DTO数据更新与会话相关的实体。因此,您实际上正在更改为显示而加载的相同对象(如果其他一些用户更改了它们,您将获得乐观的锁定失败)。

答案 3 :(得分:1)

是否应该发送整个实体对象 Hibernate将在持久化时使用每个实体的标识符,因此所需的只是该标识符,但如果您希望使用它,则需要更多信息。例如,如果用户需要编辑实体,则必须在更新请求中发送实体的标识符和更改的任何字段。不需要发送未更改的字段,但是更容易发送它们并让休眠决定值是否已更改。

是否应该发送地图 如果它让你开心,是的,你只需要一个允许杰克逊在请求接收端建立你的DTO的解决方案。我所做的是发送DTO的JSON表示,这是非常好的,所以也许这就是你的意思。

假设有一个PersonEntity,它有字段'id'和'name';创建一个JSON时发布的JSON可能是{ name : 'Bob' }(让我们说Person获取id 1),编辑的JSON可以是{ id :1, name : 'Bobby' },删除的JSON可以是{ id : 1 }。在每种情况下,Jackson都会将提供的信息映射到您的PersonEntity DTO,设置“id”和“name”字段。

如果客户端确实发送了地图 - 客户端如何知道fieldNames?我们是否需要就预先定义的名称达成一致? 无论您选择哪种解决方案,都需要让Jackson将请求内容与您的DTO字段相匹配。让它们匹配使它变得容易,但Jackson确实允许自定义序列化和反序列化实现,所以它是可能的,

如果我们同意预定义的名称 - 服务器如何解释它们?服务器是否保留了fieldName的地图 - >更新的实际DB fieldName? 让Hibernate处理这个。您的配置或注释将告诉hibernate哪些实体字段转到哪些表列。

服务器如何执行更新? 可以通过org.hibernateSession.saveOrUpdate()方法执行更新。 Hibernate将根据其持久性状态确定实体是新的还是现有的。

服务器是否应为每个更改的字段执行不同的更新脚本? 一个语句执行会更快。如果你打开Hibernate的日志记录,我会打赌它是对一行所有更新的一个查询。

服务器是否应该执行根据客户端请求更改的单个巨大更新脚本? Hibernate将处理此问题,并在会话关闭或调用flush()时刷新会话(在服务器上进行更改)。有关Sessions和States的Hibernate文档编写得很好,可在此处找到(https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/objectstate.html

答案 4 :(得分:0)

  1. 您需要在客户端和服务器中定义这些对象。 Java具有用于将json / xml对象反序列化为Java定义的接口。如果某个字段不同,则会引发错误,因此您不必担心。
  2. 这取决于你想要什么以及你的目标是什么。一次更新的优点(在一个事务中):您使用一个事务,如果某些事情失败,一切都会回滚。缺点:如果出现问题,您将失去所有更新。

答案 5 :(得分:0)

在客户端定义简单对象,只需要传递参数。 在服务器中定义DO对象,需要保存到数据库。

变换对象是你选择的json,那没问题。 [spring rest xml / java JAXB]

开发改变json的中间组件< ==> java对象。

答案 6 :(得分:0)

这不是一个真正的答案,而是一个建议。考虑您选择的有关如何检测已删除属性的解决方案。如果您有包含集合(列表,集等)的层次结构对象结构,则很难检测哪些元素被修改或删除。出于这个原因,我已经通过我的项目选择每次发送和接收整个对象(作为json)。