我深入研究了如何只删除多对多关联而不删除任何一个实体,我发现的所有内容都是关于如何删除一个实体而不级联删除另一个实体或整个表的教程或示例(当使用 CascadeType.ALL 时)。
所以:我有两个表和一个保存它们之间关系的多对多表。
authors( int author_id, varchar author_name )
books( int book_id, varchar book_name )
author_x_book( int author_id, int book_id )
一个作者可以有很多本书,一本书可以有很多作者。我没有进入有关主键和外键的详细信息以及为什么我选择该设计等。
JPA实体:
@Entity
@Table(name = "authors")
@Data
public class Author implements Serializable {
@Id
@Column(name = "author_id", nullable = false)
private Long id;
@Column(name = "author_name")
private String name;
@ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
@JoinTable(name = "author_x_book",
joinColumns = @JoinColumn(name = "author_id", referencedColumnName = "author_id"),
inverseJoinColumns = @JoinColumn(name = "book_id", referencedColumnName = "book_id"))
private List<Book> books;
}
@Entity
@Table(name = "books")
@Data
public class Book implements Serializable {
@Id
@Column(name = "book_id", nullable = false)
private Long id;
@Column(name = "book_name")
private String name;
@ManyToMany(mappedBy = "books", fetch = FetchType.EAGER,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private List<Author> authors;
}
我试图只删除 author_x_book 中的条目——也就是说,删除特定书籍和特定作者之间的关联。我不打算既不删除作者也不删除这本书,因为这本书可能还有其他作者,而作者可能还有其他一些书。
我试图从 Book.authors 中删除作者,从 Author.books 中删除这本书,然后保留这本书和作者。
@Transactional
public void deleteAssociation(String authorName, String bookName)
{
Author author = authorRepository.findFirstByName(authorName);
Book book = bookRepository.findFirstByName(bookName);
deleteAssociation(author, book);
}
private void deleteAssociation(Author author, Book book) {
book.getAuthors().remove(author);
author.getBooks().remove(book);
authorRepository.save(author);
bookRepository.save(book);
}
不工作。它试图删除所有作者与其所有书籍的关联。然后它会尝试将该关联添加回去。 SQL日志:
delete
from
"public"."author_x_book"
where
"author_id"=?
insert
into
"public"."author_x_book" ("author_id", "book_id")
values
(?, ?)
然后我使用 @EmbeddedId 和 AuthorXBookRepository 创建了一个 AuthorXBook 实体。
@Data
@Entity
@Table(name = "author_x_book")
public class AuthorXBook implements Serializable {
@EmbeddedId
private AuthorXBookId id;
@Data
@Embeddable
public static class AuthorXBookId implements Serializable {
@Column(name = "author_id", nullable = false)
private final long authorId;
@Column(name = "book_id", nullable = false)
private final long bookId;
}
}
@Repository
public interface AuthorXBookRepository extends CrudRepository<AuthorXBook, AuthorXBook.AuthorXBookId> {
}
调用 AuthorXBookRepository.delete(authorXBook) 时:
private void deleteAssociation(Author author, Book book) {
authorXBookRepository.delete(new AuthorXBook.AuthorXBookId(author.getId(), book.getId()));
book.getAuthors().remove(author);
author.getBooks().remove(book);
authorRepository.save(author);
bookRepository.save(book);
}
它执行的是选择,而不是删除,然后通过删除作者的所有书籍并重新添加关联来继续:
select
authorxboo0_."author_id" as author_1_20_0_,
authorxboo0_."book_id" as book_2_20_0_
from
"public"."author_x_book" authorxboo0_
where
authorxboo0_."author_id"=?
and authorxboo0_."book_id"=?
delete
from
"public"."author_x_book"
where
"author_id"=?
insert
into
"public"."author_x_book" ("author_id", "book_id")
values
(?, ?)
我最终创建了一个@Modifying @Query 来删除关联:
@Repository
public interface AuthorXBookRepository extends CrudRepository<AuthorXBook, AuthorXBook.AuthorXBookId> {
@Modifying
@Query("delete from #{#entityName} x where x.authorId=:aid and x.bookId=:bid")
void delete(@Param("aid") long authorId, @Param("bid") long bookId);
}
private void deleteAssociation(Author author, Book book) {
authorXBookRepository.delete(author.getId(), book.getId());
book.getAuthors().remove(author);
author.getBooks().remove(book);
authorRepository.save(author);
bookRepository.save(book);
}
成功了。它只删除关联。并且不尝试将其添加回去,我猜是因为@Modifiable 清除了 EntityManager。
所以,两个问题:
为什么我的第一次和第二次尝试没有成功?
仅删除多对多关联而不删除任何实体的正确方法是什么?
谢谢
在所有者端使用 Set 而不是 List
@Entity
@Table(name = "authors")
@Data
public class Author implements Serializable {
@Id
@Column(name = "author_id", nullable = false)
private Long id;
@Column(name = "author_name")
private String name;
@ManyToMany(fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Set<Book> books; // use Set instead of List
}
当只删除关系时,在删除彼此的引用后保存所有者“作者”就足够了,
@Transactional
public void deleteAssociation(String authorName, String bookName)
{
Author author = authorRepository.findFirstByName(authorName);
Book book = bookRepository.findFirstByName(bookName);
deleteAssociation(author, book);
}
private void deleteAssociation(Author author, Book book) {
book.getAuthors().remove(author);
author.getBooks().remove(book);
authorRepository.save(author); // saving Owner is enought
// bookRepository.save(book);
}
Entity Author 和 Book 需要分别覆盖这两个函数
public boolean equals(Object o) {
// compare by their Id
}
public int hashCode() {
return Objects.hashCode(getClass());
}