如果我删除同一类中另一个集合的所有元素,为什么Hibernate删除集合中的所有元素?

时间:2018-09-18 09:58:19

标签: java hibernate one-to-many

我有两个班级:图书和会员。在成员类中,有两个存储书的HashSet:borrowedBooksreturnedBooks

当我从borrowedBooks中删除一本Book并将其放入returnedBook中时,Hibernate会做到这一点,除了borrowedBooks中的最后一个元素之外,其他所有内容都没有问题。但是,如果我删除了borrowedBooks中的最后一个元素,Hibernate也将删除returnedBook中的所有Book。因此,在方案结束时,borrowedBooks中没有书,但是returnedBooks中也没有书。

例如:

1) borrowedBooks: a, b, c
1) returnedBooks:
---
2) borrowedBooks: a, b
2) returnedBooks: c
---
3) borrowedBooks: a
3) returnedBooks: b, c
---
4) borrowedBooks: -
4) returnedBooks: -

那真是难以理解!为什么会这样?非常感谢你的帮助! 这是我的课程:

@Entity
public class Book {

@Id
@TableGenerator(...)
@GeneratedValue(generator = "Book_Barcode")
private long barcode;
private long isbn=0;
private String bookTitle="";
// some other fields

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (int) (barcode ^ (barcode >>> 32));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Book other = (Book) obj;
    if (barcode != other.barcode)
        return false;
    return true;
}

}



@Entity
public class Member {

@Id
@TableGenerator(...)
@GeneratedValue(generator = "Member_Id")
private int id;

@OneToMany(fetch = FetchType.EAGER) //please ignore FetchType!
private Set<Book> returnedBooks = new HashSet<>();

@OneToMany(fetch = FetchType.EAGER)
private Set<Book> borrowedBooks = new HashSet<>();

@OneToMany(fetch = FetchType.EAGER)
private Set<Book> renewedBooks = new HashSet<>();
}

@Repository
public class MemberDAOImpl implements MemberDAO {

@Autowired
private SessionFactory sessionFactory;

@Override
@Transactional
public void borrowBook(Member member, Book book) {
    if (book.getStatus().equals("Available") && 
        book.getAvailableAmount() > 0) {

        Session currentSession = sessionFactory.getCurrentSession();

        book.setBorrowedDate(new Date());
        book.setAvailableAmount(book.getAvailableAmount() - 1);
        book.setStatus("Borrowed");

        Member newMember = currentSession.find(Member.class, member.getId());
        newMember.getBorrowedBooks().add(book);
    } 
}

@Override
@Transactional
public void returnBook(Member member, Book book) {
    if (book.getStatus().equals("Borrowed")) {

        Session currentSession = sessionFactory.getCurrentSession();

        Member newMember = currentSession.find(Member.class, member.getId());
        newMember.getReturnedBooks().add(book);
        newMember.getBorrowedBooks().remove(book);

        book.setBorrowedDate(null);
        book.setAvailableAmount(book.getAvailableAmount() + 1);
        book.setStatus("Available");
        book.setRenewedTimes(0);
    } 
}
}

@Controller
@RequestMapping("/member")
public class MemberController {

static Member member;

@Autowired
private MemberService memberService;

@Autowired
private BookService bookService;

@GetMapping("/bookBorrow")
public String bookBorrow(@RequestParam("barcode") long barcode, Model model) {
    Book book = bookService.searchBookByBarcode(barcode);
    memberService.borrowBook(member, book);
    model.addAttribute("booksList", member.getBorrowedBooks());
    model.addAttribute("member", member);
    return "member-specific-home-page";
}

@GetMapping("/bookReturn")
public String bookReturn(@RequestParam("barcode") long barcode, Model model) {
    Book book = bookService.searchBookByBarcode(barcode);
    memberService.returnBook(member, book);
    model.addAttribute("booksList", member.getReturnedBooks());
    model.addAttribute("member", member);
    return "member-specific-home-page";
}


}

因此,我相信public void borrowBook(...)中没有问题。 public void returnBook(...)中有问题吗? 我花了很多时间,但找不到方法...谢谢!

================================ 休眠有问题! 例如:如果我在rowedBooks中设置了3本书,并且比我尝试退还它们的书还好:那么请从rowedBooks中删除并放入returnedBook。

首次返回:

休眠:更新Book 设置数量= ?​​、 availableAmount = ?、 bookTitle = ?、 browedDate = ?、 description = ?、 editedDate = ?、 edition = ?、 isbn = ?、 issueDate =? ,语言= ?、页面= ?、价格= ?、发布者= ?、 registrationDate = ?、 renewedDate = ?、 renewedTimes = ?、 status =?条形码在哪里?

休眠:插入Member_Book (Member_id,returnedBooks_barcode)值(?,?)

休眠:从MemberBook中删除,其中Member_id =?和rowedBooks_barcode =?

第二次返回:

休眠:更新Book 设置数量= ?​​、 availableAmount = ?、 bookTitle = ?、 browedDate = ?、 description = ?、 editedDate = ?、 edition = ?、 isbn = ?、 issueDate =? ,语言= ?、页面= ?、价格= ?、发布者= ?、 registrationDate = ?、 renewedDate = ?、 renewedTimes = ?、 status =?条形码在哪里?

休眠:插入Member_Book (Member_id,returnedBooks_barcode)值(?,?)

休眠:从MemberBook中删除,其中Member_id =?和rowedBooks_barcode =?

第三次返回:

休眠:更新Book 设置数量= ?​​、 availableAmount = ?、 bookTitle = ?、 browedDate = ?、 description = ?、 editedDate = ?、 edition = ?、 isbn = ?、 issueDate =? ,语言= ?、页面= ?、价格= ?、发布者= ?、 registrationDate = ?、 renewedDate = ?、 renewedTimes = ?、 status =?条形码在哪里?

休眠:插入Member_Book (Member_id,returnedBooks_barcode)值(?,?)

休眠:从Member_Book中删除,其中Member_id =?

请查看“最后”删除操作:从Member_Book中删除,其中Member_id =? 为什么只是“ where Member_id =?” ???

1 个答案:

答案 0 :(得分:0)

不确定这是否是正确的答案,但是代码中有两件事:

会话/跨耳界

您的控制者不是事务处理者(可以)。

@GetMapping("/bookBorrow")
public String bookBorrow(@RequestParam("barcode") long barcode, Model model) {
    Book book = bookService.searchBookByBarcode(barcode);
    memberService.returnBook(member, book);
    //...

您的memberService.returnBook()是:

@Transactional
public void returnBook(Member member, Book book) {

因此我们可以推断出,对于单个HTTP请求(向您的控制者进行调用),有一个bookService.searchBook...处于休眠状态的会话,而在memberService.returnBook中有一个另一个

当您有两个共享相同实体的休眠会话时,会发生“有趣”的事情。您不应该使用从book内部的bookService获得的memeberService实例,至少不要重新连接它。

(实际上,我的建议是让您的整个控制者分派到一个事务和休眠会话中,而不是两个,并且不必担心类似的事情)。

“有趣”是什么意思?主要问题在于,休眠确保您在单个会话中为持久对象(书,成员)提供的任何句柄都相同,就像:==。如果您处于单个会话中,则bookService.load(id) == bookService.load(id)。当前的控制器实现中情况并非如此,因此对象可能相同也可能不同。

那是种语言,因为...问题2

哈希码/等于设计

您的hashCode和equals与合理的意图不匹配。基本上,您的书hashCode是条形码的哈希值。您的等值是条形码上的==比较。我敢打赌,您的意思是.equals(),但您写的是“ ==”。

因此,除非两本书具有相同的条形码String实例(极不可能),否则它们永远不会相等。

那会发生什么?

好吧,您在休眠会话中获取一本书,您得到一个我称为Book @ 1的实例,其条形码为“ 123”,这是我称为String @ 1的String实例。

然后关闭休眠会话,然后输入另一个会话。

在此会话中,您加载一个成员Member @ 1,该成员具有一组休眠的书籍,但该帐户处于休眠状态,但是您处于新的会话中。因此,hibernate每次都加载一个新的Book实例,最终得到Book实例Book @ 2,其条形码“ 123”为String @ 2(字符串具有相同的字符,它们是.equals,但它们是不是==)。

因此,您从Member @ 1的书本集中删除Book @ 1。该实现有什么作用?看起来Book @ 2和Book @ 1是否相同。 .hashcode()是明智的。 .equals()明智吗?他们不是。 @ Book1不是@ Book2,并且不等于@ Book2,因此不会将其删除。

另一个结果是,休眠状态不会跟踪您对Book @ 1所做的任何操作,因为创建Book @ 1的会话已关闭。因此,它实际上变成了灰色区域:如果将Book @ 1添加到Member @ 1,该怎么办。冬眠如何知道这实际上不是您刚刚创建的新书?它应该知道吗?

现在呢?

修正自己的问题。拥有书籍的“自然键”并使用它是很好的,但是您可能在应该使用==

的地方使用了.equals()

具有适当的交易界限。如果您不花时间创建具有相同实体的不同实例的情况,则Hibernate会更好地工作。

用户适当的命名:所谓的MemberDAO不是DAO。 DAO根据查询模式访问和存储对象。在这里,您的MemberDAO根据业务逻辑操作不同的实体(例如,如果我退还书本,请增加一个可用性计数器)。这不是DAO的工作,而是服务的工作。正确的命名通常将帮助您设置适当的事务边界,这将有助于正确的会话划分。 (例如,除非您“偷工减料”,否则在DAO实现上使用@Transactionnal通常是可疑的。如果您编写了一个非常简单的CRUD服务并希望节省时间,则会发生这种情况,但是当业务逻辑潜入时,代码闻起来有@Transactionnal DAO)。

最后,使用逐步调试器。 Hibernate通常不会发送与对象实例状态不匹配的SQL。