我有两个班级:图书和会员。在成员类中,有两个存储书的HashSet:borrowedBooks
和returnedBooks
。
当我从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 =?” ???
答案 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。