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

