使用休眠 jpa 进行 JSON 序列化和反序列化以在 JSON 响应中将父对象转换为子对象

Posted

技术标签:

【中文标题】使用休眠 jpa 进行 JSON 序列化和反序列化以在 JSON 响应中将父对象转换为子对象【英文标题】:JSON serializing and deserializing with hibernate jpa to have parent object into child in JSON response 【发布时间】:2018-03-22 21:08:04 【问题描述】:

我正在使用 Spring 框架、Hibernate 和 JSON 开发 REST Web 应用程序。请假设我有两个如下实体:

BaseEntity.java

@MappedSuperclass
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
public abstract class BaseEntity implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    public long getId() 
        return id;
    

大学.java

 public class University extends BaseEntity 

      private String uniName;

       @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
      @JoinColumn(name = "university_id")
        private List<Student> students=new ArrayList<>();
    // setter an getter
    

学生.java

    public class Student extends BaseEntity

        private String stuName;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "university_id",updatable = false,insertable = false)   
        private University university;

    // setter an getter
        

当我调用我的 rest api 来列出大学时,一切都按我的预期正常工作,但是当我调用我的 rest api 来热切地列出学生时,我的 JSON 响应是

[
   
    "id": 1,
    "stuName": "st1",
    "university": 
        "id": 1,
        "uniName": "uni1"
                 
    ,
    
        "id": 2,
        "stuName": "st2",
        "university": 1
    
]

但我的期望回应是:

[
    
        "id": 1,
        "stutName": "st1",
        "university": 
        
         "id": 1,
        "uniName": "uni1"
        
    ,
    
        "id": 2,
        "stutName": "st2",
        "university": 
        
         "id": 1,
        "uniName": "uni1"
        
    

更新 1:我的休眠注释工作正常我有 JSON 问题

要求:

    双方都急需取货(大学这边可以)

    我需要每个学生在学生方面的大学对象(当我急切地获取学生时)

我需要什么样的序列化或 JSON 配置来匹配我想要的响应?

更新 2:

通过删除 @JsonIdentityInfo 并编辑学生端,如下所示:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)
@JsonIgnoreProperties(value = "students", allowSetters = true)
private University university;

json 响应还是一样的 我需要上面提到的期望回复。

谢谢

【问题讨论】:

添加@JsonIgnore并尝试 @Hema 我需要在学生端有大学对象而不是大学ID 是的,我提供了相同的代码。你将在 Student 上大学 @Generic 不要以为可以用简单的通用方式来完成。你认为list University is Ok 因为那里没有重复项。 @JsonIdentityInfo ID/reference 机制的工作原理是,一个对象实例只被完全序列化一次,并由它的 ID 在其他地方引用 github.com/FasterXML/jackson-databind/issues/372 你能查一下***.com/a/22617567/3530898 【参考方案1】:

从基类中删除 @JsonIdentityInfo,这会导致大学对象仅序列化 id。

【讨论】:

我需要@JsonIdentityInfo 来消除循环异常 我认为自定义序列化程序可以完成这项工作 在我的应用程序中有大量对象怎么可能!我需要为每一个编写序列化程序吗?【参考方案2】:

你也可以将@JoinColumn 添加到 Student 实体吗

@ManyToOne(fetch = FetchType.EAGER)  
@JoinColumn(name = student_id")

还要检查你的大学实体类的外键。外键应该来自其他实体吧? @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "student_id")) ??

另外,您也可以使用“mappedBy”。

@JoinColumn(name = "university_id", mappedBy="university")
        private List<Student> students=new ArrayList<>();

【讨论】:

这些变化对你的想法中的 jason 输出有影响吗? 我的 json 输出有问题【参考方案3】:

你可以添加这个并检查

大学

public class University 

@Fetch(value = FetchMode.SELECT)
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "university_id")
@JsonIgnore 
 private List<Student> students;


学生

public class Student
@ManyToOne
@JoinColumn(name = "university_id", insertable = true, updatable = true, nullable = true)
private University university;

【讨论】:

我试过了。它返回 [ "id": 1, "stutName": "st1", "university": "id": 1, "uniName": "uni1" , "id": 2, "stutName": "st2", "大学": 1 ] 它为第二个学生添加 uni id 而不是 uni 对象 第一个对象,你得到想要的格式对吗...? 如果您正确获取第一个对象格式,则映射工作正常 现在问题可能出在your values saved in DB 正确检查一次【参考方案4】:

我了解您不想在 JSON 中包含 University.students

    删除@JsonIdentityInfo

    @MappedSuperclass
    //@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
    public abstract class BaseEntity implements Serializable 
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
        public long getId() 
            return id;
        
    
    

    给学生加@JsonIgnore避免圈子

    public class University extends BaseEntity 
    
        private String uniName;
    
        @JsonIgnore
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
        @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "university_id"))
        private List<Student> students=new ArrayList<>();
        // setter an getter
    
    

如果您需要在其他上下文中序列化 University.students,请阅读 http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion。那里解释了处理双向关系的其他选项。

【讨论】:

通过添加@JsonIgnore,学生对象将在json输出中被忽略 你想序列化什么? List(那么我的答案应该有效)或大学?【参考方案5】:

为 getter 方法添加 @jsonignore 并将@jsonProperty 添加到该字段 喜欢

@JsonProperty(access = Access.READ_ONLY)
private String password;

最近向 jackson 添加了一些功能,例如 Readonly 和 writeonly 你可以参考这个:http://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.Access.html

【讨论】:

我可以在哪里添加@JsonProperty(access = Access.READ_ONLY)?在大学二传手! 在学生类侧添加流派,如@JsonProperty(access = Access.READ_ONLY) 私立大学大学; 结果是一样的,没有任何区别【参考方案6】:

您可能想尝试使用@JsonRawValue 作为university 属性的注释。您遇到的行为是由于引用崩溃 - 因为它两次相同 University,所以序列化程序会尝试变得聪明,并在第二次遇到它时返回一个引用。

编辑:toString()

@Override
public String toString() 
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(this);

【讨论】:

在添加 @JsonRawValue 后,行格式的响应为 [ "id" : 1, "stuName" : "stu1", "university" : entity.University@57ab302c , "id" : 2, "stuName" : "stu2", "university" : entity.University@57ab302c ] 那么接下来的步骤是什么? 使用返回 ObjectMapperUniversity 类覆盖 toString(),返回 mapperObject.writeValueAsString(this) - 这应该使它返回 JSON 而不是标准的 toString() 表示。 添加了toString() 它在 com.fasterxml.jackson.databind.ser.std.RawSerializer.serialize(RawSerializer.java:30) 在 com.fasterxml.jackson 产生错误 University.toString(University.java:46) .databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675)【参考方案7】:

映射关系的方式,即使它“工作正常”,也不符合双向关系的 jpa 规范。请参阅this question 和相关答案。

一般来说,关系的所有者是多对一的一方,而一对多的一方是用mappedBy注解的。您的映射解决方案,拥有该关系的一对多方不是通常/推荐的(如上面的答案中所述),但在技术上是可行的。 (@manyToOne 在您的示例中遗漏了一些属性,例如“updatable=false”)

那么with JPA and recent Hibernate version,懒加载策略如下:

 OneToMany: LAZY
 ManyToOne: EAGER
 ManyToMany: LAZY
 OneToOne: EAGER

因此,我建议您使用此默认延迟加载策略,并更改您的 manyToOne 关系的所有者,因为通过单个大学资源请求获取所有学生似乎不是一个好主意。 (你听说过分页吗?)

这样做,并从编组中排除学生集合,例如使用 @JsonIgnore,应该可以解决问题。

【讨论】:

亲爱的@Rémi Bantos 我已经尝试过使用该注释,没有 json 可以完美地工作,但是它们不能使用 json。如果您尝试添加与 Student 数据的 University 关系,它将无法在 student 表中添加 university_id。 在这种情况下,@JsonManagedReference 是必需的,我必须失去一些功能,例如我不能在 Json 响应中的 Student 端拥有 University 对象。 我已经详细说明了我的答案。你真的应该考虑改变你映射你的多对一一对多关系的方式,为学生提供适当的编组排除,如此处所述。因为,您映射的方式和杰克逊进行编组的方式是相关的。此外,我了解您希望包括具有急切获取类型的学生,我真的建议您不要这样做。相反,您应该更喜欢分页来获取所有这些内容。【参考方案8】:

我遇到了同样的问题。 Hibernate(或 eclipselink)不是问题。 JPA 中唯一的约束是 FetchType.EAGER

在 BaseEntity 中我添加了一个标准方法

public String getLabel()
   return "id:"+this.getId();

这个方法是抽象的,但是我有很多类,我不想全部改变,所以我添加了一个默认值。

在父实体中,在本例中为大学,覆盖该方法

@Override
public String getLabel
    return this.uniName;

对于每个父类,使用特定字段作为实体的标签

定义一个 MyStandardSerializer:

public class StandardJsonSerializer extends JsonSerializer<EntityInterface> 

@Override
public void serializeWithType(EntityInterface value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException 
        serialize(value, jgen, provider); 


@Override
public void serialize(EntityInterface value, JsonGenerator jgen, SerializerProvider provider) 
  throws IOException, JsonProcessingException 
    jgen.writeStartObject();
    jgen.writeNumberField("id", (long) value.getId());
    jgen.writeStringField("label", value.getLabel());
    jgen.writeEndObject();

在学生课上,在大学添加:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)   
@JsonSerialize(using=StandardJsonSerializer.class)
private University university;

现在您已经解决了循环问题。 当您需要标签时,覆盖父实体中的方法。 当您需要某些特定字段时,请创建特定的序列化程序。

【讨论】:

以上是关于使用休眠 jpa 进行 JSON 序列化和反序列化以在 JSON 响应中将父对象转换为子对象的主要内容,如果未能解决你的问题,请参考以下文章

Java应用如何使用JPA进行对象关系映射和持久化

什么是json的序列化和反序列化

Java下用Jackson进行JSON序列化和反序列化(转)

Java应用使用Jackson库进行JSON序列化和反序列化

JSON 序列化和反序列化 In Go

JSON 序列化和反序列化 In Go