如何在Spring Data REST存储库中创建唯一的URI?

时间:2017-01-28 01:19:01

标签: java spring-data-rest

我面对一个微笑的场景。让我们拥有经典的多对多关系:

public class Student {
  private Long id;

  private String name;

  private List<StudentCourse> studentCourses = new ArrayList<>();
}

public class Course {
  private Long id;

  private String name;

  private List<StudentCourse> studentCourses = new ArrayList<>();
}

课程订阅由以下方式管理:

public class StudentCourse {
  private Long id;

  private Student student;

  private Course course;
}

情况

通过API检索的Student实体的表示如下所示:

{
    "name": "Peter",
    "_links": {
        "self": {
            "href": "myapiroot/api/students/1"
        },
        "studentCourses": {
            "href": "myapiroot/api/students/1/studentCourses"
        }
    }
}

Course实体的表示如下所示:

{
    "name": "Engineering",
    "_links": {
        "self": {
            "href": "myapiroot/api/courses/1"
        },
        "studentCourses": {
            "href": "myapiroot/api/courses/1/studentCourses"
        }
    }
}

从API检索的StudentCourse实体如下所示:

{
    "_links": {
        "self": {
            "href": "myapiroot/api/studentCourses/1"
        },
        "student": {
            "href": "myapiroot/api/studentCourses/1/student"
        },
        "course": {
            "href": "myapiroot/api/studentCourses/1/course"
        },            
    }
}

我想要实现的目标

如果我已经有一个内存课程(带有self URI),我希望能够从StudentCourse表示确定课程,而无需导航到course URI。我希望能够将course表示中的StudentCourse URI视为数据库中的外键 - 唯一标识符。由于每个表示都有self URI,我希望该唯一标识符为。

问题

我认为无法比较来自不同来源的两个实体之间的相等性。 StudentCourse表示中学生和课程的URI 等于selfStudent表示中的Course链接。

由于默认情况下不导出ID,如何有效确定从API中的不同路由获取的相同类型的两个不同实例是否相等?

实施例

我有一个来自Student的{​​{1}}实体。

我有一个来自myapiroot/api/students/1的{​​{1}}实体。

我想知道该学生是否订阅了该课程。为此,我必须执行以下步骤:

  1. 导航至Course以获取所有学生的课程订阅。
  2. myapiroot/api/courses/1的每个学生的课程订阅提出额外请求以检索课程实例。
  3. 将课程URI与已知课程实体的myapiroot/api/students/1/studentCourses URI进行比较。
  4. 第二步非常低效,如果myapiroot/studentCourses/{id}/course表示中的课程URI等于我已经知道的课程实体的self链接,则可以完全避免。

    我的第一个方法

    我尝试通过实现这样的自定义StudentCourse来自定义self返回的资源:

    StudentCourseRepository

    我的第一种方法的问题

    现在,REST存储库返回的StudentCourseResourceProcessor资源如下所示:

    public class StudentCourseResourceProcessor implements ResourceProcessor<Resource<StudentCourse>> {
    
      @Autowired
      private EntityLinks entityLinks;
    
    
      @Override
      public Resource<StudentCourse> process(Resource<StudentCourse> resource) {
        Student student = resource.getContent().getStudent();
        Course course = resource.getContent().getCourse();
        resource.add(entityLinks.linkToSingleResource(Student.class, student.getId()).withRel("student"));
        resource.add(entityLinks.linkToSingleResource(Course.class, course.getId()).withRel("course"));
    
        return resource;
      }
    }
    

    StudentCourse{ "_links": { "self": { "href": "myapiroot/api/studentCourses/1" }, "student": [ {"href": "myapiroot/api/students/1"}, {"href": "myapiroot/api/studentCourses/1/student"} ], "course": [ {"href": "myapiroot/api/courses/1"}, {"href": "myapiroot/api/studentCourses/1/course" ], } } 条目已从对象更改为对象数组。这使得项目的表示不规则。我试图通过调用student删除资源处理器中的旧链接,但这不起作用。就目前而言,无法删除自动生成的URI。

    我的第二种方法

    我已经深入研究了文档并尝试使用projectionscourse实体表示中嵌入关系数据:

    resource.removeLinks()

    现在我可以向StudentCourse发出GET请求。

    @Projection(name="studentCourseDetails", types={StudentCourse.class}) public interface StudentCourseDetails { Course getCourse(); } 表示现在看起来像这样:

    myapiRoot/students/1/studentCourses?projection=studentCourseDetails

    我的第二种方法的问题

    存在StudentCourse信息,但课程{ "course": { "name": "Engineering" }, "_links": { "self": { "href": "myapiroot/api/studentCourses/1" }, "student": { "href": "myapiroot/api/studentCourses/1/student" }, "course": { "href": "myapiroot/api/studentCourses/1/course" }, } } 缺失。此外,我的course设置已被忽略,只有_link&amp;的关系链接。 ResourceProcessor可用。

    我还了解到完全替换这些关系URI是一个坏主意,因为您可以对它们执行操作。例如,HTTP coursestudent,正文为

    PUT

    会更改myapiroot/studentCourses/1/course实体的{ "href": "myapiroot/courses/2" } 关联。所以这些关系URI确实有用!

    我现在的问题

    • 由于关系URI确实有用,我如何唯一地识别来自REST端点的实体(不必通过投影公开ID)?
    • 我的第一种方法是将course URI添加到相对URI。这是一种可接受的处理方式吗?这有什么含义?
    • 是否可以选择在投影内的嵌入对象上启用链接(参见第二种方法)?

2 个答案:

答案 0 :(得分:2)

你有一个课程,你有一个学生。要了解学生是否注册了该课程,您只需查询API即可。您可以通过以下两种方式之一完成此操作:在StudentCoursesRepository中创建显式查询方法,或者让此存储库扩展QueryDslPredicateExecutor。

采用后一种方法非常强大,因为它允许您通过任意标准的任意组合查询您的存储库。

public interface StudentCoursesRepository extends JpaRepository<StudentCourse, Long>, 
            QueryDslPredicateExecutor<StudentCuurse>{

}

有了这个,您现在可以查询下面的API,它将返回零或1条记录:

http://localhost/myapiroot/api/studentCourses?
      student=http://localhost/myapiroot/api/students/1
         &course=http://localhost/myapiroot/api/courses/1

或者,如果您已经公开了ID字段,而是愿意处理这些:

http://localhost/myapiroot/api/studentCourses?student.id=1&course.id=1

答案 1 :(得分:1)

根本问题在于你没有宣称你所寻求的是什么。

  

我认为没有办法比较来自不同来源的两个实体之间的相等性。

即使/ api / courses / 1和/ api / student / 2 / courses / 3都指向相同的课程,这两个URI代表不同的东西。第一个是对课程的规范性引用,没有语义覆盖,而第二个代表与某个学生相关的特定课程。

这类似于获取COURSES表的PRIMARY KEY并查找一行数据,然后将其与STUDENTS_COURSES表中的FOREIGN KEY进行比较,并想知道“为什么这两个值不相同?”

可能缺少的另一个金块是你似乎已经为所有三个表定义了聚合根,尽管你可能只需要两个最多,一个用于学生,一个用于课程。

  

StudentCourse表示中学生和课程的URI不等于学生和课程表示中的自我链接。

不是不是,因为这实际上是多对多表中的FOREIGN KEY,将它们链接在一起。在您导航到/ api / students或/ api / courses之前,您可以收集“自我”链接并对该资源进行绝对引用。

  

我想知道该学生是否订阅了该课程

现在我们到了某个地方。这是你要解决的问题。乍一看,你的域模型似乎并没有描绘出多对多的关系。这是JPA吗?如果是这样,这样的事情可能会更好地暗示如何做到这一点:http://uaihebert.com/jpa-manytomany-unidirectional-and-bidirectional/

通过正确的建模,您可以编写导航属性的查询。最终,你应该能够带一个学生并写一个发现者来获得一个课程列表,反之亦然。从那里开始,你谈论的是检测链接的存在。

基本上,一旦你通过JPA @ManyToMany关系让学生和课程互相指向,你应该能够写

interface StudentRepository extends CrudRepository<Student,Long> {
    List<Student> findByCoursesName(@Param("q") String name);
}

interface CourseRepository extends CrudRepository<Course, Long>{
    List<Student> findByStudentsName(@Param("q") String name);
}

查看此套牌了解更多详情:https://speakerdeck.com/olivergierke/ddd-and-rest-domain-driven-apis-for-the-web-4