Spring JPA调用findById触发StackOverflowError问题求助
关联实体toString循环调用导致StackOverflowError问题解决
问题现象
当Track实体存在与Source关联的数据时,调用Source Repository的findById或findAll查询会触发StackOverflowError,错误栈显示Track与Source的toString方法互相循环调用。
错误栈信息
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null at com.encode.atlas.encoder.entity.track.Track.toString(Track.java:7) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.String.valueOf(String.java:4220) ~[na:na] at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na] at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na] at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:585) ~[hibernate-core-6.1.7.Final.jar!/:6.1.7.Final] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Source.toString(Source.java:9) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Track.toString(Track.java:7) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.String.valueOf(String.java:4220) ~[na:na] at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na] at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na] at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:585) ~[hibernate-core-6.1.7.Final.jar!/:6.1.7.Final] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Source.toString(Source.java:9) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Track.toString(Track.java:7) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.String.valueOf(String.java:4220) ~[na:na] at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na] at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na] at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:585) ~[hibernate-core-6.1.7.Final.jar!/:6.1.7.Final] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Source.toString(Source.java:9) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na] at com.encode.atlas.encoder.entity.track.Track.toString(Track.java:7) ~[classes!/:0.0.1-SNAPSHOT] at java.base/java.lang.String.valueOf(String.java:4220) ~[na:na]
实体代码
Source实体
@Data @Entity public class Source { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String path; private Integer duration; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY) private List<Track> tracks = new ArrayList<>(); }
Track实体
@Data @Entity public class Track { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(cascade = CascadeType.ALL) @JsonBackReference private Source source; private Integer index; private String codecType; private String pixelFormat; private Integer width; private Integer height; private Integer bitrate; private Double fps; private String codec; private Integer sampleRate; private String channel; }
查询代码
Source source = sourceRepository.findById(job.getSource().getId()).orElseThrow();
问题原因
Lombok的@Data注解会自动生成包含所有字段的toString方法,而Source和Track存在双向关联:
- Source的toString会遍历tracks集合,调用每个Track的toString
- Track的toString又会调用关联的Source的toString
- 如此循环往复,直到JVM栈内存耗尽,抛出StackOverflowError
注:@JsonBackReference仅解决JSON序列化时的循环问题,不影响toString方法;调整FetchType为LAZY/EAGER也无法避免,因为toString触发时Hibernate会加载关联对象,依然会进入循环。
解决方案
方案一:使用Lombok注解排除关联字段
在其中一方的关联字段上添加@ToString.Exclude,禁止该字段出现在自动生成的toString中,只需在任意一方添加即可:
修改Source实体
@Data @Entity public class Source { // 其他字段不变 @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY) @ToString.Exclude // 排除tracks字段在toString中生成 private List<Track> tracks = new ArrayList<>(); }
或修改Track实体
@Data @Entity public class Track { // 其他字段不变 @ManyToOne(cascade = CascadeType.ALL) @JsonBackReference @ToString.Exclude // 排除source字段在toString中生成 private Source source; }
方案二:手动编写toString方法
放弃Lombok自动生成的toString,手动编写只包含自身基本字段的toString方法,避免调用关联对象的toString:
Source手动实现toString
@Entity public class Source { // 字段不变 @Override public String toString() { return "Source{" + "id=" + id + ", name='" + name + '\'' + ", path='" + path + '\'' + ", duration=" + duration + '}'; } }
Track手动实现toString
@Entity public class Track { // 字段不变 @Override public String toString() { return "Track{" + "id=" + id + ", index=" + index + ", codecType='" + codecType + '\'' + ", pixelFormat='" + pixelFormat + '\'' + ", width=" + width + ", height=" + height + ", bitrate=" + bitrate + ", fps=" + fps + ", codec='" + codec + '\'' + ", sampleRate=" + sampleRate + ", channel='" + channel + '\'' + '}'; } }
内容的提问来源于stack exchange,提问作者berkay demirbas
相关产品推荐
相关产品推荐

