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

Spring Boot JPA一对多关联GET接口报JsonMappingException问题

问题场景

Spring Boot项目中定义了存在一对多关联关系的两个实体模型Customer与Invoice(一个客户可对应多张发票),并在实体类中配置了映射关系:

Customer类代码

@Entity
@Table(name = "customer")
public class Customer {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;
    @Column(name = "serial_number")
    private long serialNumber;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Column(name = "email")
    private String email;
    @Column(name = "mobile_number")
    private String mobileNumber;
    @Column(name = "is_deleted")
    private boolean isDeleted;


    @OneToMany
    private Set <Invoice> invoices;
}

Invoice类代码

@Entity
@Table(name = "invoice")
public class Invoice {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;
    @Column(name = "serial_number")
    private long serialNumber;
    @Column(name = "status")
    private String status;
    @Column(name = "created_date")
    private Timestamp createdDate;
    @Column(name = "is_deleted")
    private boolean isDeleted;
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

编写查询全量客户列表的GET API后接口无法正常运行,返回错误信息:

nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.invoices.model.Customer["invoices"]), path=/customer/viewList}
相关业务代码如下:

Service层实现

public List<Customer> getAllCustomers() {
    List<Customer> customers =  cutomerRepository.findAll();
    return customers;
}

Controller层接口

@GetMapping("/viewList")
public ResponseEntity<List<Customer>> getAllCustomers() {
        List<Customer> customers = new ArrayList<>();
        customers = customerService.getAllCustomers();
        if (customers.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        return new ResponseEntity<>(customers, HttpStatus.OK);
}
异常触发原因

该报错由三个问题共同导致:

  • JPA双向关联配置错误:Customer侧的@OneToMany注解未配置mappedBy属性,JPA会默认创建第三方中间表维护关联关系,但实际Invoice侧已经通过@JoinColumn(name = "customer_id")指定了外键字段,映射逻辑不匹配会导致关联查询SQL执行失败,无法取出invoices集合数据。
  • JSON序列化无限递归:双向关联的两个实体互相持有对方引用,Jackson序列化返回结果时会循环遍历Customer -> Invoice -> Customer -> Invoice的属性链,最终触发序列化失败。
  • 懒加载序列化异常:@OneToMany默认关联加载策略为懒加载(LAZY),当Jackson尝试序列化invoices属性时,若JPA数据库会话已经关闭,会直接抛出无法提取结果集的错误。
可行解决方案

根据业务场景选择对应方案即可:

方案1:@JsonIgnore打破循环(快速修复)

在不需要序列化返回的关联属性上添加@JsonIgnore注解,直接阻断循环引用路径,同时修正JPA映射配置:

  1. 修正Customer类的一对多映射:
// 指定关联关系由Invoice类的customer属性维护,匹配已配置的外键
@OneToMany(mappedBy = "customer")
private Set<Invoice> invoices;
  1. 在Invoice类的客户属性上添加注解,阻止序列化反向关联:
@ManyToOne
@JoinColumn(name = "customer_id")
@JsonIgnore
private Customer customer;

适用场景:仅需要查询客户列表、不需要在发票数据中返回关联客户信息的简单场景,缺点是序列化Invoice时无法带出客户信息。

方案2:Jackson专用双向关联注解(保留双向序列化能力)

使用Jackson提供的@JsonManagedReference/@JsonBackReference注解对处理双向关联,不需要完全屏蔽某一侧的序列化:

  1. 同样先修正Customer类的@OneToMany映射配置,在父级集合属性上加正向序列化注解:
@OneToMany(mappedBy = "customer")
@JsonManagedReference // 正向端:该属性会被正常序列化
private Set<Invoice> invoices;
  1. 在Invoice类的客户属性上加反向注解,阻断循环:
@ManyToOne
@JoinColumn(name = "customer_id")
@JsonBackReference // 反向端:序列化时跳过该属性,避免循环
private Customer customer;

适用场景:需要返回客户下的发票列表,不需要在发票对象中嵌套返回客户信息的场景。

方案3:DTO投影返回(生产环境推荐)

不要直接将JPA实体类作为接口返回值,根据业务需求定义专用的数据传输对象(DTO),仅保留接口需要返回的字段,从根源上规避循环引用、懒加载、字段泄露等问题:

  1. 定义对应接口返回结构的DTO类,例如:
// 客户返回结构DTO
public class CustomerVO {
    private Integer id;
    private Long serialNumber;
    private String firstName;
    private String lastName;
    private String email;
    private String mobileNumber;
    // 如果需要返回发票信息,定义发票对应的VO,不要直接引用实体类
    private List<InvoiceVO> invoices;
    // 省略构造方法、getter、setter
}

// 发票返回结构VO
public class InvoiceVO {
    private Integer id;
    private Long serialNumber;
    private String status;
    private Timestamp createdDate;
    // 不需要返回关联客户就不定义对应字段,结构完全可控
}
  1. 在Service层查询到Customer实体集合后,通过手动赋值、BeanUtils或MapStruct等工具将实体转换为VO对象再返回。
    该方案是工业界通用的最佳实践,完全解耦数据库实体和对外接口结构,不会出现序列化相关问题,也能避免敏感字段意外泄露。

兜底配置(临时解决懒加载问题)

如果需要临时保留实体直接返回的逻辑,可以在application.yml中添加配置,避免懒加载会话关闭问题:

spring:
  jpa:
    open-in-view: true
    hibernate:
      enable_lazy_load_no_trans: true

注意:该配置会导致数据库会话生命周期变长,高并发场景下会明显影响性能,仅适合临时调试使用,不推荐生产环境长期开启。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 06:27:24