三张关联表场景下如何使用@JsonManagedReference等注解实现按需序列化输出
问题场景
实体关联关系
- Bill 与 Vendor 为多对一双向关联关系
- Vendor 与 Bill、VendorBank 均为一对多关联关系
- VendorBank 与 Vendor 为多对一单向关联关系
实体代码示例
Bills 类
@Entity @Getter @Setter @ToString @NoArgsConstructor @Table(name="bill_details") public class Bills { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="bill_id") private int bill_id; @Column(name="bill_no") private String billno; @ManyToOne(cascade = {CascadeType.MERGE}) @JoinColumn(name="b_vendor_id") private Vendors vendors; /* Args contructor code here */ @JsonManagedReference public Vendors getVendors() { return vendors; } }
VendorBank 类
public class VendorBank { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="v_bank_id") private int vendorBankId; @Column(name="v_acc_no") private String accountNumber; @Column(name="v_vendor_id") private int vendor_id; /*Both constructor code here*/ }
Vendors 类
/*Lombok code here*/ public class Vendors { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="vendor_id") private int vendor_id; @Column(name="vendor_name") private String vendor_name; @OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL) @JoinColumn(name="v_vendor_id") private List<VendorBank> vendorBank; @OneToMany(mappedBy = "vendors",cascade = {CascadeType.DETACH,CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH}) private List<Bills> bills; @JsonBackReference public List<Bills> getBills() { return bills; } //@JsonManagedReference() // @JsonIgnore public List<VendorBank> getVendorBank() { return vendorBank; } }
接口返回现状
调用Bill接口返回示例:
{ "bill_id": 102, "billno": "B-858", "vendors": { "vendor_id": 3, "vendor_name": "ABC Company", "vendorBank": [ { "vendorBankId": 14, "accountNumber": "502998745002", "vendor_id": 3 } ] } }
现存问题
- 调用vendor接口时,可以正常返回vendor + vendor bank的预期数据;
- 调用Bill接口时,会返回Bill + vendor + vendorbank的组合数据,不需要返回vendorbank字段。如果在vendorbank字段上添加
@JsonIgnore注解,Bill接口返回符合预期,但vendor接口就无法返回vendorbank数据。
解决方案
- 方案1:使用Jackson的
@JsonView注解做序列化分组
该方案不需要修改现有业务逻辑,仅通过注解控制不同接口的序列化范围:
- 定义视图分组接口:
public class View { // 账单接口序列化分组 public interface BillView {} // 供应商接口序列化分组,继承BillView可复用账单视图的字段配置 public interface VendorView extends BillView {} }
- 给实体字段标记对应视图:
- Bills类的所有字段添加
@JsonView(View.BillView.class)注解 - Vendors类的
vendor_id、vendor_name字段添加@JsonView(View.BillView.class)注解,vendorBank字段添加@JsonView(View.VendorView.class)注解
- 接口方法上指定序列化视图:
- Bill接口的响应方法上添加
@JsonView(View.BillView.class),序列化时会自动过滤vendor下的vendorBank字段 - Vendor接口的响应方法上添加
@JsonView(View.VendorView.class),可正常返回vendorBank字段
- 方案2:DTO分层隔离
为不同接口单独定义专属的传输对象,完全按需控制返回字段,和实体层解耦:
- 定义BillDTO、VendorSimpleDTO给账单接口使用,VendorSimpleDTO仅保留vendor_id、vendor_name两个字段
- 定义VendorDTO、VendorBankDTO给供应商接口使用,包含需要返回的全量字段
- 接口查询到实体后,通过MapStruct/BeanUtils转换为对应DTO再返回即可
- 方案3:利用懒加载特性过滤字段
你已经给vendorBank配置了FetchType.LAZY懒加载策略,只要在配置文件中添加spring.jackson.serialization.fail-on-lazy-loading=false(SpringBoot项目),Bill接口查询时只要不主动调用getVendorBank()加载关联数据,Jackson序列化时就会自动忽略未加载的懒加载字段。该方案仅适用于Bill查询链路中不会主动加载vendorBank的场景。
内容的提问来源于stack exchange,提问作者Nadim
相关产品推荐
相关产品推荐

