Не уверен, что это правильный ответ, но в вашем коде есть две вещи:
Сеансы / Трансакитональные границы
Ваш контроллер не является транзакционным (что нормально).
@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
, по крайней мере, без его повторного подключения.
(Ну, на самом деле мой совет - отправить весь контроллер на одинтранзакция и сеанс гибернации, а не две, и не нужно беспокоиться о подобных вещах).
Что значит «смешно»?Основная проблема в том, что hibernate гарантирует вам, что в одном сеансе любой дескриптор, который он дает вам постоянному объекту (книге, члену), такой же, как в: они равны ==
.Если вы находитесь в одном сеансе, тогда bookService.load(id) == bookService.load(id)
.Это не так в вашей текущей реализации контроллера, поэтому объекты могут или не могут быть одинаковыми.
Что является своего рода вероятностным, потому что ... выпуск 2
HashCode / Equals design
Ваш хэш-код и равно не согласованы с разумным намерением.По сути, ваша книга hashCode
является хешем штрих-кода.И ваши равные это ==
сравнение на штрих-коде.Могу поспорить, что вы имели в виду .equals()
, но вы написали "==".
Поэтому, если две книги не имеют одинаковый экземпляр String (крайне маловероятно), они никогда не будут равны.
Так чтопроисходит?
Ну, вы получаете книгу в спящем сеансе, вы получаете экземпляр, который я назову Book @ 1, со штрих-кодом "123", который является экземпляром String, который я назову String @ 1.
Затем вы закрываете сеанс гибернации и вводите другой.
В этом сеансе вы загружаете элемент Member @ 1, у которого есть набор книг, который загружается в режиме гибернации,но вы находитесь в новой сессии.Таким образом, hibernate каждый раз загружает новый экземпляр книги, и в итоге вы получаете экземпляр Book Book 2 со штрих-кодом «123», который равен String @ 2 (строки содержат одинаковые символы, они .equals
, но не ==
).
Таким образом, вы удаляете Книгу @ 1 из набора книг Участника @ 1.Что делает реализация?Похоже, что Book @ 2 и Book @ 1 совпадают..hashcode()
мудрый, они есть..equals()
мудрый?Они не.@ Book1 не является @ Book2 и не равен ему, поэтому он не удаляется.
Другое следствие состоит в том, что все, что вы делаете с Book @ 1, не отслеживается hibernate, потому что сеанс, который создал Book@ 1 закрыто.Таким образом, это фактически становится серой областью: что, если вы добавите Книгу @ 1 к Участнику @ 1.Как спящий может знать, что это на самом деле не новая книга, которую вы только что создали?Должен ли он это знать вообще?
Что теперь?
Исправьте свои равные.Хорошо, что у вас есть «естественный ключ» для книг, и вы его используете, но вы, вероятно, использовали ==
там, где вы должны были использовать .equals()
Иметь правильные границы транзакций.Hibernate будет работать намного лучше, если вы не будете тратить время на создание ситуаций, когда у вас разные экземпляры одного и того же объекта.
Правильное именование пользователя: то, что вы называете MemberDAO, не является DAO.DAO получает доступ и сохраняет объекты в соответствии с шаблонами запросов.Здесь ваш MemberDAO манипулирует различными объектами в соответствии с бизнес-логикой (например, если я верну книгу, увеличу счетчик доступности).Это не работа DAO, это работа службы.Правильное именование обычно помогает вам установить правильные границы транзакций, что поможет правильно разграничить сеанс.(Например, обычно подозрительно иметь @Transactionnal в реализации DAO, если только вы не «срезаете углы». Это случается, если вы пишете действительно простой CRUD-сервис и хотите сэкономить время, но когда бизнес-логика подкрадывается, этоЗапах кода, чтобы иметь @Transactionnal DAO).
И, наконец, используйте пошаговый отладчик. Hibernate обычно не отправляет SQL, который не соответствует состоянию экземпляров вашего объекта.