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

三张关联表场景下如何使用@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
            }
        ]
    }
}

现存问题

  1. 调用vendor接口时,可以正常返回vendor + vendor bank的预期数据;
  2. 调用Bill接口时,会返回Bill + vendor + vendorbank的组合数据,不需要返回vendorbank字段。如果在vendorbank字段上添加@JsonIgnore注解,Bill接口返回符合预期,但vendor接口就无法返回vendorbank数据。

解决方案
  • 方案1:使用Jackson的@JsonView注解做序列化分组
    该方案不需要修改现有业务逻辑,仅通过注解控制不同接口的序列化范围:
  1. 定义视图分组接口:
public class View {
    // 账单接口序列化分组
    public interface BillView {}
    // 供应商接口序列化分组,继承BillView可复用账单视图的字段配置
    public interface VendorView extends BillView {}
}
  1. 给实体字段标记对应视图:
  • Bills类的所有字段添加@JsonView(View.BillView.class)注解
  • Vendors类的vendor_id、vendor_name字段添加@JsonView(View.BillView.class)注解,vendorBank字段添加@JsonView(View.VendorView.class)注解
  1. 接口方法上指定序列化视图:
  • Bill接口的响应方法上添加@JsonView(View.BillView.class),序列化时会自动过滤vendor下的vendorBank字段
  • Vendor接口的响应方法上添加@JsonView(View.VendorView.class),可正常返回vendorBank字段

  • 方案2:DTO分层隔离
    为不同接口单独定义专属的传输对象,完全按需控制返回字段,和实体层解耦:
  1. 定义BillDTO、VendorSimpleDTO给账单接口使用,VendorSimpleDTO仅保留vendor_id、vendor_name两个字段
  2. 定义VendorDTO、VendorBankDTO给供应商接口使用,包含需要返回的全量字段
  3. 接口查询到实体后,通过MapStruct/BeanUtils转换为对应DTO再返回即可

  • 方案3:利用懒加载特性过滤字段
    你已经给vendorBank配置了FetchType.LAZY懒加载策略,只要在配置文件中添加spring.jackson.serialization.fail-on-lazy-loading=false(SpringBoot项目),Bill接口查询时只要不主动调用getVendorBank()加载关联数据,Jackson序列化时就会自动忽略未加载的懒加载字段。该方案仅适用于Bill查询链路中不会主动加载vendorBank的场景。

内容的提问来源于stack exchange,提问作者Nadim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:15:02