如何在 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 所基于的一些概念。让我从解释您提供的代码开始。

基础知识

由于 AuthorBook 均由存储库管理,因此它们基本上从普通实体提升为聚合根。 DDD 中的聚合根应该管理自身内部的不变量,并且不能作为操纵另一个聚合的副作用进行调整。这就是 Spring Data REST 默认为它们公开专用资源并在两者之间创建 links(字面意思)的原因。

我想简要阐述的另一个方面是 REST 中的身份主题。实际上它非常简单,因为它非常明确。资源具有身份——它们的 URI(顾名思义)。因此,服务器必须识别身份的唯一方法是向其提供相同的 URI。

你的样本

因此在您的特定示例中,服务器无法判断第二个 Book 您 post 指的是同一位作者。基本上是因为您没有引用,而是内联,这使得 Author 成为 Book 聚合的一部分,这与您首先为两者都拥有存储库的方法相矛盾。

如果你仔细想想,你的例子提出了两个问题,甚至使事情变得复杂:

  1. 为什么要第二次转移 Author 的所有属性?您基本上想表达的是:"this book belongs to the author I already created",您可以通过提交第一作者的 URI 作为第二个 Book 属性 的值创建] 待创建。

  2. 如果第二个内联 Author 文档也更改了某些属性,应该会发生什么情况?这基本上与我首先概述的聚合假设相矛盾,并回到问题 1:你想引用已经存在的东西,而不是 re-submit 东西。

建议的步骤

所以我基本上建议如下:

  1. Bookauthors 属性 中删除 @RestResource(exported = false)
  2. 提交您已有的第一个请求。
  3. 按照 Location header 中提供的 link 操作,你会得到服务器 returned。
  4. 遵循 link 目标中提供的 authors link。
  5. 使用由该资源编辑的 links return 并在后续创建请求中使用它们来填充 authors 属性。

如果您可以在单独的步骤中创建作者,我什至建议您这样做,因为它们 return 立即创建作者的 URI,以便随后可以使用它们创作书籍。