如何在 Spring 数据 REST 存储库中创建唯一的 URI?
How to create unique URIs in Spring Data REST repositories?
我遇到了与此类似的情况。让我们建立经典的多对多关系:
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
表示中确定课程,而不必导航到 [=27] =] 网址。我希望能够将 StudentCourse
表示中的 course
URI 视为数据库中的外键 - 一个唯一标识符。由于每个表示都有一个 self
URI,我希望唯一标识符是那个。
问题
我认为无法比较来自不同来源的两个实体之间的平等性。 StudentCourse
表示中学生和课程的 URI 是 not 等于 Student
中的 self
link 和 Course
表示。
由于默认不导出ID,如何有效判断API中不同路由获取的同类型的两个不同实例是否相等?
示例:
我有一个来自 myapiroot/api/students/1
的 Student
实体。
我有一个来自 myapiroot/api/courses/1
的 Course
实体。
我想知道该学生是否订阅了该课程。为此,我必须执行以下步骤:
- 导航至
myapiroot/api/students/1/studentCourses
以获取学生的所有课程订阅。
- 为每个学生的课程订阅额外请求
myapiroot/studentCourses/{id}/course
以检索课程实例。
- 将课程 URI 与来自已知课程实体的
self
URI 进行比较。
第二步效率极低,如果 StudentCourse
表示中的课程 URI 等于我已经知道的课程实体的 self
link,则完全可以避免。
我的第一个方法
我已尝试通过实现自定义 StudentCourseResourceProcessor
来自定义 StudentCourseRepository
返回的资源:
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;
}
}
我第一种方法的问题
现在 REST 存储库返回的 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
和 course
条目已从对象更改为对象数组。这使得项目的表示不规则。我试图通过调用 resource.removeLinks()
来删除资源处理器中的旧 link,但这不起作用。目前,无法删除自动生成的 URI。
我的第二种方法
我深入研究了文档并尝试使用 projections 将关系数据嵌入到 StudentCourse
实体表示中:
@Projection(name="studentCourseDetails", types={StudentCourse.class})
public interface StudentCourseDetails {
Course getCourse();
}
现在我可以向 myapiRoot/students/1/studentCourses?projection=studentCourseDetails
发出 GET 请求。
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
。此外,我的 ResourceProcessor
中的设置已被忽略,只有 course
和 student
的关系 link 可用。
我还了解到完全替换那些关系 URI 不是一个好主意,因为您可以对它们执行操作。例如,正文为
的 HTTP PUT
到 myapiroot/studentCourses/1/course
{ "href": "myapiroot/courses/2" }
将更改 StudentCourse
实体的 course
关联。所以这些关系 URI 确实有用!
我现在的问题
- 由于关系 URI 确实有用,我如何才能唯一地标识来自 REST 端点的实体(而不必通过投影公开 ID)?
- 我的第一种方法是将
self
URI 添加到相对 URI。这是一种可接受的处理方式吗?这有什么影响?
- 是否可以选择在投影内的嵌入对象上启用 links(参见第二种方法)?
您有一个课程和一个学生。要了解学生是否注册了该课程,您可以简单地查询 API。您可以通过以下两种方式之一执行此操作:通过在 StudentCoursesRepository 中创建显式查询方法或让此存储库扩展 QueryDslPredicateExecutor。
采用后一种方法非常强大,因为它允许您通过任意组合的任意条件来查询您的存储库。
public interface StudentCoursesRepository extends JpaRepository<StudentCourse, Long>,
QueryDslPredicateExecutor<StudentCuurse>{
}
有了这个,您现在可以查询 API,如下所示,这将 return 零或 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
根本的问题是你没有声明你要找的东西。
I see no way compare equality between two entities from different sources.
即使 /api/courses/1 和 /api/student/2/courses/3 都指向同一个课程,这两个 URI 代表不同的事物。第一个是对课程的规范引用,没有语义叠加,而第二个代表与特定学生相关的特定课程。
这类似于获取课程 table 的主键并查找一行数据,然后将其与 STUDENTS_COURSES table 中的外键进行比较,想知道 "why aren't these two values the same?"
另一个可能遗漏的要点是您似乎已经为所有三个 table 定义了聚合根,尽管有争议您可能最多只需要两个,一个用于学生,一个用于学生对于课程。
The URI for both student and course in the StudentCourse representation is not equal to the self link in the Student and Course representations.
不是,因为这本质上是 many-to-many table 中的外键,link 将它们组合在一起。直到您导航到 /api/students 或 /api/courses,您才能收集到 "self" link 并获得对该资源的绝对引用。
I want to find out if that student is subscribed to that course
现在我们有所进展。这是您要解决的问题。乍一看,您的域模型似乎没有描述 many-to-many 关系。这是 JPA 吗?如果是这样,这样的事情可能会更好地提示如何做到这一点:http://uaihebert.com/jpa-manytomany-unidirectional-and-bidirectional/
通过适当的建模,您可以编写查询属性导航。最终,您应该能够带一个学生并编写一个查找器来获取课程列表,反之亦然。从那里开始,您正在谈论检测 link.
的存在
基本上,一旦学生和课程通过 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
我遇到了与此类似的情况。让我们建立经典的多对多关系:
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
表示中确定课程,而不必导航到 [=27] =] 网址。我希望能够将 StudentCourse
表示中的 course
URI 视为数据库中的外键 - 一个唯一标识符。由于每个表示都有一个 self
URI,我希望唯一标识符是那个。
问题
我认为无法比较来自不同来源的两个实体之间的平等性。 StudentCourse
表示中学生和课程的 URI 是 not 等于 Student
中的 self
link 和 Course
表示。
由于默认不导出ID,如何有效判断API中不同路由获取的同类型的两个不同实例是否相等?
示例:
我有一个来自 myapiroot/api/students/1
的 Student
实体。
我有一个来自 myapiroot/api/courses/1
的 Course
实体。
我想知道该学生是否订阅了该课程。为此,我必须执行以下步骤:
- 导航至
myapiroot/api/students/1/studentCourses
以获取学生的所有课程订阅。 - 为每个学生的课程订阅额外请求
myapiroot/studentCourses/{id}/course
以检索课程实例。 - 将课程 URI 与来自已知课程实体的
self
URI 进行比较。
第二步效率极低,如果 StudentCourse
表示中的课程 URI 等于我已经知道的课程实体的 self
link,则完全可以避免。
我的第一个方法
我已尝试通过实现自定义 StudentCourseResourceProcessor
来自定义 StudentCourseRepository
返回的资源:
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;
}
}
我第一种方法的问题
现在 REST 存储库返回的 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
和 course
条目已从对象更改为对象数组。这使得项目的表示不规则。我试图通过调用 resource.removeLinks()
来删除资源处理器中的旧 link,但这不起作用。目前,无法删除自动生成的 URI。
我的第二种方法
我深入研究了文档并尝试使用 projections 将关系数据嵌入到 StudentCourse
实体表示中:
@Projection(name="studentCourseDetails", types={StudentCourse.class})
public interface StudentCourseDetails {
Course getCourse();
}
现在我可以向 myapiRoot/students/1/studentCourses?projection=studentCourseDetails
发出 GET 请求。
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
。此外,我的 ResourceProcessor
中的设置已被忽略,只有 course
和 student
的关系 link 可用。
我还了解到完全替换那些关系 URI 不是一个好主意,因为您可以对它们执行操作。例如,正文为
的 HTTPPUT
到 myapiroot/studentCourses/1/course
{ "href": "myapiroot/courses/2" }
将更改 StudentCourse
实体的 course
关联。所以这些关系 URI 确实有用!
我现在的问题
- 由于关系 URI 确实有用,我如何才能唯一地标识来自 REST 端点的实体(而不必通过投影公开 ID)?
- 我的第一种方法是将
self
URI 添加到相对 URI。这是一种可接受的处理方式吗?这有什么影响? - 是否可以选择在投影内的嵌入对象上启用 links(参见第二种方法)?
您有一个课程和一个学生。要了解学生是否注册了该课程,您可以简单地查询 API。您可以通过以下两种方式之一执行此操作:通过在 StudentCoursesRepository 中创建显式查询方法或让此存储库扩展 QueryDslPredicateExecutor。
采用后一种方法非常强大,因为它允许您通过任意组合的任意条件来查询您的存储库。
public interface StudentCoursesRepository extends JpaRepository<StudentCourse, Long>,
QueryDslPredicateExecutor<StudentCuurse>{
}
有了这个,您现在可以查询 API,如下所示,这将 return 零或 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
根本的问题是你没有声明你要找的东西。
I see no way compare equality between two entities from different sources.
即使 /api/courses/1 和 /api/student/2/courses/3 都指向同一个课程,这两个 URI 代表不同的事物。第一个是对课程的规范引用,没有语义叠加,而第二个代表与特定学生相关的特定课程。
这类似于获取课程 table 的主键并查找一行数据,然后将其与 STUDENTS_COURSES table 中的外键进行比较,想知道 "why aren't these two values the same?"
另一个可能遗漏的要点是您似乎已经为所有三个 table 定义了聚合根,尽管有争议您可能最多只需要两个,一个用于学生,一个用于学生对于课程。
The URI for both student and course in the StudentCourse representation is not equal to the self link in the Student and Course representations.
不是,因为这本质上是 many-to-many table 中的外键,link 将它们组合在一起。直到您导航到 /api/students 或 /api/courses,您才能收集到 "self" link 并获得对该资源的绝对引用。
I want to find out if that student is subscribed to that course
现在我们有所进展。这是您要解决的问题。乍一看,您的域模型似乎没有描述 many-to-many 关系。这是 JPA 吗?如果是这样,这样的事情可能会更好地提示如何做到这一点:http://uaihebert.com/jpa-manytomany-unidirectional-and-bidirectional/
通过适当的建模,您可以编写查询属性导航。最终,您应该能够带一个学生并编写一个查找器来获取课程列表,反之亦然。从那里开始,您正在谈论检测 link.
的存在基本上,一旦学生和课程通过 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