使用较旧版本的实体类实现复杂JPA实体的部分更新

Mar*_*eim 6 rest jpa jax-rs forward-compatibility

我有一个概念性的问题,我希望有人可能有答案.

我有什么?

我有一个RESTful服务,它为单一类型的实体提供类似CRUD的操作.实体(让我们称之为命令)使用Jackson表示为JSON.Jackson注释直接附加到实体类,没有明确的数据传输层.实体类包含许多属性,一对一和一对多关系.

从技术上讲,它基于Wildfly 10,JAX-RS 2.0和JPA 2.1(使用Hibernate 5).

目前支持的操作是:

  • GET 通过使用orders/123 读取实体EntityManager.find
  • PUT 使用或orders/123 创建替换实体EntityManager.persistEntityManager.merge

代码示例

该实体的简化版本如下所示:

@Entity
@Table(name = "order")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_EMPTY)
public class Order {

    @Id
    @SequenceGenerator(name = "order_id", sequenceName = "order_id_seq", allocationSize=1)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="order_id")
    @Column(name = "id")
    private long id;

    @Version
    private long version;

    @Column(name = "correlation_id", unique = true)
    private long correlationId;

    @OneToOne(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JsonManagedReference
    private SubOrder subOrder;
}

@Entity
@Table(name = "suborder")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_EMPTY)
public class SubOrder {

    @Id
    @SequenceGenerator(name = "suborder_id", sequenceName = "suborder_id_seq", allocationSize=1)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="suborder_id")
    @Column(name = "id")
    private long id;

    @Version
    private long version;

    @OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id", foreignKey = @ForeignKey(name = "suborder_order_FK1"))
    @JsonBackReference
    private Order order;

    @Column(name = "field1", length = CommonConstants.LENGTH_4, nullable = true)
    private String field1;
}
Run Code Online (Sandbox Code Playgroud)

JSON表示形式如下所示:

{
  "id": 6000000197444,
  "version": 358,
  "correlationId": 4711,
  "suborder": {
    "id": 1000000005025,
    "version": 40,
    "field1": "value"
  }
}
Run Code Online (Sandbox Code Playgroud)

我想做什么?

现在,我想向实体添加字段field2Order.该服务有多个客户端 - Client1Client2.

Client1执行实体的更新,并为新字段赋值newValue:

{
  "id": 6000000197444,
  "version": 358,
  "correlationId": 4711,
  "field2": "newValue",
  "suborder": {
    "id": 1000000005025,
    "version": 40,
    "field1": "value"
  }
}
Run Code Online (Sandbox Code Playgroud)

Client2对新字段不感兴趣.因此,没有开发新版本的Client2.现在,它想要将值更改field1为值AnotherNewValue.由于它不知道新字段field2,它将以下JSON提供给服务:

{
  "id": 6000000197444,
  "version": 358,
  "correlationId": 4711,
  "suborder": {
    "id": 1000000005025,
    "version": 40,
    "field1": "newValue"
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,Client1决定再次删除该值field2.因为该值现在为空,并且根据Jackson注释,不包括空值,以下JSON正在被PUT:

{
  "id": 6000000197444,
  "version": 358,
  "correlationId": 4711,
  "suborder": {
    "id": 1000000005025,
    "version": 40,
    "field1": "newValue"
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是什么?

此解决方案提供了一个严重的问题: 使用较旧的实体表示的客户端将意外删除其未知的字段.

所以基本上它会突破兼容性,这对我来说是个大问题!

我有一些可能的解决方案,但每个都有严重的缺点:

  1. 为实体的每次演变部署新版本的服务:简单且可以肯定,但会产生大量必须同时维护的版本.
  2. 为每个版本使用特定的媒体类型:这将允许客户端指定它引用的实体的版本.但是,我需要手动合并接收实体之间的差异(考虑哪个版本中存在哪些字段)和数据库中的差异.
  3. 具有部分更新的PATCH:我可以将问题分成两个问题:
    • 创建补丁(客户端):我必须在修改之前和之后手动比较实体.
    • 应用补丁(服务器):我必须从数据库中选择实体并手动应用补丁.

在哪里,我希望得到你的帮助

我认为这是一个非常常见的问题所以我希望你们中的一些人已经做过类似的事情了.我有具体的问题:1.有没有人知道允许在arbirtary对象树上创建和应用补丁的Java库(如解决方案2和3中所要求的)?2.有没有人有更简单的解决方案?

很抱歉,文字变得太长了,但我想要包含所有相关细节.非常感谢您的宝贵时间!