1. 程式人生 > >spring-data-jpa雙向表關聯查詢時引發異常:java.lang.StackOverflowError: null

spring-data-jpa雙向表關聯查詢時引發異常:java.lang.StackOverflowError: null

專案異常如下:

2018-01-26 17:12:38.162  WARN 3128 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: 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: demo.server.core.domain.User["userAddresses"]->
org.hibernate.collection.internal.PersistentBag[0]-> demo.server.core.domain.UserAddress["user"]-> demo.server.core.domain.User["userAddresses"]-> org.hibernate.collection.internal.PersistentBag[0]-> demo.server.core.domain.UserAddress["user"]-> ......//無限遞迴 java.lang.StackOverflowError:
null at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_111] at java.lang.ClassLoader.defineClass(Unknown Source) ~[na:1.8.0_111] at java.security.SecureClassLoader.defineClass(Unknown Source) ~[na:1.8.0_111] at java.net.URLClassLoader.defineClass(Unknown Source) ~[na:1.8.0
_111] at java.net.URLClassLoader.access$100(Unknown Source) ~[na:1.8.0_111] at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_111] at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_111] at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_111] at java.net.URLClassLoader.findClass(Unknown Source) ~[na:1.8.0_111] at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_111] at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) ~[na:1.8.0_111] at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_111] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:709) ~[jackson-databind-2.8.1.jar:2.8.1] at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.1.jar:2.8.1] at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:149) ~[jackson-databind-2.8.1.jar:2.8.1] at ...

看異常的名字會發現是棧溢位,而且第一處後面出現大量的遞迴的提示,這邊的原因是因為使用了jpa的實體類註解@ManyToMany,@ManyToOne,@OneToOne並且是雙向表關聯

舉個例子:

@Entity
@Table(name = "t_student")
public class Student{
    @Id
    @GeneratedValue
    private Integer sId;
    @Column(length = 20)
    private String sName;
    //學生所修課程
    @ManyToMany(mappedBy = "students")
    private List<Course> courses;
    //get、set 方法省略
}
@Entity
@Table(name = "t_course")
public class Course{
    @Id
    @GeneratedValue
    private Integer cId;
    @Column(length = 20)
    private String cName;
    //上課的學生
    @ManyToMany(cascade = CascadeType.ALL)
    private List<Student> students;
    //get、set 方法省略
}

一個student類和一個course類分別對應 t_student 表和 t_course表,它們是多對多的關係,一個學生可以修多個課程,一個課程可以有多個學生修

省略倉儲層程式碼…

控制層程式碼

@RestController
public class TestController{
    @Autowired
    private StudentRepository studentRep;

    @RequestMapping("/test")
    public int test(){
        List<Student> students = stntRep.findAll();
        students.forEach(data->{
            System.out.println(data);
        });
        return 1;
    }
}

此時,程式執行便會報 java.lang.StackOverflowError: null 異常

java.lang.StackOverflowError: null
    at java.util.AbstractCollection.toString(Unknown Source) ~[na:1.8.0_111]
    at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:510) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(Unknown Source) ~[na:1.8.0_111]
    at java.lang.StringBuilder.append(Unknown Source) ~[na:1.8.0_111]
    at com.demo.domain.Course.toString(Course.java:38) ~[classes/:na]
    at java.lang.String.valueOf(Unknown Source) ~[na:1.8.0_111]
    at java.lang.StringBuilder.append(Unknown Source) ~[na:1.8.0_111]
    at java.util.AbstractCollection.toString(Unknown Source) ~[na:1.8.0_111]
    at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:510) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at java.lang.String.valueOf(Unknown Source) ~[na:1.8.0_111]
    at java.lang.StringBuilder.append(Unknown Source) ~[na:1.8.0_111]
    at com.demo.domain.Student.toString(Student.java:40) ~[classes/:na]
    ....

Student類中有一個欄位List<Course> courses,在遍歷集合中,輸出一個student例項的時候,List<Course> courses 欄位也將輸出,因為是雙向多對多的關聯查詢,每一個Course例項也會輸出List<Student> students 欄位值,因此一直遞迴下去直到棧溢位報錯,若是單向多對多就不會發生這樣地遞迴

其實一開始報這個錯完全是因為對spring-data-jpa不熟悉,瞭解之後一想就通了。

解決辦法:

方法一:
此處,若不需要檢視關聯表中的欄位資訊時,可以在遍歷List<Student> students集合時先將關聯物件設定為null,即:

students.forEach(data->{
    data.setCourses(null);
    System.out.println(data);
});

這樣才能打印出Student 例項中除了courses 欄位之外地所有資料

方法二:
取消使用雙向多對多關聯,改為使用單向多對多,這樣也可以同時將關聯表中所對應的資料查詢出來使用