Spring Data、REST 和 ManyToMany 关系

Posted

技术标签:

【中文标题】Spring Data、REST 和 ManyToMany 关系【英文标题】:Spring Data, REST and ManyToMany relationship 【发布时间】:2017-10-16 21:44:05 【问题描述】:

我想知道创建、记录(swagger)和公开涉及多对多关系的 REST API 的良好做法是什么。一个简单的例子 - Student,“拥有”方:

@Entity
public class Student 

    private int id;
    private String name;
    private Set<Course> courses;

    ...
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    public Set<Course> getCourses() 
        return courses;
    

    public void setCourses(Set<Course> courses) 
        this.courses = courses;
    
    ...

还有Course(连学生都没有曝光):

@Entity
public class Course 

    private int id;
    private String name;
    ...

这会产生 3 个数据库表 - 学生、课程和关系表(H2/休眠)。让我们找一个现有的学生:

GET /api/students/1 HTTP/1.1
...


  "id": 1,
  "name": "John Smith",
  "courses": [
    
      "id": 2,
      "name": "Maths"
    ,
    
      "id": 1,
      "name": "Java Programming"
    
  ]

很好。现在我想通过 POST 请求创建一个新学生,该学生注册了两个现有课程:

curl -X POST \
  http://localhost:8080/api/students \
  -H 'postman-token: 706c8d0e-eca5-7bff-c557-3e199e8a0c17' \
  -d '
  "name": "Peter Brown",
  "courses": [
    
      "id": 1,
      "name": "Maths"
    ,
    
      "id": 2,
      "name": "Java Programming"
    
  ]
'

在服务器端,这可能会触发映射方法,例如:

@ApiOperation("Creates a new student.")
@RequestMapping(method = RequestMethod.POST, 
    value = "/api/students",
    consumes = MediaType.APPLICATION_JSON_VALUE,
    produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Student> createStudent(@RequestBody Student student) 

    Student newStudent = studentRepository.save(student);

    URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/api/students/id").buildAndExpand(newStudent.getId())
            .toUri();

    return ResponseEntity.created(location).body(newStudent);

学生存储库是CrudRepository

问题:这将触发以下错误:

传递给持久化的分离实体:xxx.domain.Course;嵌套异常 是 org.hibernate.PersistentObjectException: 分离的实体传递给 坚持:xxx.domain.Course

原因:现有课程(它们存在于数据库中)上的 ID-s 使它们“分离”。我应该如何处理这些情况?

通过创建学生然后通过 REST 更新它? 用特殊的逻辑进入控制器? 通过更改CascadeType?

谢谢!

更新:我已按照接受的答案中的建议将学生视为 DTO,并且我还为课程引入了 PATCH/GET 方法。

【问题讨论】:

我的猜测是,您必须先将学生“空”,然后再更新。 【参考方案1】:

我相信您应该将 @RequestBody Student 视为 dto(实际上是)不要直接保存它,最好提取它,创建实体或从存储库中检索它们并构建您的最终 Student 实体并将其保存到数据库。

【讨论】:

你的意思是我应该用控制器(或服务)实现所有这些特殊处理?首先创建一个学生,然后创建或分配课程,然后在响应正文中返回最终对象? 完全正确,但是在只接受 dto 对象并返回你的学生对象的服务中,不要污染控制器。 我会四处看看,看看这是否完全好看(包括最后将生成的 swagger 文档)。 我添加了一些骨架来实现这个逻辑,希望对你有帮助! 代码不太好,因为如果新学生注册到现有课程,repo 将抛出我在问题中引用的异常。相反,它应该首先创建一个空学生,然后使用现有(或新)课程对其进行更新。

以上是关于Spring Data、REST 和 ManyToMany 关系的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data Rest 和 Cors

Spring Data REST (2.4.4.RELEASE) 和 CORS

spring-data-rest 和控制器,使用相同的 objectMaper 进行序列化/反序列化

Spring Data Rest 和 Hateoas

Spring Data REST

将业务逻辑添加到 spring-data-rest 应用程序