SpringDataJpa的使用 -- 实体类序列化

508 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

SpringDataJpa的使用 -- 实体类序列化

前面提到了 JAP 的几个常用注解 @OneToOne、@OneToMany、@ManyToOne、@ManyToMany 的使用,但没有说 序列化的问题,现在来补充一下。

上一篇文章:SpringDataJpa的使用 -- 一对一、一对多、多对多 关系映射 - 掘金 (juejin.cn)

1、实体类实现 Serializable 接口

Teacher.java

@Entity
@Table(name = "TEACHER")
public class Teacher implements Serializable {
    /**  其它属性及方法省略  */
    
    /**
     * 多对多
     * 被维护方
     * 学生 外键
     */
    @ManyToMany(mappedBy = "teacherList", fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    private List<Student> studentList;

}

Student.java

@Entity
@Table(name = "STUDENT")
public class Student implements Serializable {
    /**  其它属性及方法省略  */

    /**
     * 一对一
     * 家长 外键
     * 维护方
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.REMOVE, CascadeType.MERGE})
    @JoinColumn(name = "patriarch_id", unique = true, nullable = false)
    private Patriarch patriarch;
    
    /**
     * 多对一
     * 多方
     * 教室外键
     * 维护方
     */
    @JoinColumn(name = "class_room_id", unique = true, nullable = false)
    @ManyToOne(fetch = FetchType.EAGER)
    private ClassRoom classRoom;
    
    /**
     * 多对多
     * 维护方
     * 教师 外键
     */
    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JoinTable(name = "student_teacher", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "teacher_id"))
    private List<Teacher> teacherList;

}

Patriarch.java

@Entity
@Table(name = "PATRIARCH")
public class Patriarch implements Serializable {
    /**  其它属性及方法省略  */
    
    /**
     * 一对一
     * 被维护方
     */
    @OneToOne(mappedBy = "patriarch", fetch = FetchType.EAGER, cascade = {CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE})
    private Student student;
}

ClassRoom.java

@Entity
@Table(name = "CLASSROOM")
public class ClassRoom implements Serializable {
    /**  其它属性及方法省略  */

    /**
     * 一对多
     * 一方
     * (被)维护方
     * 采用下面注释里的注解时也是维护方,这里采用 被维护方代码
     */
    @OneToMany(mappedBy = "classRoom", fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REMOVE})
    private List<Student> studentList;

}

2、添加注解 @JsonIgnore

在添加注解 @JsonIgnore 前 会出现异常,主要是:某个A类中有一个属性是一个B类,在A类序列化时,A类的属性B类也要序列化,但是B类也有一个属性是A类,这导致了无限递归循环。

异常参考:《解决java.lang.IllegalStateException: Cannot call sendError() after the response has been committe》

2022-07-06 14:08:35.522  WARN 6140 --- [nio-8092-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failure while trying to resolve exception [org.springframework.http.converter.HttpMessageNotWritableException]

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
	at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:472) ~[tomcat-embed-core-9.0.63.jar:9.0.63]
	at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.sendServerError(DefaultHandlerExceptionResolver.java:552) ~[spring-webmvc-5.3.20.jar:5.3.20]
	at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.handleHttpMessageNotWritable(DefaultHandlerExceptionResolver.java:442) ~[spring-webmvc-5.3.20.jar:5.3.20]
	at 
        ...

异常参考:《对象序列化成json报错:java.lang.StackOverflowError: null》

2022-07-06 14:08:35.526 ERROR 6140 --- [nio-8092-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/jpa] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.example.demo.entity.Patriarch["student"]->com.example.demo.entity.Student["patriarch"]->com.example.demo.entity.Patriarch["student"]->com.example.demo.entity.Patriarch["student"]-
 ....
>com.example.demo.entity.Student["patriarch"]->com.example.demo.entity.Patriarch["student"]->com.example.demo.entity.Student["patriarch"])] with root cause

java.lang.StackOverflowError: null
	at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_152]
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_152]
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_152]
    ...
    
    2022-07-06 14:08:35.560 ERROR 6140 --- [nio-8092-exec-1] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/jpa/student/selectById/1] as the response has already been committed. As a result, the response may have the wrong status code.
解决方案:添加 @JsonIgnore 注解,破环无限递归循环

参考:《JAVA——json序列化错误["hibernateLazyInitializer","handler","fieldHandler"]解决方案》

在 ClassRoom、Teacher、Patriarch 三个类的外键上添加 注解 @JsonIgnore,而 Student 无需修改。(方法不唯一),注解来源 com.fasterxml.jackson.annotation.JsonIgnore

Teacher.java,添加 注解 @JsonIgnore 在外键上。

@Entity
@Table(name = "TEACHER")
public class Teacher implements Serializable {
    /**  其它属性及方法省略  */
    
    /**
     * 多对多
     * 被维护方
     * 学生 外键
     */
    @JsonIgnore
    @ManyToMany(mappedBy = "teacherList", fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    private List<Student> studentList;

}

Student.java,无变化,不添加 注解 @JsonIgnore

@Entity
@Table(name = "STUDENT")
public class Student implements Serializable {
    /**  其它属性及方法省略  */

    /**
     * 一对一
     * 家长 外键
     * 维护方
     */
    @OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.REMOVE, CascadeType.MERGE})
    @JoinColumn(name = "patriarch_id", unique = true, nullable = false)
    private Patriarch patriarch;
    
    /**
     * 多对一
     * 多方
     * 教室外键
     * 维护方
     */
    @JoinColumn(name = "class_room_id", unique = true, nullable = false)
    @ManyToOne(fetch = FetchType.EAGER)
    private ClassRoom classRoom;
    
    
    /**
     * 多对多
     * 维护方
     * 教师 外键
     */
    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JoinTable(name = "student_teacher", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "teacher_id"))
    private List<Teacher> teacherList;

}

Patriarch.java,添加 注解 @JsonIgnore 在外键上。

@Entity
@Table(name = "PATRIARCH")
public class Patriarch implements Serializable {
    /**  其它属性及方法省略  */
    
    /**
     * 一对一
     * 被维护方
     */
    @JsonIgnore
    @OneToOne(mappedBy = "patriarch", fetch = FetchType.EAGER, cascade = {CascadeType.REFRESH, CascadeType.MERGE, CascadeType.REMOVE})
    private Student student;
}

ClassRoom.java,添加 注解 @JsonIgnore 在外键上。

@Entity
@Table(name = "CLASSROOM")
public class ClassRoom implements Serializable {
    /**  其它属性及方法省略  */

    /**
     * 一对多
     * 一方
     * (被)维护方
     * 采用下面注释里的注解时也是维护方,这里采用 被维护方代码
     */
    @JsonIgnore
    @OneToMany(mappedBy = "classRoom", fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REMOVE})
    private List<Student> studentList;

}

到此:问题解决了。select的 SQL 语句展示。

Hibernate: select student0_.student_id as student_1_2_0_, student0_.class_room_id as class_ro5_2_0_, student0_.patriarch_id as patriarc6_2_0_, student0_.student_age as student_2_2_0_, student0_.student_name as student_3_2_0_, student0_.student_sex as student_4_2_0_, classroom1_.class_room_id as class_ro1_0_1_, classroom1_.class_room_capacity as class_ro2_0_1_, classroom1_.class_room_grade as class_ro3_0_1_, classroom1_.class_room_location as class_ro4_0_1_, classroom1_.class_room_name as class_ro5_0_1_, patriarch2_.patriarch_id as patriarc1_1_2_, patriarch2_.patriarch_address as patriarc2_1_2_, patriarch2_.patriarch_age as patriarc3_1_2_, patriarch2_.patriarch_name as patriarc4_1_2_, patriarch2_.patriarch_phone as patriarc5_1_2_, patriarch2_.patriarch_sex as patriarc6_1_2_, teacherlis3_.student_id as student_1_3_3_, teacher4_.teacher_id as teacher_2_3_3_, teacher4_.teacher_id as teacher_1_4_4_, teacher4_.teacher_age as teacher_2_4_4_, teacher4_.teacher_course as teacher_3_4_4_, teacher4_.teacher_name as teacher_4_4_4_, teacher4_.teacher_sex as teacher_5_4_4_ from student student0_ inner join classroom classroom1_ on student0_.class_room_id=classroom1_.class_room_id inner join patriarch patriarch2_ on student0_.patriarch_id=patriarch2_.patriarch_id left outer join student_teacher teacherlis3_ on student0_.student_id=teacherlis3_.student_id left outer join teacher teacher4_ on teacherlis3_.teacher_id=teacher4_.teacher_id where student0_.student_id=?
Hibernate: select studentlis0_.teacher_id as teacher_2_3_0_, studentlis0_.student_id as student_1_3_0_, student1_.student_id as student_1_2_1_, student1_.class_room_id as class_ro5_2_1_, student1_.patriarch_id as patriarc6_2_1_, student1_.student_age as student_2_2_1_, student1_.student_name as student_3_2_1_, student1_.student_sex as student_4_2_1_, classroom2_.class_room_id as class_ro1_0_2_, classroom2_.class_room_capacity as class_ro2_0_2_, classroom2_.class_room_grade as class_ro3_0_2_, classroom2_.class_room_location as class_ro4_0_2_, classroom2_.class_room_name as class_ro5_0_2_, patriarch3_.patriarch_id as patriarc1_1_3_, patriarch3_.patriarch_address as patriarc2_1_3_, patriarch3_.patriarch_age as patriarc3_1_3_, patriarch3_.patriarch_name as patriarc4_1_3_, patriarch3_.patriarch_phone as patriarc5_1_3_, patriarch3_.patriarch_sex as patriarc6_1_3_ from student_teacher studentlis0_ inner join student student1_ on studentlis0_.student_id=student1_.student_id inner join classroom classroom2_ on student1_.class_room_id=classroom2_.class_room_id inner join patriarch patriarch3_ on student1_.patriarch_id=patriarch3_.patriarch_id where studentlis0_.teacher_id=?
...

结果,json 数据,安装插件就可以在浏览器展示了。

{
    "code": 200,
    "msg": "请求成功",
    "data": {
        "studentId": 1,
        "studentName": "李四",
        "studentAge": 14,
        "studentSex": "男",
        "patriarch": {
            "patriarchId": 1,
            "patriarchName": "李华",
            "patriarchAge": 40,
            "patriarchSex": "男",
            "patriarchPhone": "15677777888",
            "patriarchAddress": "中国"
        },
        "classRoom": {
            "classRoomId": 1,
            "classRoomName": "高二三班",
            "classRoomLocation": "A栋三楼",
            "classRoomCapacity": 45,
            "classRoomGrade": "高二"
        },
        "teacherList": [
            {
                "teacherId": 1,
                "teacherName": "黄岐",
                "teacherAge": 38,
                "teacherSex": "男",
                "teacherCourse": "语文"
            },
            {
                "teacherId": 2,
                "teacherName": "黄芳",
                "teacherAge": 35,
                "teacherSex": "女",
                "teacherCourse": "英语"
            }
        ]
    }
}

(二发)如果对你有帮助,点赞可好!!