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

如何阻止Spring REST Controller响应中对象无限递归展开?

解决Spring Boot接口返回对象无限递归问题

问题描述

编写了一个返回用户信息的接口,User类包含id、name和posts属性,posts是Post类的列表;Post类中又包含User对象。请求接口时,响应出现对象无限递归展开的情况:

{"id":1,"name":"foo","posts":[{"id":1,"name":"diary","createdAt":null,"user":{"id":1,"name":"foo","posts":...

期望的响应格式:

{"id": 1, "name": "foo", "posts":[{"id": 1, "name": "diarty", "createdAt": null, "userId": 1}]}

相关代码

User.java

@Entity
public class User {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    public Long id;
    public String name;

    @OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
    public List<Post> posts;
}

Post.java

@Entity
public class Post {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    public Long id;
    public String name;
    public Date createdAt;

    @ManyToOne
    @JoinColumn(name="user")
    public User user;
}

接口端点

@GetMapping("/users/{id}")
public User getUserInfo(@PathVariable Long id) {
  Optional<User> res = userRepo.findById(id);
  if (res.isEmpty()) {
    throw new IllegalArgumentException("no user " + id + " found");
  } else {
    return res.get();
  }
}

解决方案

方法一:使用Jackson注解控制序列化

直接在实体类上通过Jackson注解避免递归序列化,同时可以自定义返回字段:

  1. 忽略反向关联避免递归:在User的posts字段标记@JsonManagedReference(父对象关联,正常序列化),在Post的user字段标记@JsonBackReference(子对象反向关联,序列化时忽略)
    修改后的User.java:

    @Entity
    public class User {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
        public Long id;
        public String name;
    
        @JsonManagedReference
        @OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
        public List<Post> posts;
    }
    

    修改后的Post.java:

    @Entity
    public class Post {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
        public Long id;
        public String name;
        public Date createdAt;
    
        @JsonBackReference
        @ManyToOne
        @JoinColumn(name="user")
        public User user;
    }
    
  2. 自定义返回userId字段:如果希望Post中显示userId而非完全忽略关联,可调整Post类:

    @Entity
    public class Post {
        // 原有字段不变
    
        @JsonIgnore // 忽略原user字段的序列化
        @ManyToOne
        @JoinColumn(name="user")
        public User user;
    
        // 序列化时自动生成userId字段
        @JsonProperty("userId")
        public Long getUserId() {
            return user != null ? user.id : null;
        }
    }
    

方法二:使用DTO(数据传输对象)

更推荐的方案,通过专门的DTO类控制返回字段,避免实体类与序列化耦合:

  1. 定义UserDTO和PostDTO:

    public class UserDTO {
        private Long id;
        private String name;
        private List<PostDTO> posts;
    
        public UserDTO(User user) {
            this.id = user.id;
            this.name = user.name;
            this.posts = user.posts.stream()
                    .map(PostDTO::new)
                    .collect(Collectors.toList());
        }
    
        // getter省略
    }
    
    public class PostDTO {
        private Long id;
        private String name;
        private Date createdAt;
        private Long userId;
    
        public PostDTO(Post post) {
            this.id = post.id;
            this.name = post.name;
            this.createdAt = post.createdAt;
            this.userId = post.user != null ? post.user.id : null;
        }
    
        // getter省略
    }
    
  2. 修改接口端点返回DTO:

    @GetMapping("/users/{id}")
    public UserDTO getUserInfo(@PathVariable Long id) {
      Optional<User> res = userRepo.findById(id);
      if (res.isEmpty()) {
        throw new IllegalArgumentException("no user " + id + " found");
      } else {
        return new UserDTO(res.get());
      }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:05:04