如何在 Spring Data REST 中创建具有唯一多对多关系的资源?
How to create resource with unique many-to-many relationsships in Spring Data REST?
我正在使用 Spring Boot 并尝试使用 Spring Data REST 在实体之间实现多对多关系,其中没有创建依赖 Author
实体的副本。
给定以下代码,当我 post 一本有作者的初始书籍时,作者被创建,书籍被创建,关系(查找 table 条目)被创建.当我 post 第二本书时,作者相同,第二本书被创建,它试图创建第二个作者,但由于作者姓名的唯一约束而失败。
我会 expect/want 发生,是第二本书被创建,并且将第二本书与原始作者记录相关联的条目,而不是试图创建 second/duplicate作者.
@Entity(name = 'book')
class Book {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
long id
...
@ManyToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
@JoinTable(name = 'books_authors', joinColumns = @JoinColumn(name = 'book_id'),
inverseJoinColumns = @JoinColumn(name = 'author_id'))
@RestResource(exported = false)
Set<Author> authors
}
@Entity(name = 'author')
@ToString
class Author {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
long id
@Column(nullable = false, unique = true)
String fullName
@ManyToMany(mappedBy = "authors")
Set<Book> books
}
@RepositoryRestResource(collectionResourceRel = "authors", path = "authors")
interface AuthorRepository extends PagingAndSortingRepository<Author, Long> { }
@RepositoryRestResource(collectionResourceRel = "books", path = "books")
interface BookRepository extends PagingAndSortingRepository<Book, Long> { }
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "title" : "Book One", "authors" { "fullName": "AuthorName"} }' ....
HTTP/1.1 201 Created
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "title" : "Book Two", "authors" { "fullName": "AuthorName"} }' ....
第二个 post 由于尝试创建具有重复 fullName
的新作者而导致失败。
我使用 Spring 数据实现了相同的关系,但没有 Spring 数据 REST。在其中,我有控制器和服务,并且能够在 createBook(Book book)
方法中覆盖它。但是对于 Spring Data REST,我没有要实施的服务。我尝试在 AbstractRepositoryEventListener<Book>#onBeforeCreate()
中实现相同的逻辑,但最终遇到了分离的实体问题。
这似乎是我应该能够通过 JPA 关系定义来处理的事情,但我似乎做不到。
您稍微误解或误用了 REST 和 Spring Data REST 所基于的一些概念。让我从解释您提供的代码开始。
基础知识
由于 Author
和 Book
均由存储库管理,因此它们基本上从普通实体提升为聚合根。 DDD 中的聚合根应该管理自身内部的不变量,并且不能作为操纵另一个聚合的副作用进行调整。这就是 Spring Data REST 默认为它们公开专用资源并在两者之间创建 links(字面意思)的原因。
我想简要阐述的另一个方面是 REST 中的身份主题。实际上它非常简单,因为它非常明确。资源具有身份——它们的 URI(顾名思义)。因此,服务器必须识别身份的唯一方法是向其提供相同的 URI。
你的样本
因此在您的特定示例中,服务器无法判断第二个 Book
您 post 指的是同一位作者。基本上是因为您没有引用,而是内联,这使得 Author
成为 Book
聚合的一部分,这与您首先为两者都拥有存储库的方法相矛盾。
如果你仔细想想,你的例子提出了两个问题,甚至使事情变得复杂:
为什么要第二次转移 Author
的所有属性?您基本上想表达的是:"this book belongs to the author I already created",您可以通过提交第一作者的 URI 作为第二个 Book
属性 的值创建] 待创建。
如果第二个内联 Author
文档也更改了某些属性,应该会发生什么情况?这基本上与我首先概述的聚合假设相矛盾,并回到问题 1:你想引用已经存在的东西,而不是 re-submit 东西。
建议的步骤
所以我基本上建议如下:
- 从
Book
的 authors
属性 中删除 @RestResource(exported = false)
。
- 提交您已有的第一个请求。
- 按照
Location
header 中提供的 link 操作,你会得到服务器 returned。
- 遵循 link 目标中提供的
authors
link。
- 使用由该资源编辑的 links return 并在后续创建请求中使用它们来填充
authors
属性。
如果您可以在单独的步骤中创建作者,我什至建议您这样做,因为它们 return 立即创建作者的 URI,以便随后可以使用它们创作书籍。
我正在使用 Spring Boot 并尝试使用 Spring Data REST 在实体之间实现多对多关系,其中没有创建依赖 Author
实体的副本。
给定以下代码,当我 post 一本有作者的初始书籍时,作者被创建,书籍被创建,关系(查找 table 条目)被创建.当我 post 第二本书时,作者相同,第二本书被创建,它试图创建第二个作者,但由于作者姓名的唯一约束而失败。
我会 expect/want 发生,是第二本书被创建,并且将第二本书与原始作者记录相关联的条目,而不是试图创建 second/duplicate作者.
@Entity(name = 'book')
class Book {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
long id
...
@ManyToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
@JoinTable(name = 'books_authors', joinColumns = @JoinColumn(name = 'book_id'),
inverseJoinColumns = @JoinColumn(name = 'author_id'))
@RestResource(exported = false)
Set<Author> authors
}
@Entity(name = 'author')
@ToString
class Author {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
long id
@Column(nullable = false, unique = true)
String fullName
@ManyToMany(mappedBy = "authors")
Set<Book> books
}
@RepositoryRestResource(collectionResourceRel = "authors", path = "authors")
interface AuthorRepository extends PagingAndSortingRepository<Author, Long> { }
@RepositoryRestResource(collectionResourceRel = "books", path = "books")
interface BookRepository extends PagingAndSortingRepository<Book, Long> { }
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "title" : "Book One", "authors" { "fullName": "AuthorName"} }' ....
HTTP/1.1 201 Created
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "title" : "Book Two", "authors" { "fullName": "AuthorName"} }' ....
第二个 post 由于尝试创建具有重复 fullName
的新作者而导致失败。
我使用 Spring 数据实现了相同的关系,但没有 Spring 数据 REST。在其中,我有控制器和服务,并且能够在 createBook(Book book)
方法中覆盖它。但是对于 Spring Data REST,我没有要实施的服务。我尝试在 AbstractRepositoryEventListener<Book>#onBeforeCreate()
中实现相同的逻辑,但最终遇到了分离的实体问题。
这似乎是我应该能够通过 JPA 关系定义来处理的事情,但我似乎做不到。
您稍微误解或误用了 REST 和 Spring Data REST 所基于的一些概念。让我从解释您提供的代码开始。
基础知识
由于 Author
和 Book
均由存储库管理,因此它们基本上从普通实体提升为聚合根。 DDD 中的聚合根应该管理自身内部的不变量,并且不能作为操纵另一个聚合的副作用进行调整。这就是 Spring Data REST 默认为它们公开专用资源并在两者之间创建 links(字面意思)的原因。
我想简要阐述的另一个方面是 REST 中的身份主题。实际上它非常简单,因为它非常明确。资源具有身份——它们的 URI(顾名思义)。因此,服务器必须识别身份的唯一方法是向其提供相同的 URI。
你的样本
因此在您的特定示例中,服务器无法判断第二个 Book
您 post 指的是同一位作者。基本上是因为您没有引用,而是内联,这使得 Author
成为 Book
聚合的一部分,这与您首先为两者都拥有存储库的方法相矛盾。
如果你仔细想想,你的例子提出了两个问题,甚至使事情变得复杂:
为什么要第二次转移
Author
的所有属性?您基本上想表达的是:"this book belongs to the author I already created",您可以通过提交第一作者的 URI 作为第二个Book
属性 的值创建] 待创建。如果第二个内联
Author
文档也更改了某些属性,应该会发生什么情况?这基本上与我首先概述的聚合假设相矛盾,并回到问题 1:你想引用已经存在的东西,而不是 re-submit 东西。
建议的步骤
所以我基本上建议如下:
- 从
Book
的authors
属性 中删除@RestResource(exported = false)
。 - 提交您已有的第一个请求。
- 按照
Location
header 中提供的 link 操作,你会得到服务器 returned。 - 遵循 link 目标中提供的
authors
link。 - 使用由该资源编辑的 links return 并在后续创建请求中使用它们来填充
authors
属性。
如果您可以在单独的步骤中创建作者,我什至建议您这样做,因为它们 return 立即创建作者的 URI,以便随后可以使用它们创作书籍。