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映射配置:
- 修正
Customer类的一对多映射:
// 指定关联关系由Invoice类的customer属性维护,匹配已配置的外键 @OneToMany(mappedBy = "customer") private Set<Invoice> invoices;
- 在
Invoice类的客户属性上添加注解,阻止序列化反向关联:
@ManyToOne @JoinColumn(name = "customer_id") @JsonIgnore private Customer customer;
适用场景:仅需要查询客户列表、不需要在发票数据中返回关联客户信息的简单场景,缺点是序列化Invoice时无法带出客户信息。
方案2:Jackson专用双向关联注解(保留双向序列化能力)
使用Jackson提供的@JsonManagedReference/@JsonBackReference注解对处理双向关联,不需要完全屏蔽某一侧的序列化:
- 同样先修正
Customer类的@OneToMany映射配置,在父级集合属性上加正向序列化注解:
@OneToMany(mappedBy = "customer") @JsonManagedReference // 正向端:该属性会被正常序列化 private Set<Invoice> invoices;
- 在
Invoice类的客户属性上加反向注解,阻断循环:
@ManyToOne @JoinColumn(name = "customer_id") @JsonBackReference // 反向端:序列化时跳过该属性,避免循环 private Customer customer;
适用场景:需要返回客户下的发票列表,不需要在发票对象中嵌套返回客户信息的场景。
方案3:DTO投影返回(生产环境推荐)
不要直接将JPA实体类作为接口返回值,根据业务需求定义专用的数据传输对象(DTO),仅保留接口需要返回的字段,从根源上规避循环引用、懒加载、字段泄露等问题:
- 定义对应接口返回结构的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; // 不需要返回关联客户就不定义对应字段,结构完全可控 }
- 在Service层查询到
Customer实体集合后,通过手动赋值、BeanUtils或MapStruct等工具将实体转换为VO对象再返回。
该方案是工业界通用的最佳实践,完全解耦数据库实体和对外接口结构,不会出现序列化相关问题,也能避免敏感字段意外泄露。
兜底配置(临时解决懒加载问题)
如果需要临时保留实体直接返回的逻辑,可以在application.yml中添加配置,避免懒加载会话关闭问题:
spring: jpa: open-in-view: true hibernate: enable_lazy_load_no_trans: true
注意:该配置会导致数据库会话生命周期变长,高并发场景下会明显影响性能,仅适合临时调试使用,不推荐生产环境长期开启。
内容的提问来源于stack exchange,提问作者renad sahal

