fixed-bug:JPA 关联关系的对象序列化循环引用问题

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、JPA-的关联关系映射:
  • [二、JSON 序列化问题:](#二、JSON 序列化问题:)
    • [2.1 通过Jpa 获取到的对象会出现栈溢出现象](#2.1 通过Jpa 获取到的对象会出现栈溢出现象)
    • [2.2 JSON.toJSONString() 会将 关联的对象一起输出](#2.2 JSON.toJSONString() 会将 关联的对象一起输出)
    • [2.3 对象中的getXXX 方法也会输出,即使没有XXX 属性](#2.3 对象中的getXXX 方法也会输出,即使没有XXX 属性)
  • 三、处理方式:
    • [3.1 对于不必要的字段不进行序列化:](#3.1 对于不必要的字段不进行序列化:)
  • 总结

前言

在使用 FastJSON 序列化包含 @OneToMany、@ManyToMany 等 JPA 关联关系的对象时,出现循环嵌套问题会导致栈溢出或 JSON 结构无限嵌套。


一、JPA-的关联关系映射:

JPA(Java Persistence API)的关联关系映射用于描述实体之间的关系(如一对一、一对多、多对一、多对多),通过注解实现对象模型与数据库表结构的映射。

二、JSON 序列化问题:

2.1 通过Jpa 获取到的对象会出现栈溢出现象

在使用 JpaRepository 获取包含关联关系的对象时出现栈溢出(StackOverflowError),双向关联导致的循环引用引起的。当两个实体相互引用(如 @OneToMany 和 @ManyToOne 双向关联),在序列化或打印对象时会触发无限递归,最终导致栈溢出。

java 复制代码
// 订单实体
@Entity
public class Order {
    @Id
    private Long id;
    
    @ManyToOne
    private User user; // 引用用户(多对一)
    
    // getter/setter
}

// 用户实体
@Entity
public class User {
    @Id
    private Long id;
    
    @OneToMany(mappedBy = "user")
    private List<Order> orders; // 引用订单列表(一对多)
    
    // getter/setter
}

当执行 userRepository.findById(1L) 获取用户对象时:

  • JPA 会加载 User 对象及其关联的 orders 列表(若为即时加载)。
  • 每个 Order 对象又会引用回 User 对象。
  • 当尝试序列化(如返回给前端)或打印对象时,会出现 User → orders → Order → user → User → ... 的无限递归,最终导致栈溢出。

2.2 JSON.toJSONString() 会将 关联的对象一起输出

FastJSON 的 JSON.toJSONString() 方法在默认情况下会递归序列化对象的所有关联属性,包括 JPA 中通过 @OneToMany、@ManyToOne 等注解定义的关联对象。如果存在双向关联(如 A 包含 B,B 又包含 A),这种递归序列化会导致无限循环,最终抛出 StackOverflowError(栈溢出);

但是 FastJSON 默认会检测循环引用,并在 JSON 中用 KaTeX parse error: Expected '}', got 'EOF' at end of input: ref 标记(如 {"ref":"$.orders[0].user"}),避免无限递归。

2.3 对象中的getXXX 方法也会输出,即使没有XXX 属性

在 FastJSON 中,JSON.toJSONString() 序列化对象时,默认会优先通过 getter 方法(getXXX())获取属性值,而不仅仅依赖对象的字段(成员变量)。这就是为什么即使实体类中没有 XXX 字段,但只要存在 getXXX() 方法,序列化结果中就会出现 xxx 键(方法名去除 get 后首字母小写)。

原因解析

FastJSON 的序列化逻辑遵循 "** getter 优先 **" 原则,具体规则:

遍历类中所有的 getXXX() 方法(方法名以 get 开头,且无参数、返回值不为 void)。

对方法名进行转换:去除 get 后,将首字母小写,作为 JSON 中的键(例如 getFullName() → 键为 fullName)。

调用该 getter 方法获取值,并写入 JSON 字符串。

字段(成员变量)是否存在不影响,只要 getter 方法存在,就会被序列化

三、处理方式:

3.1 对于不必要的字段不进行序列化:

使用 @JSONField(serialize = false) 注解标记 getter 方法,或者标准字段属性;

注意事项:

  • 仅对 FastJSON 生效:该注解是 FastJSON 的专属注解,若项目中使用其他序列化工具(如 Jackson),需要改用对应工具的注解(如 Jackson 的 @JsonIgnore)。
  • 反序列化不受影响:serialize = false 仅控制 "对象→JSON" 的序列化过程,不影响 "JSON→对象" 的反序列化(即前端传入该字段时,仍可正常解析到对象中)。若需同时排除反序列化,可添加 deserialize = false

总结

在使用 FastJSON 序列化包含 @OneToMany、@ManyToMany 等 JPA 关联关系的对象时,核心问题是避免循环引用(如 A 包含 B,B 又包含 A)导致的栈溢出或 JSON 结构无限嵌套。

相关推荐
雁于飞4 分钟前
分布式基础
java·spring boot·分布式·spring·wpf·cloud native
东南门吹雪3 小时前
Spring的Bean相关
java·spring·bean·aop
带刺的坐椅3 小时前
AspectJ、Spring AOP 与 Solon AOP:Java AOP 框架的三剑客
java·spring·solon·aop·aspectj
百***81273 小时前
使用 Logback 的最佳实践:`logback.xml` 与 `logback-spring.xml` 的区别与用法
xml·spring·logback
用户49187503811884 小时前
hibernate数据库连接密码解析问题
后端·spring
mqiqe4 小时前
【Spring AI MCP】六、SpringAI MCP 服务端 STDIO & SSE
java·人工智能·spring
草原印象5 小时前
Spring Cloud、Spring Cloud Alibaba微服务实战
spring·spring cloud·微服务
爱吃烤鸡翅的酸菜鱼7 小时前
Spring Boot 实现 WebSocket 实时通信:从原理到生产级实战
java·开发语言·spring boot·后端·websocket·spring
daidaidaiyu16 小时前
Spring IOC 源码学习一 基本姿势
java·spring
间彧16 小时前
Spring AOT + GraalVM Native Image:云原生Java的效能引擎
spring