Hibernate Tips: The best way to remove entities from a many-to-many association



Get access to all my video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.

Join the Persistence Hub!


Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please post a comment below.

Question:

In a comment on one of my previous articles, I was asked the following question:

What’s the best way to remove entities from a many-to-many association?

Solution:

The mapping of a many-to-many association seems to be easy. But there are a few rules you need to follow to avoid common pitfalls:

  1. You should map the association to a Set.
  2. You should not use CascadeType.REMOVE.
  3. You should provide utility methods to add entities to and to remove them from a bidirectional association.
  4. You need to clean up all associations if you remove the entity that doesn’t own the association.

Let’s take a quick look at all 4 rules.

1. Use a Set instead of a List

Mapping a many-to-many association to a List might seem like the most intuitive approach. But as I showed in a previous article, Hibernate removes association entries very inefficiently, if you map it as a List.

Instead of deleting the removed association, it first removes all entries from the association table and then adds the remaining ones. That’s obviously not the most effective approach.

You can avoid it by mapping your many-to-many association to a Set.

@Entity
public class Book {
 
    @ManyToMany
    @JoinTable(name = "book_author", 
            joinColumns = { @JoinColumn(name = "fk_book") }, 
            inverseJoinColumns = { @JoinColumn(name = "fk_author") })
    private Set<Author> authors = new HashSet<Author>();
     
    ...
}

Check out the following article to dive deeper into Hibernate’s different options to map a to-many association and the differences between a mapping as a List, Set or Bag:

How to Choose the Most Efficient Data Type for To-Many Associations – Bag vs. List vs. Set.

2. Don’t use CascadeType.REMOVE

Using CascadeType.REMOVE on a many-to-many association removes more entities than you probably expect. Hibernate removes all associated entities, even if they are associated with other entities.

Let’s say you model a many-to-many association using CascadeType.REMOVE between your Author and Book entity. If you then remove an Author who wrote a Book on her own and coauthored a second Book, Hibernate removes both Books when you delete the Author.

And it gets even worse if you also activate CascadeType.REMOVE on the BookAuthor association. Hibernate would then cascade the remove operation until it removed all associated Author and Book entities. This can easily remove a huge part of your database.

I explained the pitfalls of CascadeType.REMOVE in much more details in:

Why you should avoid CascadeType.REMOVE for to-many associations and what to do instead.

3. Provide utility methods for bidirectional associations

If you model a bidirectional many-to-many association, you need to make sure to always update both ends of the association. To make that a little bit easier, it’s a general best practice to provide utility methods that add entities to and remove them from the association.

@Entity
public class Author {
 
    @ManyToMany(mappedBy = "authors")
    private List<Book> books = new ArrayList<Book>();
 
    ...
     
    public void addBook(Book book) {
        this.books.add(book);
        book.getAuthors().add(this);
    }
 
    public void removeBook(Book book) {
        this.books.remove(book);
        book.getAuthors().remove(this);
    }
}

You can find more best practices for many-to-many mappings in:

Best Practices for Many-to-Many Associations with Hibernate and JPA

4. Cleanup association when removing a referencing entity

A bidirectional association mapping consists of an owning and a referencing part. The owning part defines the association, and the referencing part reuses the existing mapping.

For a many-to-many association, both parts might seem identical. But they are handled differently if you remove an entity.

If you remove an entity that owns the association, Hibernate also removes all its associations. But it doesn’t do that if you delete an entity that references the entity. You then need to remove all associations yourself before you can remove the entity.

Author author = em.find(Author.class, authorId);
for (Book book : author.getBooks()) {
	author.removeBook(book);
}
em.remove(author);

Learn more:

If you want to learn more about Hibernate’s and JPA’s association mapping capabilities, you should also read the following articles:

 

Hibernate Tips Book







Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.

It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.

Get it now!


Related Articles

Responses

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  1. Thank you for this article.

    I think you have a typo in the Chapter 1 ” Use a Map instead of a List”. I guess you mean “Use a Set instead of a List”

    1. You’re absolutely right. I fixed it.

      Thanks,
      Thorben