【问题标题】:Spring Boot API response returns repeating nested JSONSpring Boot API 响应返回重复的嵌套 JSON
【发布时间】:2021-04-25 06:18:00
【问题描述】:

我有一个User 模型和一个TodoItem 模型,其中TodoItem 模型具有User 模型的主键和user_id @joincolumn。我的问题是我在添加项目后从getUsers API 得到的响应。它创建了这个超长的嵌套 JSON,它会一遍又一遍地重复自己。我觉得我没有正确处理主键大小写。

TodoController.java

@RestController
@RequestMapping("/api")
public class TodoController {

@Autowired
private TodoRepository todoRepository;

@PostMapping("/addItem")
public TodoItem addTodoItem(@RequestBody TodoItem todoItem) {
    return todoRepository.save(todoItem);
}

User.java

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(name = "name")
private String name;

@Column(name = "password")
private String password;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<TodoItem> todos;

public User() {
}

public User(String name, String password, List<TodoItem> todos) {
    this.name = name;
    this.password = password;
    this.todos = todos;
}
// setter and getters

TodoItem.java

@Entity
@Table(name = "todo_item")
public class TodoItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

@Column(name = "todo")
private String todo;

@Column(name = "completed")
private boolean completed;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

public TodoItem() {
}

public TodoItem(String todo, boolean completed) {
    this.todo = todo;
    this.completed = completed;
}
// setters and getters

添加项目请求

{
  "todo": "blahblah",
  "completed": false,
  "user": {
      "id": 6
  }
}

添加项目响应

{
  "id": 26,
  "todo": "blahblah",
  "completed": false,
  "user": {
      "id": 6,
      "name": null,
      "password": null,
      "todos": null
  }

}

所以我已经不喜欢给出响应的方式了,为什么当 id 为 6 的用户存在时 name、pass 和 todos 为 null,我也只是给它传递了一个 todoitem,所以为什么 todo 为 null。数据库正确填充,只是响应似乎错误。然后我认为它与我在这里遇到的主要问题有关;这是在我向用户添加项目之后:

获取用户响应

[
{
    "id": 6,
    "name": "joe",
    "password": "pass",
    "todos": [
        {
            "id": 26,
            "todo": "blahblah",
            "completed": false,
            "user": {
                "id": 6,
                "name": "joe",
                "password": "pass",
                "todos": [
                    {
                        "id": 26,
                        "todo": "blahblah",
                        "completed": false,
                        "user": {
                            "id": 6,
                            "name": "joe",
                            "password": "pass",
                            "todos": [
                                {
                                    "id": 26,
                                    "todo": "blahblah",
                                    "completed": false,
                                    "user": {
                                        "id": 6,
                                        "name": "joe",
                                        "password": "pass",
                                        "todos": [
                                            {
                                                "id": 26,
                                                "todo": "blahblah",
                                                "completed": false,
                                                "user": {
                                                    "id": 6,
                                                    "name": "joe",
                                                    "password": "pass",
                                                    "todos": [
                                                        {
                                                            "id": 26,
                                                            "todo": "blahblah",

而且它只是这样持续了数千行。即使响应很疯狂,数据库也会正确更新,但由于此问题,API 调用可能需要一段时间

【问题讨论】:

标签: java mysql spring spring-boot api


【解决方案1】:

在您的 TodoItem.java 中,删除 User 属性的 getter。

确保您的 TodoItem.java 中只有用户属性的设置器。

【讨论】:

  • 哇,这很有效,究竟是什么在调用 getter 并将其粘贴在响应中?
  • 你删除的getter正在调用用户,因此,用户将有一个TodoItem列表,在每个TodoItem中,另一个用户等等,循环引用错误。
【解决方案2】:

本质上,当 Spring 形成响应时,它正在执行类似“.toString()”的方法来将实体映射到 JSON 对象以传递到前端。

您的实体之间有一个bidirectional 关联,因此当映射器进入user 时,它会映射所有todos,并且因为这些todos 都与user 有关系,所以它会得到user...再一次...再一次溢出死亡循环。

“最好”的方式也是常见的,你应该创建一个你自己构建的 DTO 类。

UserTodoDTO:


//Lombok Getter/Setter/ToString and All Args Constructor.
@ToString
@Getter
@Setter
@AllArgsConstructor
public class UserTodoDTO {

    private long id;
    @JsonProperty("name")
    private String username;
    private List<TodoItem> todoItems;

}


//Pretend this is full of your 'users'.
List<User> usersFromDatabaseAsEntity = new ArrayList<>();

//Return these and the serialisation will not occur.
final List<UserTodoDTO> viewsOfUser = usersFromDatabaseAsEntity
        .stream()
        .map(entity -> new UserTodoDTO(entity.getId(), entity.getName(), entity.getTodos()))
        .collect(Collectors.toList());

请注意,如果您执行log.info(user),它将执行相同的操作。 避免这种情况的方法(还有其他方法)是将@JsonIgnore 添加到关系的一侧(例如在待办事项上的@ManyToOne-Users 上)或覆盖待办事项的 toString()。

更改为TodoItem


    //Will not be mapped to JSON so stops the loop of death.
    @JsonIgnore
    //Lombok can make the toString() for you but need to
    // tell it to ignore this field to stop loop of death.
    @ToString.Exclude
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    //If not using lombok and you are doing TodoItem.toString() somewhere...
    //Remove the user from toString().
    @Override
    public String toString() {
        return "TodoItem{" +
                "id=" + id +
                ", todo='" + todo + '\'' +
                ", completed=" + completed +
                '}';
    }

【讨论】:

    猜你喜欢
    • 2020-10-23
    • 2017-03-15
    • 2019-07-27
    • 1970-01-01
    • 2021-07-05
    • 2018-02-13
    • 2017-02-17
    • 1970-01-01
    • 2021-02-20
    相关资源
    最近更新 更多