在 JPA 中刪除具有多對多關係的實體
一、概述
在本教程中,我們將學習如何從 JPA 中的多對多關係中刪除實體。
2. 多對多關係
多對多關係是兩個實體與附加聯結表連接的關係。為了有效地映射這些實體,我們應該遵循一些準則。
首先,在定義多對多關係時,我們應該考慮使用Set
而不是List
。作為 JPA 實現,Hibernate 不會以有效的方式從 List 中刪除實體。
使用List
時,Hibernate 從聯結表中刪除所有實體並插入剩餘的實體。這可能會導致性能問題。我們可以使用Set
輕鬆避免這個問題。
其次,我們不應在映射中使用CascadeType.REMOVE
,因此不應使用CascadeType.ALL
。
在多對多關係中,兩個實體彼此獨立。例如,假設我們有兩個實體, Post
和Category
。從Post
實體中刪除記錄時,我們通常不想刪除關聯的Category
實體。使用CascadeType.REMOVE
,JPA 將刪除所有關聯的實體,即使是那些可能仍連接到其他實體的實體。
要在 JPA 中定義多對多關係,我們可以使用@ManyToMany
註釋。
多對多關聯可以是單向的或雙向的。
3.從單向移除@ManyToMany
現在,讓我們看看如何從單向多對多關聯中刪除實體。
首先,讓我們定義我們將在示例中使用的數據模型Post
和Category
:
@Entity
@Table(name = "post")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "title")
private String title;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@JoinTable(name = "post_category",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "category_id")
)
private Set<Category> categories = new HashSet<>();
// getters and setters
}
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
// getters and setters
}
在這裡,我們將關係定義為單向關係,因為它不需要使用Category
表中的關聯。此外,我們將Post
實體聲明為負責管理關係的實體。
現在,假設我們有兩個Post
實體,它們都與同一個Category
相關聯:
Category category1 = new Category("JPA");
Category category2 = new Category("Persistence");
Post post1 = new Post("Many-to-Many Relationship");
post1.getCategories().add(category1);
post1.getCategories().add(category2);
Post post2 = new Post("Entity Manager");
post2.getCategories().add(category1);
entityManager.persist(post1);
entityManager.persist(post2);
接下來,讓我們從第一個Post
實體中刪除類別:
void givenEntities_whenRemove_thenRemoveAssociation() {
Post post1 = entityManager.find(Post.class, 1L);
Post post2 = entityManager.find(Post.class, 2L);
Category category = entityManager.find(Category.class, 1L);
post1.getCategories().remove(category);
assertEquals(1, post1.getCategories().size());
assertEquals(1, post2.getCategories().size());
}
結果,第一個Post
和 Category 之間的關係被刪除了。此外,JPA 沒有刪除關聯的類別。
由於我們使用的是Set
而不是List
,因此 JPA會生成一個 delete 語句以從聯結表中刪除關聯。
4.從雙向@ManyToMany
中刪除
在雙向關係中,我們可以從雙方管理關聯。
首先,讓我們在Book
和Author
實體之間創建一個雙向關聯:
@Entity
@Table(name = "book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "title")
private String title;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(name = "book_author",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "author_id")
)
private Set<Author> authors = new HashSet<>();
// getters and setters
}
@Entity
@Table(name = "author")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
@ManyToMany(mappedBy = "authors")
private Set<Book> books = new HashSet<>();
// getters and setters
}
JPA 將創建一個額外的聯結表來存儲兩個實體之間的連接。該表充當關聯的子端。因此, Author
和Book
實體都代表父端。
然而,儘管父母可能看起來相同,但事實並非如此。關係的所有權由mappedBy
屬性確定。不是所有者的實體將具有mappedBy
屬性。
在我們的示例中, Book
實體是所有者。因此,它會將關聯更改傳播到聯結表。
4.1.從所有者實體中刪除
讓我們看看如何從所有者端刪除實體。
我們將向充當關聯所有者的實體添加輔助方法。在我們的例子中,它是Book
實體:
public void removeAuthor(Author author){
this.authors.remove(author);
author.getBooks().remove(this);
}
在刪除作者時,我們可以調用輔助方法來移除關聯:
void givenEntities_whenRemoveFromOwner_thenRemoveAssociation() {
Author author = (Author) entityManager
.createQuery("SELECT author from Author author where author.name = ?1")
.setParameter(1, "Ralph Johnson")
.getSingleResult();
Book book1 = entityManager.find(Book.class, 1L);
Book book2 = entityManager.find(Book.class, 2L);
book1.removeAuthor(author);
entityManager.persist(book1);
assertEquals(3, book1.getAuthors().size());
assertEquals(1, book2.getAuthors().size());
}
4.2.從非所有者實體中移除
從不是關係所有者的實體中刪除記錄時,我們應該注意手動刪除關係:
for (Book book : author1.getBooks()) {
book.getAuthors().remove(author1);
}
entityManager.remove(author1);
此外,讓我們將代碼放在方法中並使用@PreRemove
註釋對其進行註釋:
@PreRemove
private void removeBookAssociations() {
for (Book book: this.books) {
book.getAuthors().remove(this);
}
}
在刪除實體之前,JPA 將執行此方法內的所有內容。
最後,讓我們創建一個測試來檢查功能是否正常工作:
void givenEntities_whenRemoveFromNotOwner_thenRemoveAssociation() {
Author author = (Author) entityManager
.createQuery("SELECT author from Author author where author.name = ?1")
.setParameter(1, "Ralph Johnson")
.getSingleResult();
Book book1 = entityManager.find(Book.class, 1L);
Book book2 = entityManager.find(Book.class, 2L);
entityManager.remove(author);
assertEquals(3, book1.getAuthors().size());
assertEquals(0, book2.getAuthors().size());
}
5.結論
在本文中,我們學習瞭如何從 JPA 中的多對多關係中刪除實體。
像往常一樣,這些示例可以在 GitHub 上找到。