首页 > 解决方案 > 将@OneToMany 与@RestController 关系使用而不进行无限缩减的最佳实践是什么

问题描述

我有以下内容:

@Data
@Entity
@Table(name = "floor")
public class Floor {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "floor_id")
    private Long id;

    @JsonIgnoreProperties(value= {"floor", "registrations"})
    @OneToMany (mappedBy = "floor", cascade = CascadeType.ALL)
    private Set<Slot> slots;
}

@Entity
@Table(name = "slot")
public class Slot {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "slot_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "floor_id")
    private Floor floor;
}

@RestController
public class Controller {

    @Autowired
    FloorService floorService;

    @GetMapping("/api/floors")
    public List<Floor> getAllFloors() {
        // calls repository.findAll()
        return floorService.getAllFloors();
    }
}

访问 API 端点时,我得到:

2021-03-06 06:52:30.038  WARN 699889 --- [tp1028811481-19] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.veebzone.parking.model.Floor["slots"])]

通过使用 /api/floors 端点获取每个楼层的列表,我的目标是在其 JSON 元素中获取与每个楼层关联的插槽列表。

实现这一目标的最佳做法是什么:

  1. 我的方法是正确的,只是一些配置问题?
  2. 我应该改用服务返回的一些 DTO(将插槽列表作为子元素)并使用 JSONIgnore 作为插槽字段。
  3. 别的东西

标签: javaspring-bootspring-data-jpaspring-restcontrollerhibernate-onetomany

解决方案


我假设您在调用端点时想要这样的东西(一组楼层,每个楼层都有一组插槽):

[
    { "id": 1,
      "slots": [
            { "id": 1 },
            { "id": 2 }
        ]
    },
    { "id": 2,
      "slots": [
            { "id": 3 }
        ]
    }
]

您有一个无限递归,因为 Jackson 是 Spring Boot 中用于写入/读取 Json 的默认库(就像在您的 enpoint 中一样),它尝试使用您类中的每个字段来构建 Json。

所以发生的事情是,Jackson 试图在输出 Json 中写入第一层,但这一层与第一个插槽有关,它与第一层有关,依此类推。这是你的循环。

如果你想打破循环,你有两个选择:

  1. 删除Floor类中的@JsonIgnoreProperties(value= {"floor", "registrations"}) ,然后在Slot类的 floor 字段上添加@JsonIgnore 。这样,杰克逊就不会序列化该字段。
  2. 使用@JsonSerialize为Floor类构建您自己的序列化程序(参见)。

现在,哪个选项更好将取决于您的实体有多大以及实体之间的关系有多复杂。


推荐阅读