使用@OneToMany 关系与@RestController 没有无限recution 的最佳实践是什么

问题描述

我有以下几点:

@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. 别的东西

解决方法

最好使用 DTO,这样您就可以将数据库类与 REST API 分离。

如果您想要快速解决方案,您可以使用 Jackson 的 @JsonManagedReference@JsonBackReference。这将告诉 Jackson 存在递归/双向引用,以及如何序列化它:

class Floor {
  @JsonManagedReference
  Set<Slot> slots;
}

class Slot {
  @JsonBackReference
  Floor floor;
}
,

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

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

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

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

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

  1. 删除 Floor 类中的 @JsonIgnoreProperties(value= {"floor","registrations"}),然后添加 @JsonIgnore Slot 类中的 floor 字段。这样,Jackson 就不会序列化该字段。
  2. 使用 @JsonSerializeFloor 类构建您自己的序列化程序(请参阅 this)。

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...