JPA双向关联中@JsonIgnoreProperties无效,如何解决JSON无限递归?
解决JPA双向关联JSON序列化无限递归问题(无需DTO/映射工具)
以下是几种无需创建DTO或使用映射工具的解决方案,直接通过Jackson注解和Spring特性实现期望的JSON结构:
方案1:使用@JsonIdentityInfo识别对象唯一标识
该注解让Jackson在序列化时记录对象的唯一ID,后续遇到同一对象时仅输出ID,避免循环嵌套。
在两个实体类上添加注解:
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.*; import java.util.List; @Entity @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Departamento { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @OneToMany(mappedBy = "departamento") private List<Funcionario> funcionarios; // getter、setter省略 } @Entity @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Funcionario { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @ManyToOne private Departamento departamento; // getter、setter省略 }
效果:第一次序列化对象时输出完整字段,后续重复出现的对象仅显示ID,既保留关联关系又避免递归。
方案2:使用@JsonView控制不同场景的序列化字段
定义视图接口区分不同查询场景,在实体字段上标注允许序列化的视图,最后在Controller中指定使用的视图。
- 定义视图接口
public class Views { public interface FuncionarioView {} public interface DepartamentoView {} }
- 实体类标注视图
import com.fasterxml.jackson.annotation.JsonView; import jakarta.persistence.*; import java.util.List; @Entity public class Departamento { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonView({Views.FuncionarioView.class, Views.DepartamentoView.class}) private Long id; @JsonView({Views.FuncionarioView.class, Views.DepartamentoView.class}) private String nome; @OneToMany(mappedBy = "departamento") @JsonView(Views.DepartamentoView.class) private List<Funcionario> funcionarios; // getter、setter省略 } @Entity public class Funcionario { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonView({Views.FuncionarioView.class, Views.DepartamentoView.class}) private Long id; @JsonView({Views.FuncionarioView.class, Views.DepartamentoView.class}) private String nome; @ManyToOne @JsonView(Views.FuncionarioView.class) private Departamento departamento; // getter、setter省略 }
- Controller指定视图
import com.fasterxml.jackson.annotation.JsonView; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/funcionarios") public class FuncionarioController { private final FuncionarioRepository funcionarioRepo; public FuncionarioController(FuncionarioRepository funcionarioRepo) { this.funcionarioRepo = funcionarioRepo; } @GetMapping("/{id}") @JsonView(Views.FuncionarioView.class) public Funcionario getFuncionario(@PathVariable Long id) { return funcionarioRepo.findById(id).orElseThrow(); } } @RestController @RequestMapping("/departamentos") public class DepartamentoController { private final DepartamentoRepository departamentoRepo; public DepartamentoController(DepartamentoRepository departamentoRepo) { this.departamentoRepo = departamentoRepo; } @GetMapping("/{id}") @JsonView(Views.DepartamentoView.class) public Departamento getDepartamento(@PathVariable Long id) { return departamentoRepo.findById(id).orElseThrow(); } }
效果:查询Funcionario时仅返回Departamento的基础字段,查询Departamento时仅返回Funcionario的基础字段,完全避免循环嵌套。
方案3:正确使用@JsonIgnoreProperties双向忽略关联字段
确保在双向关联的两端都忽略对方的关联属性,避免序列化时触发循环。
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import java.util.List; @Entity public class Departamento { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @OneToMany(mappedBy = "departamento") @JsonIgnoreProperties("departamento") private List<Funcionario> funcionarios; // getter、setter省略 } @Entity public class Funcionario { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @ManyToOne @JsonIgnoreProperties("funcionarios") private Departamento departamento; // getter、setter省略 }
效果:序列化Departamento时,Funcionario中的departamento字段会被忽略;序列化Funcionario时,Departamento中的funcionarios字段会被忽略,彻底阻断递归链。
内容的提问来源于stack exchange,提问作者Camila Alves
相关产品推荐
相关产品推荐

