2011-04-19

@ManyToMany - delete inverse side object

假設兩個有著多對多關係的 class 如下:
@Entity
@SuppressWarnings("serial")
public class Author extends CommonVersionEntity {

 private Set<Book> bookSet = new HashSet<Book>();

 @ManyToMany
 @JoinTable(name = "author_book", joinColumns = {
  @JoinColumn(name = "author_id")
 }, inverseJoinColumns = {
  @JoinColumn(name = "book_id")
 }, uniqueConstraints = @UniqueConstraint(columnNames = {
   "author_id", "book_id"
 }))
 @ForeignKey(name = "author_book_fk", inverseName = "book_author_fk")
 public Set<Book> getBookSet() {
  return bookSet;
 }

 public void setBookSet(Set<Book> bookSet) {
  this.bookSet = bookSet;
 }
}

@Entity
@SuppressWarnings("serial")
public class Book extends CommonVersionEntity {

 private Set<Author> authorSet = new HashSet<Author>();

 @ManyToMany(mappedBy = "bookSet")
 public Set<Author> getAuthorSet() {
  return authorSet;
 }

 public void setAuthorSet(Set<Author> authorSet) {
  this.authorSet = authorSet;
 }
}

Author 是 owner side,Book 是 inverse side,join table 為 author_book。

遇到的問題是,當直接刪除一筆 Book 時,會發生以下的 error:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`db`.`author_book`, CONSTRAINT `book_author_fk` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`))

簡單說,就是因為 Book 是 inverse side ,所以對 Book 做的任何操作都不會反應到 Author 與 Book 的關聯上,也就是 join table author_book,所以導致 author_book上的 book_author_fk 發出抗議,因為 Book 被刪除,而該 Book id 還留在 author_book 上。

如果單純將 Author 上的 @ForeignKey 拿掉,那會造成 Book 順利刪除,但是該 Book id 還留在 author_book 上,導致下次載入 Author 時發生錯誤,當然可以將錯就錯的在 public Set<Book> getBookSet() { ... } 加上 @NotFound(action = NotFoundAction.IGNORE) 來迴避這樣的問題,唯一的後果就是在 author_book 留下一堆垃圾資料。

不然就是得在刪除 Book 時多做一些事情:
Book obj = this.getBook(id);
for (Author author : obj.getAuthorSet()) {
    author.getBookSet().remove(obj);
}
this.getHibernateTemplate().delete(obj);
將 Book 的每位 Author 找出來,再從 Author 這個 owner side 去移除與該 Book 的關聯。

沒有留言:

張貼留言