You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双向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注解实现按需序列化

通过定义视图接口,标记需要在不同场景下返回的字段,实现灵活控制序列化内容,避免递归。

步骤如下:

  1. 定义视图接口:
public class Views {
    // 定义公共视图,用于返回Sinistre的完整信息(不含递归字段)
    public static class Public {}
}
  1. 在实体类中标记字段所属视图:
@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;
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 15:33:17