如何阻止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注解避免递归序列化,同时可以自定义返回字段:
忽略反向关联避免递归:在
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; }自定义返回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类控制返回字段,避免实体类与序列化耦合:
定义
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省略 }修改接口端点返回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
相关产品推荐
相关产品推荐

