使用杰克逊将双向 JPA 实体序列化为 JSON

Posted

技术标签:

【中文标题】使用杰克逊将双向 JPA 实体序列化为 JSON【英文标题】:Serialize bi-directional JPA entities to JSON with jackson 【发布时间】:2014-05-02 03:48:45 【问题描述】:

我正在使用 Jackson 将我的 JPA 模型序列化为 JSON。

我有以下课程:

import com.fasterxml.jackson.annotation.*;
import javax.persistence.*;
import java.util.Set;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
@Entity
public class Parent 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  @JsonManagedReference
  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  private Set<Child> children;

  //Getters and setters

import com.fasterxml.jackson.annotation.*;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
@Entity
public class Child 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  @JsonBackReference
  @ManyToOne
  @JoinColumn(name = "parentId")
  private Parent parent;

  //Getters and setters

我正在使用 POJO 映射从模型序列化到 JSON。当我序列化父对象时,我得到以下 JSON:


  "id": 1,
  "name": "John Doe",
  "children": [
    
      "id": 1,
      "name": "child1"
    ,
      "id": 2,
      "name": "child2"
    
  ]

但是当我序列化一个 Child 时,我得到以下 JSON:


  "id": 1,
  "name": "child1"

缺少对父级的引用。 有没有办法解决这个问题?

【问题讨论】:

在实体中包含与 UI 相关的逻辑(即 json 注释)不是很糟糕吗?这不是扼杀模块化吗? 嗯...不。这就是实体存在的主要原因:作为数据模型表示,无论是 JPA、XML、JSON 还是这些的组合。让您的整个应用程序使用一组实体是设计良好的应用程序的指标 - 一组实体会导致单点故障,这反过来使应用程序在更高程度上可维护(和可交换)。跨度> 【参考方案1】:

我认为您必须在 @JsonIdentityInfo@JsonBackReference / @JsonManagedReference 之间进行选择。

我会在你的实体上使用:@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id"),删除 @JsonBackReference / @JsonManagedReference 对。

并在要排除的字段上添加@JsonIgnore

【讨论】:

【参考方案2】:

您可以使用 JsonManagedReference / JsonBackReference 同时使用 JsonIdentityInfo 来补充双向关系。

有问题的班级:

// bi-directional one-to-many association to Answer (Question is owner)
@JsonManagedReference
@OneToMany(mappedBy = "question", cascade = CascadeType.ALL)
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@QuestionAnswers")
private Set<Answer> answers = new HashSet<>();

在回答类中: // 与 Question 的双向多对一关联

@JsonBackReference
@ManyToOne
@JoinColumn(name = "questionId", referencedColumnName="id", foreignKey = @ForeignKey(name = "fk_answer_question"))
private Question question;

如果您需要子对象中的父引用,请删除托管/反向引用,这对我来说很好。

【讨论】:

什么是属性=“@QuestionAnswers”?据我了解,类的名称是 Answer 和 Question 同时使用两者对我没有任何帮助。 @JsonManagedReference/BackReference 似乎覆盖了@JsonIdentityInfo。【参考方案3】:

问题在于使用托管/反向引用要求遍历的方向始终是从父级到子级(即首先使用托管引用)。这是这些注释的限制。

正如另一个答案所暗示的那样,使用对象 ID 是更灵活的替代方法,可能可行。

另一个可能可行的选项是使用 JSON 视图或 JSON 过滤器来有条件地包含/排除父引用,如果您可以分开案例的话。这可能会变得一团糟。

【讨论】:

【参考方案4】:

@JsonIgnoreProperties("excludedPropertyName") 可以胜任。

@Entity
public class Parent 

    @JsonIgnoreProperties("parent")
    @OneToMany(mappedBy = "parent")
    private List<Child> children; 

    // ...

@Entity
public class Child 

    @JsonIgnoreProperties("children")
    @ManyToOne
    private Parent parent;

    // ...

【讨论】:

【参考方案5】:

您可以使用@JsonBackReference/@JsonManagedReference 并将此方法添加到孩子

@JsonProperty
public Long getParentId() 
    return parent == null ? null : parent.getId();

结果将是:


  "id": 1,
  "name": "child1",
  "parentId": 1

我希望这对某人有所帮助。

【讨论】:

帮助获取 parentIds。但是当我尝试向系统添加新实体并在 json 中指定时,例如"parentId":3 这不会持久化到数据库。 parentId 结果为空。也许你知道为什么?

以上是关于使用杰克逊将双向 JPA 实体序列化为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

杰克逊:将纪元反序列化为 LocalDate

是否有相当于杰克逊的@JsonAnyGetter/@JsonAnySetter 的 JPA 注释?

杰克逊未能将字符串反序列化为 Joda-Time

杰克逊 JSON、Spring MVC 4.2 和 Hibernate JPA 问题的无限递归

REST:如何以“浅”的方式将 java 对象序列化为 JSON?

高级双向杰克逊序列化以避免无限递归