Spring Boot双向关联引发hashCode/toString递归问题求最优解
解决Spring Boot实体双向关联的hashCode/equals/toString无限递归问题
核心思路
双向关联触发递归的根源是:A类的hashCode/equals/toString调用B类的对应方法,B类又反过来调用A类的方法,形成循环。解决关键是避免直接引用关联对象的整体,改用其唯一标识(主键ID)。
方法1:仅用自身主键实现hashCode/equals,toString输出关联对象ID
实体对应数据库中的唯一记录,主键是天然的唯一标识,用它判断相等、计算哈希既符合逻辑,又彻底避免递归。
示例代码(StundenplanEintrag类):
@Override public int hashCode() { return Objects.hash(id); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StundenplanEintrag that = (StundenplanEintrag) o; return Objects.equals(id, that.id); } @Override public String toString() { return "StundenplanEintrag{" + "id=" + id + ", bezeichnung='" + bezeichnung + '\'' + ", klasseId=" + (klasse != null ? klasse.getId() : null) + ", fachId=" + (fach != null ? fach.getId() : null) + '}'; }
对应Klasse类的toString处理:
@Override public String toString() { List<Long> eintragIds = stundenplanEintraege.stream() .map(StundenplanEintrag::getId) .collect(Collectors.toList()); return "Klasse{" + "id=" + id + ", name='" + name + '\'' + ", stundenplanEintragIds=" + eintragIds + '}'; }
这个方法简单直接,单元测试中完全可以通过主键判断对象是否对应同一条数据,不会影响测试有效性。
方法2:hashCode/equals中包含关联对象的主键(而非对象本身)
如果希望hashCode/equals包含更多业务属性,同时避免递归,可以在计算时用关联对象的ID代替整个对象。
示例代码:
@Override public int hashCode() { return Objects.hash(id, bezeichnung, (klasse != null ? klasse.getId() : null), (fach != null ? fach.getId() : null)); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StundenplanEintrag that = (StundenplanEintrag) o; return Objects.equals(id, that.id) && Objects.equals(bezeichnung, that.bezeichnung) && Objects.equals(klasse != null ? klasse.getId() : null, that.klasse != null ? that.klasse.getId() : null) && Objects.equals(fach != null ? fach.getId() : null, that.fach != null ? that.fach.getId() : null); }
这种方式既保留了关联关系的信息,又不会触发对方的hashCode/equals调用,避免递归。
方法3:用Lombok简化配置
如果项目使用Lombok,可以通过注解参数直接指定要包含的属性(或排除关联属性,手动加入关联ID):
示例代码:
@Entity // 只包含自身主键和业务属性,关联关系用ID代替 @EqualsAndHashCode(of = {"id", "bezeichnung"}) @ToString(of = {"id", "bezeichnung"}) public class StundenplanEintrag { @Id private Long id; private String bezeichnung; @ManyToOne private Klasse klasse; @ManyToOne private Fach fach; // 手动给toString添加关联ID字段 @ToString.Include(name = "klasseId") public Long getKlasseId() { return klasse != null ? klasse.getId() : null; } @ToString.Include(name = "fachId") public Long getFachId() { return fach != null ? fach.getId() : null; } }
Klasse类同理,用@EqualsAndHashCode(of = "id"),toString中通过@ToString.Include处理关联的StundenplanEintrag ID列表。
内容的提问来源于stack exchange,提问作者Gabriel
相关产品推荐
相关产品推荐

