Jackson序列化JPA双向关联时如何避免重复字段问题
问题场景
运行环境为Jackson 2.13.0搭配Spring Boot框架,需要对JPA实体ProjectPhase执行序列化操作,实体间存在Project与ProjectPhase的一对多双向关联,当前需解决两个问题:
- 序列化
ProjectPhase时,不需要输出其中嵌套的project关联字段 - Jackson序列化JPA双向关联关系时,需要规避字段重复输出、循环引用问题
相关实体代码
ProjectPhase实体代码:
@Entity @Data @NoArgsConstructor @AllArgsConstructor @Builder(toBuilder = true) @JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use = JsonTypeInfo.Id.NAME) public class ProjectPhase { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id_phase; private String phaseName; private Date dateDebt; private Date dateFin; @OneToMany(mappedBy = "projectPhase") private List<TypePhase> typesPhase; @ManyToOne private Project project; @OneToMany(mappedBy = "projectPhase") private List<Collab_Task_ProjectPhase> collab_task_projectPhases; }
当前ProjectPhase列表序列化输出结果:
[ { "ProjectPhase": { "id_phase": 1, "phaseName": "phase1", "dateDebt": null, "dateFin": null, "typesPhase": [], "project": { "Project": { "id": 1, "actif": true, "dateDebut": "2022-06-03T10:13:02.975+00:00", "dateFin": null, "projectName": "project 1" } }, "collab_task_projectPhases": [] } }, { "ProjectPhase": { "id_phase": 2, "phaseName": "phase2", "dateDebt": null, "dateFin": null, "typesPhase": [], "project": { "Project": { "id": 1, "actif": true, "dateDebut": "2022-06-03T10:13:02.975+00:00", "dateFin": null, "projectName": "project 1" } }, "collab_task_projectPhases": [] } } ]
关联的Project实体代码:
@Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder(toBuilder = true) @JsonRootName(value = "project") @JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT,use = JsonTypeInfo.Id.NAME) public class Project implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String ProjectName; private boolean actif; private Date dateDebut; private Date dateFin; //Status Enum (Dev Liv ...) @OneToMany(mappedBy = "project",cascade = CascadeType.ALL) @JsonIgnore private List<ProjectPhase> projectPhases; }
具体解决方案
问题1:序列化ProjectPhase时排除project字段
直接在ProjectPhase实体的project字段上添加@JsonIgnore注解即可,该注解标记的字段会在序列化、反序列化流程中被完全忽略:
@ManyToOne @JsonIgnore // 新增注解 private Project project;
如果需要保留反序列化时接收project参数的能力、仅在序列化时忽略该字段,使用@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)替代@JsonIgnore即可:
@ManyToOne @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private Project project;
修改后序列化ProjectPhase时,将不再输出嵌套的project字段内容。
问题2:双向关联序列化规避重复字段/循环引用
JPA双向关联场景下,仅在单侧加@JsonIgnore虽然能阻断循环,但会导致被忽略侧的关联字段完全无法序列化,可根据业务场景选择以下两种通用方案:
方案1:使用@JsonManagedReference + @JsonBackReference配对注解
该方案通过明确双向关联的序列化层级阻断循环:
- 在父级实体(持有关联集合的一方,即
Project类的projectPhases字段)上添加@JsonManagedReference,标记为正向引用,序列化时正常输出 - 在子级实体(持有单个关联对象的一方,即
ProjectPhase类的project字段)上添加@JsonBackReference,标记为反向引用,序列化时直接跳过,避免循环
当前Project的projectPhases字段上添加了@JsonIgnore,如果需要序列化Project时带出关联的阶段列表,将该注解替换为@JsonManagedReference即可:
// Project实体对应字段修改 @OneToMany(mappedBy = "project",cascade = CascadeType.ALL) @JsonManagedReference // 替换原有@JsonIgnore private List<ProjectPhase> projectPhases;
// ProjectPhase实体对应字段修改 @ManyToOne @JsonBackReference private Project project;
修改后效果:
- 序列化
Project时,会正常输出下属的projectPhases列表,列表内的ProjectPhase对象不会重复嵌套project字段,同时解决循环递归和字段重复问题 - 序列化
ProjectPhase时,不会输出关联的project字段,直接满足第一个问题的需求
方案2:使用@JsonIdentityInfo实现ID引用
如果业务要求双向都保留关联字段、不希望某一侧关联被完全忽略,可以在两个实体类上添加@JsonIdentityInfo注解,让Jackson遇到重复序列化的对象时,直接输出对象ID而非重复序列化完整内容,从根源避免循环和冗余:
// Project类顶部添加注解 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") // 其余原有注解保持不变 public class Project implements Serializable { // 类内容不变 }
// ProjectPhase类顶部添加注解 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id_phase") // 其余原有注解保持不变 public class ProjectPhase { // 类内容不变 }
修改后效果:
- 第一次序列化某个实体对象时会输出完整字段,后续再引用同一个对象时,仅输出该对象配置的主键(即上面配置的
id/id_phase字段) - 不需要忽略任何关联字段,双向关联关系都会保留,也不会出现无限递归、同一对象重复序列化的问题
提示:如果选择该方案但仍不需要
ProjectPhase序列化时输出project字段,依然可以配合@JsonIgnore或@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)使用。
内容的提问来源于stack exchange,提问作者Mohamedkms

