JPA查询数据库触发java.lang.StackOverflowError栈溢出错误如何解决?
异常根因分析
核心是JPA双向关联实体类的hashCode/equals方法循环调用导致栈溢出,具体触发逻辑:
UserDomain、UserRoleDomain、RoleDomain三者存在双向关联映射:UserDomain持有Set<UserRoleDomain>集合,UserRoleDomain同时持有UserDomain和RoleDomain的引用,RoleDomain也持有反向关联的UserRoleDomain集合。- 实体类的
hashCode方法(大概率是Lombok@Data自动生成,或是手动编写时未排除关联属性)计算时会递归调用关联对象的hashCode:UserRoleDomain计算哈希要调用关联的RoleDomain的哈希方法,RoleDomain计算哈希要调用持有的UserRoleDomain集合的哈希,Set计算哈希会遍历所有元素调用hashCode,形成无限循环调用,最终触发StackOverflowError。 - 本次调用
userService.getById查询用户后,用objectMapper.convertValue将UserDomain转为UserDto时,触发了Hibernate懒加载集合的初始化,Set初始化后要计算哈希值,直接触发了循环调用逻辑。
修复方案
- 方案1:重写关联实体的
hashCode和equals方法,仅用主键/业务唯一字段生成,排除关联属性
如果是用Lombok生成实体方法,给关联属性加上@EqualsAndHashCode.Exclude注解即可,示例:
如果是手动编写@Data @Entity public class RoleDomain { @Id private Long roleId; private String roleName; @OneToMany(mappedBy = "roleDomain") @EqualsAndHashCode.Exclude // 排除关联属性,避免循环调用 private Set<UserRoleDomain> userRoleDomains; }hashCode/equals,仅用实体自身的主键、业务唯一字段参与计算即可,不要引入关联对象。 - 方案2:实体转DTO时避免触发懒加载
将关联集合的FetchType设为LAZY后,转换DTO前手动关闭Hibernate会话,或是用MapStruct等DTO映射框架指定仅映射需要的基础字段,不映射关联的集合属性;也可以在Repository查询时直接用DTO投影返回需要的字段,避免查询不需要的关联对象。 - 方案3:打断双向关联的转换依赖
如果接口不需要返回角色关联数据,UserDto不要定义userRoleDomains对应的属性,ObjectMapper转换时会自动忽略实体中不存在对应DTO字段的属性,不会触发关联集合的加载和哈希计算。
内容的提问来源于stack exchange,提问作者Rohail Iqbal
相关产品推荐
相关产品推荐

