那晚,我在Spring Boot项目中进行了一次再普通不过的JPA查询:
userRepository.findAll();
当我刷新接口准备查看返回结果时,浏览器却直接崩溃——控制台抛出 StackOverflowError 异常,提示 JSON无限递归(Infinite recursion)。

问题的根源正在于实体类的设计:User 中引用了 Department,而 Department 中又引用了 User 的集合。JPA在内存中维护这种双向关联本是合理的,但JSON序列化工具(如Jackson)会尝试展开所有关联属性,于是陷入循环:
User → Department → users(List<User>)→ 每个User又指向Department → …… 如此循环,直到栈溢出。
解决方案:使用 @JsonManagedReference 与 @JsonBackReference
在Jackson中,解决此问题的标准方案是使用双向关联注解,明确标记关系的“主从”方向,避免循环序列化:
- @JsonManagedReference:标记在关系的“主控方”,序列化时正常输出。
- JsonBackReference:标记在关系的“被控方”,序列化时忽略此字段。
我在代码中做出如下调整:
在Department实体中:
@OneToMany(mappedBy = "department")
@JsonManagedReference // 主控方:部门可序列化其用户列表
private List<User> users;
在User实体中:
@ManyToOne
@JoinColumn(name = "department_id")
@JsonBackReference // 被控方:避免序列化时循环引用
private Department department;

重启项目后,接口返回清晰的JSON结构,递归问题得以解决。
注意:FastJSON需要使用 @JSONField(serialize = false)
当项目中使用的是Alibaba的FastJSON而非Jackson时,上述注解将失效。此时应使用FastJSON提供的注解:
@ManyToOne
@JoinColumn(name = "department_id")
@JSONField(serialize = false) // FastJSON中忽略序列化
private Department department;
问题本质:JPA双向关联 vs. JSON树形序列化
这一问题的本质在于JPA的双向对象模型与JSON的树形序列化方式之间的不匹配:
- JPA支持对象间的双向导航,形成内存中的环状引用;
- JSON序列化器默认会递归展开所有属性,遇到环状引用时无法终止。
注解的作用正是在序列化阶段“剪断”循环引用的链条,使对象图能够被安全地展开为树形结构。

实战示例与常见误区
正确查询示例:
// 查询部门及其所有用户
Department dept = departmentRepository.findByName("研发部");
List<User> users = dept.getUsers(); // 依赖注解避免递归
常见误区提醒:
- 注解需配对使用,仅在一方添加可能导致数据不完整;
- 明确项目使用的JSON库(Jackson / FastJSON),选择对应注解;
- 勿将 @JsonIgnore / @JSONField(serialize = false) 与JPA的 @Transient 混淆,后者会导致字段不被持久化。
总结
|
场景 |
解决方案 |
说明 |
|
使用 Jackson |
@JsonManagedReference & @JsonBackReference |
提议用于Spring Boot默认项目 |
|
使用 FastJSON |
@JSONField(serialize = false) |
用于明确使用FastJSON的项目 |
|
根本预防 |
设计DTO取代直接返回实体 |
彻底避免实体序列化问题 |
核心启示:理解JPA关联关系与序列化机制之间的交互,是构建稳定应用的关键。恰当的注解使用如同为对象关系设立“边界”,避免无限递归,保证系统稳定运行。



















暂无评论内容