JPA多对多关系配置及JSON序列化异常排查求助
问题
正在给Customer与Category实体配置多对多关联,通过中间表CustomerCategory实现,但执行JPQL查询SELECT c FROM customer c LEFT JOIN FETCH c.customerCategories时,出现JSON序列化错误:
JSON Binding serialization error jakarta.json.bind.JsonbException: Unable to serialize property 'customerCategories' from nl.project.entities.Customer
相关代码
Category 实体
package nl.project.entities; import jakarta.persistence.*; import java.math.BigDecimal; import java.util.List; @Entity(name = "category") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String name; @Column private BigDecimal price; @Column private int content; @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) private List<CustomerCategory> customerCategories; // Getters and Setters // Generated toString() method }
Customer 实体
package nl.project.entities; import jakarta.persistence.*; import java.util.List; @Entity(name = "customer") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String email; @Column private String password; @OneToMany(mappedBy = "customer", fetch = FetchType.LAZY) private List<CustomerCategory> customerCategories; // Getters and Setters // Generated toString() method }
CustomerCategory 中间表实体
package nl.project.entities; import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDate; @Entity(name = "customer_category") public class CustomerCategory { @EmbeddedId private CustomerCategoryKey id; @ManyToOne @MapsId("customerId") @JoinColumn(name = "customer_id") private Customer customer; @ManyToOne @MapsId("categoryId") @JoinColumn(name = "category_id") private Category category; @Column private BigDecimal price; @Column(name = "start_date", columnDefinition = "DATE") private LocalDate startDate; // Getters and Setters }
CustomerCategoryKey 复合主键
package nl.project.entities; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; @Embeddable public class CustomerCategoryKey implements Serializable { @Column(name = "customer_id") private Long customerId; @Column(name = "category_id") private Long categoryId; // Getters and Setters @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomerCategoryKey that = (CustomerCategoryKey) o; return Objects.equals(customerId, that.customerId) && Objects.equals(categoryId, that.categoryId); } @Override public int hashCode() { return Objects.hash(customerId, categoryId); } }
DebugResource 接口
package nl.project; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.TypedQuery; import jakarta.persistence.metamodel.EntityType; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import nl.project.entities.Category; import nl.project.entities.Customer; import java.util.List; @Path("/debug") public class DebugResource { @PersistenceContext(unitName = "InMemoryPU") private EntityManager em; @GET @Path("/customers") @Produces(MediaType.APPLICATION_JSON) public List<Customer> customers() { TypedQuery<Customer> query = em.createQuery("SELECT c FROM customer c LEFT JOIN FETCH c.customerCategories", Customer.class); return query.getResultList(); } }
解决方案
这个错误是循环引用导致的JSON序列化死循环:Customer包含CustomerCategory列表,而CustomerCategory又持有Customer引用,序列化时Jsonb会无限递归遍历这些关联对象,最终抛出异常。可以通过以下几种方式解决:
方法一:添加序列化忽略注解
在循环引用的字段上添加@JsonbTransient注解,告诉Jsonb序列化时忽略该字段:
- 若需要在返回的Customer中保留CustomerCategory数据,就在
CustomerCategory类的关联字段上添加:
@ManyToOne @MapsId("customerId") @JoinColumn(name = "customer_id") @JsonbTransient private Customer customer; @ManyToOne @MapsId("categoryId") @JoinColumn(name = "category_id") @JsonbTransient private Category category;
- 若不需要返回CustomerCategory数据,就在
Customer类的关联字段上添加:
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY) @JsonbTransient private List<CustomerCategory> customerCategories;
方法二:使用DTO(数据传输对象)
不直接返回JPA实体类,而是创建只包含对外暴露字段的DTO,避免循环引用:
public class CustomerDTO { private Long id; private String email; private List<CategoryDTO> categories; // 构造方法、getters、setters } public class CategoryDTO { private Long id; private String name; private BigDecimal price; // 构造方法、getters、setters }
然后在接口中完成实体到DTO的转换:
@GET @Path("/customers") @Produces(MediaType.APPLICATION_JSON) public List<CustomerDTO> customers() { TypedQuery<Customer> query = em.createQuery("SELECT c FROM customer c LEFT JOIN FETCH c.customerCategories", Customer.class); List<Customer> customers = query.getResultList(); return customers.stream().map(customer -> { CustomerDTO dto = new CustomerDTO(); dto.setId(customer.getId()); dto.setEmail(customer.getEmail()); List<CategoryDTO> categoryDTOs = customer.getCustomerCategories().stream() .map(cc -> { CategoryDTO catDto = new CategoryDTO(); catDto.setId(cc.getCategory().getId()); catDto.setName(cc.getCategory().getName()); catDto.setPrice(cc.getPrice()); return catDto; }) .toList(); dto.setCategories(categoryDTOs); return dto; }).toList(); }
方法三:修改toString方法
如果实体的toString方法包含关联对象,可能会间接触发循环引用,建议修改toString方法,只保留基本类型字段,不要包含关联对象列表。
内容的提问来源于stack exchange,提问作者Tjeu Foolen
相关产品推荐
相关产品推荐

