1. 概述
在这个教程中,我们将学习如何在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生成一条删除语句来从关联表中移除关联。
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上找到。