Hibernate @ManyToMany双向关联用Set持久化时StackOverflowError问题
异常根因分析
这个栈溢出完全是Lombok @Data注解 + 双向关联 + HashSet的去重校验逻辑三者共同触发的循环调用问题,具体逻辑如下:
1. 前置前提
Lombok的@Data注解会默认给实体类生成包含所有字段的equals、hashCode、toString方法,你这两个实体的关联字段Set<Trigger> trigger和Set<Category> categories都会被包含在这三个方法的计算逻辑里,形成双向依赖。
2. 为什么Set会触发栈溢出
Collectors.toSet()默认返回的是HashSet实现,HashSet添加元素时会执行两个操作:
- 调用元素的
hashCode方法计算哈希值 - 用
equals方法判断集合中是否已存在相同元素
触发栈溢出的完整流程:
- 首次持久化Trigger时,从数据库查出来的Category实例的
trigger集合是空的(还没有关联任何Trigger),此时把Category加入Trigger的HashSet时,计算hashCode不会触发循环,能正常执行。持久化完成后Hibernate会自动维护双向关联,把新建的Trigger加入到对应Category的trigger集合中。 - 第二次持久化新Trigger时,查出来的Category实例的
trigger集合已经存储了第一次创建的Trigger实例。此时把Category加入新Trigger的HashSet时,计算Category的hashCode会遍历它的trigger集合,调用每个Trigger的hashCode;而Trigger的hashCode又会遍历它的categories集合调用每个Category的hashCode,直接形成无限递归,最终抛出StackOverflowError。
3. 为什么换成List就正常
List接口的实现类(比如ArrayList)添加元素时不会做去重校验,不会主动调用元素的hashCode和equals方法,也就不会触发上述的递归循环,所以能正常运行。
修复建议
- 不要给JPA实体类使用
@Data注解,改用@Getter+@Setter组合,自定义equals和hashCode方法,仅基于主键或者业务唯一键(比如Category的name、id)生成,不要包含关联属性。 - 移除Category侧
@ManyToMany注解的cascade = CascadeType.ALL配置,你的场景中Category是预定义数据,配置全级联会导致删除Trigger时连带删除关联的Category,不符合业务逻辑。
内容的提问来源于stack exchange,提问作者Dima Kovbasa
相关产品推荐
相关产品推荐

