本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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类,这导致了无限递归循环。
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": "英语"
}
]
}
}
(二发)如果对你有帮助,点赞可好!!