如何在Spring Data REST中创建具有唯一多对多关系的资源?

P. *_*ers 4 rest jpa spring-data spring-data-rest

我正在使用Spring Boot并尝试使用Spring Data REST实现实体之间的多对多关系,其中不会Author创建依赖实体的重复项.

给定以下代码,当我发布初始书籍时,与作者一起创建作者,创建书籍,并创建关系(查找表条目).当我使用同一作者发布第二本书时,会创建第二本书,并尝试创建第二位作者,由于对作者姓名的唯一约束而导致第二本书失败.

我是否期望/想要发生,是否创建了第二本书,并且将第二本书与原始作者记录相关联,而不是尝试创建第二本/重复作者.

@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"} }' ....
Run Code Online (Sandbox Code Playgroud)

第二篇文章因尝试创建具有重复的新作者而导致失败fullName.

我使用Spring Data实现了相同的关系,没有Spring Data REST.在其中,我有控制器和服务,并且能够在createBook(Book book)方法中覆盖它.但是使用Spring Data REST,我没有服务可以实现.我尝试过实现相同的逻辑,AbstractRepositoryEventListener<Book>#onBeforeCreate()但最终会出现分离的实体问题.

这似乎是我应该能够处理的JPA关系定义,但我似乎无法实现.

Oli*_*ohm 9

您对REST和Spring Data REST所基于的一些概念略有误解或误用.首先让我解释一下您提供的代码.

基本面

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

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

你的样本

因此,在您的特定示例中,服务器无法判断Book您发布的第二个是指同一个作者.基本上是因为你没有提到,你在内联,这使得与你的方法相矛盾AuthorBook聚合的一部分首先拥有两个存储库.

如果你考虑一下,你的例子提出了两个问题,甚至使问题复杂化:

  1. 你为什么要Author第二次转移所有属性?您基本上要表达的是:"这本书属于我已经创建的作者",您可以通过提交第一作者的URI作为要创建author的第二个属性的值Book来创建.

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

建议的步骤

所以我基本上建议如下:

  1. 删除@RestResource(exported = false)Bookauthors财产.
  2. 提交您已有的第一个请求.
  3. 按照Location服务器返回的标题中提供的链接进行操作.
  4. 按照authors链接目标中提供的链接进行操作.
  5. 使用该资源返回的链接,并在后续创建请求中使用它们来填充authors属性.

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