最近写个小项目,用到JPA框架,项目很简单,两张表一对多关系,写完发现总是会有在json的时候递归栈溢出的问题,开始也没多想,就在关系的属性上加了个@JsonIgnore的注解,暂且能用,项目写到后面,我就是要关联查询啊,这么玩怎么搞。
感觉是不是自己项目配置或者哪里用的不对,就搜了篇标准的JPA 一对多的例子来照抄:
https://attacomsian.com/blog/spring-data-jpa-one-to-many-mapping
抄完之后,还是不得行,如果去掉JsonIgnore注解,还是会出现 StackOverflowError: null 这个异常,感觉事情不简单,我看了下异常栈,点来点去,感觉可能是我引入的lombok坑了我。我参考的项目没有是 lombok,于是乎我就带这lombok的关键字去搜了下,果然找到了这篇文章:
https://blog.glinboy.com/2019/02/lombok-hibernate-and-stackoverflow-error.html
I really like Project Lombok, with use of this library you can code cleaner, just look at the sample of @Data (here) and you give me the right!
But after using @Data on an entity, and use it into another entity (eg a Set of comments of a post), throw an ugly error: java.lang.StackOverflowError: null. @Data is a shortcut for these annotations of Lombok:
- @Getter
- @Setter
- @ToString
- @RequiredArgsConstructor
- @EqualsAndHashCode
And @EqualsAndHashCode makes this error! I found the solution at last comment of this question on StackOverflow: https://stackoverflow.com/questions/34972895 We should add this line just after at @Data and exclude relational fields from Equals and Hash methods:
@EqualsAndHashCode(exclude="entries")
I used it on my project, Feader, and you can use it as a sample to check it out ( on develop branch); These are my use case classes Feed.java, and FeedEntry.java, FeedEntry use into Feed (like comments and post):
大概意思就是说,Lombok框架虽然好啊,但是容易上头,这个@Data注解如果包含的实体如果被另一个实体引用,就可能丢出来一个栈溢出的异常,导致这个问题的原因是@Data注解包含的@EqualsAndHashCode这个注解。解决的方法很简单,只需要在@Data注解的实体类上加上一个排除那个字段的equals注解就行了,比如你那个字段叫 entries,就加个注解 @EqualsAndHashCode(exclude=”entries”),经过验证确实解决了问题。
延伸:
https://github.com/rzwitserloot/lombok/issues/1007
With
public class Book {
private BookDetail bookDetail;
}
public class BookDetail {
private Book book;
}
you’ve created a cyclic structure. Generated methods like equals
, hashCode
and toString
can’t deal with it… and there’s no unique way how they could. They could detect cycles, but this would cost time even for non-cyclic structures. You can exclude
a field manually instead.
I always use an ID-based equality, which avoids this sort of problems and many others (and introduces some other problems, I can better live with).
Add @EqualsAndHashCode(exclude="book")
to BookDetail
, or alike. For other possibilities, look here.
“ID-based equality” means just @EqualsAndHashCode(of="id")
. It just says, two objects are equals if they have the same ID. This can be a problem with newly created objects (having no ID yet), etc. It avoids problems with objects changing their state (as the ID never changes, at least for persisted objects) and so changing their hashCode
(such a change corrupts any HashMap/Set).
In your case: bookDetail
is not something saying “this is a different book”, so I’d exclude it. This is enough to break the cycle.
Update
As of now, the preferred way is
@EqualsAndHashCode.Exclude private BookDetail bookDetail;