簡體   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