繁体   English   中英

如何维护Spring数据REST和JPA的双向关系?

[英]How to maintain bi-directional relationships with Spring Data REST and JPA?

使用 Spring 数据 REST,如果您有OneToManyManyToOne关系,PUT 操作会在“非拥有”实体上返回 200,但实际上不会保留已加入的资源。

示例实体:

@Entity(name = 'author')
@ToString
class AuthorEntity implements Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id

    String fullName

    @ManyToMany(mappedBy = 'authors')
    Set<BookEntity> books
}


@Entity(name = 'book')
@EqualsAndHashCode
class BookEntity implements Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id

    @Column(nullable = false)
    String title

    @Column(nullable = false)
    String isbn

    @Column(nullable = false)
    String publisher

    @ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
    Set<AuthorEntity> authors
}

如果你用PagingAndSortingRepository支持它们,你可以 GET a Book ,点击书上的authors链接,并使用作者的 URI 进行 PUT 关联。 你不能 go 反过来。

如果您对作者执行 GET 操作并对其books链接执行 PUT 操作,则响应会返回 200,但这种关系永远不会持续存在。

这是预期的行为吗?

TL;博士

关键在于Spring Data REST中没有任何东西 - 因为您可以轻松地让它在您的场景中运行 - 但要确保您的模型保持关联的两端同步。

问题

您在此处看到的问题源于Spring Data REST基本上修改了AuthorEntitybooks属性。 这本身并不反映BookEntityauthors属性中的此更新。 这必须手动解决,这不是Spring Data REST组成的约束,而是JPA的一般工作方式。 您只需手动调用setter并尝试保留结果即可重现错误行为。

怎么解决这个?

如果删除双向关联不是一个选项(请参阅下面为什么我建议这样做),使这项工作的唯一方法是确保关联的更改反映在双方。 通常人们通过在添加BookEntity时手动将作者添加到BookEntity来处理此BookEntity

class AuthorEntity {

  void add(BookEntity book) {

    this.books.add(book);

    if (!book.getAuthors().contains(this)) {
       book.add(this);
    }
  }
}

如果你想确保来自另一方的更改也被传播,那么额外的if子句也将被添加到BookEntity端。 if基本上是必需的,否则这两种方法会不断地自称。

Spring Data REST默认使用字段访问,因此实际上没有可以将此逻辑放入的方法。 一种选择是切换到属性访问并将逻辑放入setter。 另一种选择是使用@PreUpdate / @PrePersist注释的方法,该方法迭代实体并确保修改反映在双方。

消除问题的根本原因

如您所见,这为域模型增加了相当多的复杂性。 我昨天在Twitter上开玩笑说:

#1双向关联规则:不要使用它们...... :)

如果您尽可能不尝试使用双向关系,而是回到存储库以获取构成关联背面的所有实体,它通常会简化问题。

一个很好的启发式方法来确定哪一方需要考虑关联的哪一方对于您正在建模的领域是真正的核心和关键。 在你的情况下,我认为对于一个没有她写的书而存在的作者来说完全没问题。 另一方面,没有作者的书根本没有太多意义。 所以我保持authors财产BookEntity但介绍的以下方法BookRepository

interface BookRepository extends Repository<Book, Long> {

  List<Book> findByAuthor(Author author);
}

是的,这要求之前可以调用author.getBooks()所有客户端现在可以使用存储库。 但从积极的方面来说,你已经删除了域对象中的所有内容,并在整个过程中创建了从书到作者的清晰依赖方向。 书籍取决于作者,但不是相反。

我遇到了类似的问题,在通过REST api将我的POJO(包含双向映射@OneToMany和@ManyToOne)作为JSON发送时,数据在父实体和子实体中都保持不变,但未建立外键关系。 这是因为需要手动维护双向关联。

JPA提供了一个注释@PrePersist ,可用于确保在实体持久化之前执行使用它注释的方法。 因为,JPA首先将父实体插入数据库后跟子实体,所以我包含了一个用@PrePersist注释的方法,该方法将迭代子实体列表并手动设置父实体。

在你的情况下,它将是这样的:

class AuthorEntitiy {
    @PrePersist
    public void populateBooks {
        for(BookEntity book : books)
            book.addToAuthorList(this);   
    }
}

class BookEntity {
    @PrePersist
    public void populateAuthors {
        for(AuthorEntity author : authors)
            author.addToBookList(this);   
    }
}

在此之后,你可能会得到一个无限递归误差,避免注释父类@JsonManagedReference和你的子类@JsonBackReference 这个解决方案对我有用,希望它也适合你。

这个链接有一个关于如何解决递归问题的很好的教程: 双向关系

我能够使用@JsonManagedReference 和@JsonBackReference,它就像一个魅力

我相信人们还可以通过添加 @BeforeLinkSave 处理程序来利用 @RepositoryEventHandler 来交叉链接实体之间的双向关系。 这似乎对我有用。

@Component
@RepositoryEventHandler
public class BiDirectionalLinkHandler {

    @HandleBeforeLinkSave
    public void crossLink(Author author, Collection<Books> books) {
        for (Book b : books) {
            b.setAuthor(author);
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM