Why do Hibernate remove all elements in set, if i remove all elements of another set in the same class?

I have two classes: Book and Member. In Member class, there is two HashSet which stores Books: borrowedBooks and returnedBooks. If i want to remove one Book from borrowedBooks and put it in returnedBook, will Hibernate do that without problem till the last element in borrowedBooks. But if i remove the last element in borrowedBooks, than remove Hibernate also all of the Books in returnedBook. So, at the end of scenario there is no more Book in borrowedBooks but also in returnedBooks… Like that:

a) borrowedBooks: a, b, c

a) returnedBooks:


b) borrowedBooks: a, b

b) returnedBooks: c


c) borrowedBooks: a

c) returnedBooks: b, c


d) borrowedBooks: -

d) returnedBooks: -


Thats really not understandable! Thanks a lot for your helps! Here are my classes:

@Entity public class Book {

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

getters / setters

@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 = "Book_Barcode")
private int id;

@OneToMany(fetch = FetchType.EAGER)
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");
        currentSession.update(book);

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

@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);
        currentSession.update(newMember);

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

So, there is no problem in public void borrowBook(…)
There is something wrong in public void returnBook(…)!!!
I spent a lot of time, but i did find any way… Thanks in advance!

There are many things that are wrong in your code, which could have been easily prevented by reading the Hibernate User Guide.

Using EAGER fetching is a bad idea anyway, but using it for 3 collections is going to generate a huge Cartesian Product which is going to cause a significant performance issue.

@OneToMany(fetch = FetchType.EAGER)
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<>();

You’re using primitive-based identifiers:

private long barcode;

You’re using the TABLE generator which is also the worst identifier generator to choose from.

Your hashCode relies on the identifier which might change after the entities get persisted. Check out this article for more details about how to write equals and hashCode when using the entity identifier only.

This line:

currentSession.update(newMember);

is useless. The Session#update is not for updating a managed entity. It’s for scheduling an update for detached one. You can remove it and HIbernate will still update the newMember since it’s a manged entity.

This one:

currentSession.update(book);

might be replaced with merge which is safer if the entity is already attached to the current running Session.

So, there is no problem in public void borrowBook(…)
There is something wrong in public void returnBook(…)!!!

Did you write a unit test to validate the logic? After you add the unit test, you have to post it in your question as well.

Thanks for your suggestions!
I did not know that TABLE generator is not good and also about “update”.
I know that EAGER fetching is not good, but it’s not that important right now. There is another big problem which i can not solve for 2 days. I didn’t right any unit test, but i tested it myself.

borrowBook method does everything i want. But in method “returnBook()” when the last element in set “borrowBook” is deleted, all elements in set “returnBook” deleted also.
hashCode will not change, because the barcode (which is ID) will not change.

That’s how many problems start. You need to write the unit test now, and after you’re done, you will have to add it here along with the SQL statements executed by Hibernate, so we get a better understanding of what happens behind the scenes.