简体   繁体   English

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

[英]JSON serializing and deserializing with hibernate jpa to have parent object into child in JSON response

I am developing rest web app with spring framework, Hibernate and JSON. 我正在使用spring框架,Hibernate和JSON开发rest web app。 Please Assume that I have two entities like below: 请假设我有两个实体,如下所示:

BaseEntity.java 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;
    }
}

University.java University.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
    }

Student.java Student.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
        }

when I call my rest api to list University every things work fine as I expect, but when I call my rest api to list student eagerly my JSON response is 当我打电话给我的休息api列出大学的时候,每件事情都按照我的预期正常工作,但是当我打电话给我的休息api时,我急切地希望我的JSON回应是

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

but my desired response is: 但我希望的回答是:

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

Update 1 : my hibernate annotation working fine I have JSON issue 更新1 :我的hibernate注释工作正常我有JSON问题

Requirements : 要求 :

  1. I need both side fetch eagerly(the university side is Ok) 我急切地需要双方取得(大学方面是好的)

  2. I need university object in student side for every student(when I fetching student eagerly) 我需要每个学生在学生方面的大学对象(当我热切地拿学生时)

What kind of serialization or JSON config I need to do that for matching my desired response? 我需要什么样的序列化或JSON配置才能匹配我想要的响应?

Update 2: 更新2:

by removing @JsonIdentityInfo and editing student side like below: 删除@JsonIdentityInfo并编辑学生方面如下:

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

the json response still same I need my desired response that is mentioned above. json响应仍然相同,我需要上面提到的我想要的响应。

thanks 谢谢

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

Can you add @JoinColumn to Student entity as well 您是否也可以将@JoinColumn添加到Student实体

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

Also check your University entity class's foreign key.The foreign key should be from other entity right? 还要检查你的大学实体类的外键。外键应该来自其他实体吗? @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "student_id")) ?? @JoinColumn(name =“university_id”,foreignKey = @ForeignKey(name =“student_id”))??

Else alternatively you can use the "mappedBy" as well. 另外,您也可以使用“mappedBy”。

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

You can add this and check 你可以添加它并检查

University 大学

public class University {

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

}

Student 学生

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

I understand you do not want to include University.students in your JSON. 我知道你不想在你的JSON中包含University.students

  1. Remove @JsonIdentityInfo 删除@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; } } 
  2. Add @JsonIgnore to students to avoid circle @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 } 

If you need University.students to be serialized in other contexts give http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion a read. 如果你需要在其他环境中对University.students进行序列化, 阅读http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion Other options to deal with bidirectional relationships are explained there. 其中解释了处理双向关系的其他选项。

The way you map your relationsip, even if it is "working fine", does not comply with jpa spec for bi-directional relationship. 映射关系的方式,即使它“工作正常”,也不符合双向关系的jpa规范。 See this question and related answers. 看到这个问题和相关的答案。

To summarize usually, the owner of the relationship is the many-to-one side and the one-to-many side is annotated with mappedBy. 通常总结一下,关系的所有者是多对一方,而一对多方是用mappedBy注释的。 Your mapping solution, with the one-to-many side owning the relationship is not usual / recommended (as described in the answers above) but technically possible. 您的映射解决方案,一对多方拥有关系是不常见/推荐(如上面的答案所述),但技术上可行。 (@manyToOne side misses some attributes like "updatable=false" in your example) (@manyToOne方面错过了一些属性,比如你的例子中的“updatable = false”)

Then, with JPA and recent Hibernate version , the lazy loading policy is the following: 然后, 使用JPA和最近的Hibernate版本 ,延迟加载策略如下:

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

So I would suggest you to use this default lazy loading policy, and to change the owner of your manyToOne relationship as it does not seem like a good idea to get all the students via a single University resource request. 因此,我建议您使用此默认延迟加载策略,并更改manyToOne关系的所有者,因为通过单个大学资源请求获取所有学生似乎不是一个好主意。 (Have you heard about pagination?) (你听说过分页吗?)

Doing so, and also excluding students collection from Marshalling, using for example @JsonIgnore , should do the trick. 这样做,并且使用例如@JsonIgnore排除来自编组的学生集合应该可以解决问题。

Add @jsonignore for getter method 为getter方法添加@jsonignore
and add @jsonProperty to the field like 并将@jsonProperty添加到字段中

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

Recently added some feature to jackson like Readonly and writeonly 最近为Jackon添加了一些功能,如Readonly和writeonly
you can refer this: 你可以参考这个:
http://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.Access.html http://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.Access.html

You might want to try using @JsonRawValue as an annotation for your university property. 您可能想尝试使用@JsonRawValue作为university财产的注释。 The behavior you're encountering is due to reference collapsing - since it's the same University twice, the serializer tries to be smart and just return a reference the second time it's encountered. 您遇到的行为是由于引用折叠 - 因为它是同一个University两次,序列化程序会尝试变聪明,并在第二次遇到时返回引用。

EDIT: The toString() : 编辑: toString()

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

I had the same problem. 我有同样的问题。 Hibernate (or eclipselink) are not the problem. Hibernate(或eclipselink)不是问题。 The only constraint in JPA is the FetchType.EAGER . JPA中唯一的约束是FetchType.EAGER

In the BaseEntity I have added a standard method 在BaseEntity中,我添加了一个标准方法

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

this method would be abstract, but I had a lot of class and i didn't want to change it all so I added a default value. 这个方法是抽象的,但我有很多类,我不想改变它所以我添加了一个默认值。

In parent entity, in this case University, override the method 在父实体中,在本例中为University,重写该方法

@Override
public String getLabel{
    return this.uniName;
}

For each parent class, use a particular field as a label for your entity 对于每个父类,请使用特定字段作为实体的标签

Define a MyStandardSerializer: 定义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();
}

} }

In the student class, on univrsity add: 在学生班上,在大学里添加:

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

Now you have resolved circularity. 现在你已经解决了循环问题。 When you need a label, override the method in the parent entity. 需要标签时,覆盖父实体中的方法。 When you need some particular fields, create a specific Serializer. 当您需要某些特定字段时,请创建特定的Serializer。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM