双向OneToOne关系下的JSON序列化递归问题
解决双向OneToOne关系JSON序列化递归问题
我有两个Java实体Sinistre和RapportTechnique,二者为双向OneToOne关系。在生成JSON文件发送至前端时,出现了不必要的递归问题。
试过@JsonIgnore注解,但它会在JSON中完全忽略该关系;后来发现@JsonManagedReference和@JsonBackReference注解可解决递归问题,但仅支持单向。如何在保持双向关系的同时,生成Sinistre实体的JSON时避免递归?
Sinistre类代码:
@Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "sinistre") public class Sinistre extends Audit { @Column private String code; @Column private LocalDateTime dateIncident; @Column private double montantIndem; @ManyToOne @JoinColumn(name = "structure_id", referencedColumnName = "id") private Structure structure; @OneToOne(mappedBy = "sinistre") @JsonManagedReference private RapportTechnique rapportTechnique; }
RapportTechnique类代码:
@Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "rapportTech") public class RapportTechnique extends Audit { @Column private String reference; @Column private String nature_Sinistre; @Column private String causes_Sinistre; @Column private String date_intervention; @OneToOne @JoinColumn(name = "sinistre_id") @JsonBackReference private Sinistre sinistre; }
解决方案
1. 使用@JsonIdentityInfo注解
这是Jackson提供的注解,支持双向序列化时避免递归。原理是第一次序列化对象时输出完整内容,后续出现时用对象的唯一ID引用代替,从而打破循环。
只需在两个实体类上添加该注解(假设Audit父类包含id字段作为唯一标识):
@Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "sinistre") @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Sinistre extends Audit { // 原有代码不变 } @Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "rapportTech") @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class RapportTechnique extends Audit { // 原有代码不变 }
2. 使用@JsonView注解实现按需序列化
通过定义视图接口,标记需要在不同场景下返回的字段,实现灵活控制序列化内容,避免递归。
步骤如下:
- 定义视图接口:
public class Views { // 定义公共视图,用于返回Sinistre的完整信息(不含递归字段) public static class Public {} }
- 在实体类中标记字段所属视图:
@Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "sinistre") public class Sinistre extends Audit { // 原有字段不变 @OneToOne(mappedBy = "sinistre") @JsonView(Views.Public.class) private RapportTechnique rapportTechnique; } @Data @Entity @EqualsAndHashCode(callSuper = false) @Table(name = "rapportTech") public class RapportTechnique extends Audit { // 原有字段不变 // 不标记Sinistre字段的视图,这样序列化Sinistre时不会递归返回Sinistre对象 @OneToOne @JoinColumn(name = "sinistre_id") private Sinistre sinistre; }
- 在Controller中指定返回视图:
@GetMapping("/sinistres/{id}") @JsonView(Views.Public.class) public ResponseEntity<Sinistre> getSinistre(@PathVariable Long id) { Sinistre sinistre = sinistreRepository.findById(id).orElseThrow(() -> new RuntimeException("Sinistre not found")); return ResponseEntity.ok(sinistre); }
3. 使用DTO(数据传输对象)
最规范的做法是创建专门的DTO类,只包含需要返回前端的字段,将实体对象转换为DTO后再序列化。这种方式完全控制返回内容,从根源避免递归问题。
示例DTO类:
// SinistreDTO public class SinistreDTO { private Long id; private String code; private LocalDateTime dateIncident; private double montantIndem; private Structure structure; private RapportTechniqueDTO rapportTechnique; // 构造方法、getter/setter,或使用MapStruct等工具自动转换实体与DTO } // RapportTechniqueDTO public class RapportTechniqueDTO { private Long id; private String reference; private String nature_Sinistre; private String causes_Sinistre; private String date_intervention; // 仅返回关联的Sinistre ID,而非完整对象 private Long sinistreId; // 构造方法、getter/setter }
在Controller中转换并返回DTO:
@GetMapping("/sinistres/{id}") public ResponseEntity<SinistreDTO> getSinistre(@PathVariable Long id) { Sinistre sinistre = sinistreRepository.findById(id).orElseThrow(() -> new RuntimeException("Sinistre not found")); // 手动转换或用MapStruct生成转换代码 SinistreDTO dto = new SinistreDTO(); dto.setId(sinistre.getId()); dto.setCode(sinistre.getCode()); dto.setDateIncident(sinistre.getDateIncident()); dto.setMontantIndem(sinistre.getMontantIndem()); dto.setStructure(sinistre.getStructure()); if (sinistre.getRapportTechnique() != null) { RapportTechniqueDTO rtDto = new RapportTechniqueDTO(); rtDto.setId(sinistre.getRapportTechnique().getId()); rtDto.setReference(sinistre.getRapportTechnique().getReference()); rtDto.setNature_Sinistre(sinistre.getRapportTechnique().getNature_Sinistre()); rtDto.setCauses_Sinistre(sinistre.getRapportTechnique().getCauses_Sinistre()); rtDto.setDate_intervention(sinistre.getRapportTechnique().getDate_intervention()); rtDto.setSinistreId(sinistre.getId()); dto.setRapportTechnique(rtDto); } return ResponseEntity.ok(dto); }
内容的提问来源于stack exchange,提问作者Zade Abderrahmane
相关产品推荐
相关产品推荐

