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

Spring Data REST投影解决双向关联JSON无限递归问题求助

解决Spring Data REST中双向关联导致的JSON无限递归问题

这个问题我太熟了!双向关联的实体在JSON序列化时简直是踩坑重灾区——你的User里挂着Company集合,每个Company又引用回对应的User,Jackson序列化的时候就会来回嵌套,直到栈溢出抛出Infinite recursion (StackOverflowError)。既然你指定要用Projection解决,这绝对是Spring Data REST里最优雅的方案之一,不用改动实体本身的映射关系,直接通过投影精准控制返回的字段。

先假设你的实体结构大概是这样的(方便后续举例):

// User 实体
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Company> companies = new ArrayList<>();
    
    // getter、setter 省略
}

// Company 实体
@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
    
    // getter、setter 省略
}

下面是具体的Projection解决方案步骤:

1. 定义User的投影接口

创建一个投影接口,明确指定User需要返回的字段,对于关联的Company集合,我们用另一个Company投影来序列化,避免直接引用完整的Company实体(从而切断递归链):

import org.springframework.data.rest.core.config.Projection;
import java.util.List;

@Projection(name = "userWithCompanies", types = User.class)
public interface UserWithCompaniesProjection {
    Long getId();
    String getUsername();
    // 引用Company的投影,而非完整实体
    List<CompanyBasicProjection> getCompanies();
}

2. 定义Company的投影接口

同样,为Company创建投影,只返回必要的字段,不要包含完整的User引用(如果需要返回User的基础信息,也可以用一个极简的User投影,避免递归):

import org.springframework.data.rest.core.config.Projection;

@Projection(name = "companyBasic", types = Company.class)
public interface CompanyBasicProjection {
    Long getId();
    String getName();
    
    // 如果需要返回User的基础信息,就用下面的方式引用User的极简投影
    // UserBasicProjection getUser();
}

// 可选:User的极简投影,仅返回核心信息,不含companies字段
@Projection(name = "userBasic", types = User.class)
public interface UserBasicProjection {
    Long getId();
    String getUsername();
}

3. 生效投影的两种方式

方式一:设置全局默认投影

如果你希望每次查询User时,默认返回这个投影结构,可以在UserRepository上指定excerptProjection:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(excerptProjection = UserWithCompaniesProjection.class)
public interface UserRepository extends JpaRepository<User, Long> {
}

方式二:请求时动态指定投影

如果不想全局生效,也可以在请求URL里通过projection参数按需指定:

GET /users?projection=userWithCompanies
GET /companies?projection=companyBasic

原理说明

Projection本质上是通过接口定义了序列化的"视图",当Spring Data REST序列化实体时,会严格按照投影接口里定义的方法来返回字段。这样一来,User的companies字段会用CompanyBasicProjection序列化,而这个投影里要么没有User引用,要么只引用了不含companies字段的UserBasicProjection,彻底打破了无限递归的循环链,自然就解决了StackOverflowError问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:04:13