Почему Hibernate удаляет все элементы в наборе, если я удаляю все элементы другого набора в том же классе? - PullRequest
0 голосов
/ 18 сентября 2018

У меня есть два класса: Book и Member. В классе Member есть два HashSet, в которых хранятся книги: borrowedBooks и returnedBooks.

Когда я удаляю одну Книгу из borrowedBooks и помещаю ее в returnedBook, Hibernate сделает это без проблем для всех, кроме последнего элемента в borrowedBooks. Однако, если я удаляю последний элемент в * 1008, * Hibernate также удаляет все книги в returnedBook. Итак, в конце сценария нет книг в 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(...)? Я провел много времени, но я не мог найти способ ... Заранее спасибо!

================================ Что-то не так с Hibernate! Например: если у меня есть 3 книги в заимствованных книгах и если я пытаюсь их вернуть, удалите их из заимствованных книг и вставьте в возвращенную книгу.

ПЕРВЫЙ ВОЗВРАТ:

Hibernate: обновление книги установить сумму = ?, availableAmount = ?, bookTitle = ?, loanedDate = ?, description = ?, editedDate = ?, edition = ?, isbn = ?, выпустилDate = ?, language = ?, page = ?, price = ?, publisher = ?, registrationDate = ?, renewedDate = ?, renewedTimes = ?, status =? где штрих-код =?

Hibernate: вставка в Member_Book (Member_id, возвращенный Book__код) значения (?,?)

Hibernate: удалить из Member_Book где Member_id =? и позаимствовал Books_barcode =?

ВТОРОЙ ВОЗВРАТ:

Спящий режим: Обновление книги Задать сумму = ?, availableAmount = ?, bookTitle = ?, loanedDate = ?, description = ?, editedDate = ?, edition = ?, isbn = ?, выпустилDate = ?, language = ?, page = ?, price = ?, publisher = ?, registrationDate = ?, renewedDate = ?, renewedTimes = ?, status =? где штрих-код =?

Hibernate: вставка в Member_Book (Member_id, возвращенный Book__код) значения (?,?)

Hibernate: удалить из Member_Book где Member_id =? и позаимствовал Books_barcode =?

Третий возврат:

Hibernate: обновление книги заданная сумма = ?, availableAmount = ?, bookTitle = ?, loanedDate = ?, description = ?, editedDate = ?, edition = ?, isbn = ?, выпустилDate = ?, language = ?, page = ?, price = ?, publisher = ?, registrationDate = ?, renewedDate = ?, renewedTimes = ?, status =? где штрих-код =?

Hibernate: вставка в Member_Book (Member_id, возвращенный Book__код-код) значения (?,?)

Hibernate: удалить из Member_Book где Member_id =?

ПОЖАЛУЙСТА, СМОТРИТЕ НА "ПОСЛЕДНЮЮ" ОПЕРАЦИЮ УДАЛИТЬ: удалить из Member_Book, где Member_id =? ПОЧЕМУ ПРОСТО "где Member_id =?" ???

1 Ответ

0 голосов
/ 19 сентября 2018

Не уверен, что это правильный ответ, но в вашем коде есть две вещи:

Сеансы / Трансакитональные границы

Ваш контроллер не является транзакционным (что нормально).

@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, который не соответствует состоянию экземпляров вашего объекта.

...