DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 1)
  • Why Should Databases Go Natural?
  • SQL Interview Preparation Series: Mastering Questions and Answers Quickly
  • Keep Calm and Column Wise

Trending

  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  • Java Virtual Threads and Scaling
  • Evolution of Cloud Services for MCP/A2A Protocols in AI Agents
  • AI, ML, and Data Science: Shaping the Future of Automation
  1. DZone
  2. Data Engineering
  3. Databases
  4. Why Set Is Better Than List in @ManyToMany

Why Set Is Better Than List in @ManyToMany

In this article, we'll highlight the performance penalties involves by using List or Set in @ManyToMany relationships.

By 
Anghel Leonard user avatar
Anghel Leonard
DZone Core CORE ·
Jun. 10, 20 · Opinion
Likes (6)
Comment
Save
Tweet
Share
22.9K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, we'll highlight the performance penalties involves by using List or Set in @ManyToMany relationships. We use Spring Data JPA with the default persistence provider, therefore with Hibernate JPA.

First of all, keep in mind that Hibernate deals with @ManyToMany relationships as two unidirectional @OneToMany associations. The owner-side and the child-side (the junction table) represents one unidirectional @OneToMany association. On the other hand, the non-owner-side and the child-side (the junction table) represent another unidirectional @OneToMany association. Each association relies on a foreign key stored in the junction table.

In the context of this statement, the entity removal (or reordering) results in deleting all junction entries from the junction table and reinserts them to reflect the memory content (the current Persistence Context content).

Using List

Let’s assume that Author and Book involved in a bidirectional lazy @ManyToMany association are mapped via java.util.List, as shown here (only the relevant code is listed):

Java
 




x


 
1
@Entity
2
public class AuthorList implements Serializable {
3
  ...
4
  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
5
  @JoinTable(name = "author_book_list",
6
             joinColumns = @JoinColumn(name = "author_id"),
7
             inverseJoinColumns = @JoinColumn(name = "book_id")
8
            )
9
  private List<BookList> books = new ArrayList<>();
10
  ... 
11
  public void removeBook(BookList book) {
12
    this.books.remove(book);
13
    book.getAuthors().remove(this);
14
  }
15
}


Java
 




xxxxxxxxxx
1


 
1
@Entity
2
public class BookList implements Serializable {
3
  ...
4
  @ManyToMany(mappedBy = "books")
5
  private List<AuthorList> authors = new ArrayList<>();
6
  ...
7
}



Further, consider the data snapshot shown in the figure below:

author_list

The goal is to remove the book called One Day (the book with the ID of 2) written by the author, Alicia Tom (the author with ID 1). Considering that the entity representing this author is stored via a variable named alicia, and the book is stored via a variable named oneDay, the deletion can be done via removeBook() as follows: 

Java
 




xxxxxxxxxx
1


 
1
alicia.removeBook(oneDay);



The SQL statements triggered by this deletion are:

SQL
 




xxxxxxxxxx
1
11


 
1
DELETE FROM author_book_list
2
WHERE author_id = ?
3
Binding: [1]
4
    
5
INSERT INTO author_book_list (author_id, book_id)
6
VALUES (?, ?)
7
Binding: [1, 1]
8
    
9
INSERT INTO author_book_list (author_id, book_id)
10
VALUES (?, ?)
11
Binding: [1, 3]



So, the removal didn’t materialize in a single SQL statement. Actually, it started by deleting all junction entries of alicia from the junction table. Further, the junction entries that were not the subject of removal were reinserted to reflect the in-memory content (Persistence Context). The more junction entries reinserted, the longer the database transaction.

Using Set

Consider switching from List to Set as follows:

Java
 




x


 
1
@Entity
2
public class AuthorSet implements Serializable {
3
  ...
4
  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
5
  @JoinTable(name = "author_book_set",
6
             joinColumns = @JoinColumn(name = "author_id"),
7
             inverseJoinColumns = @JoinColumn(name = "book_id")
8
            )
9
  private Set<BookSet> books = new HashSet<>();
10
  ...
11
  public void removeBook(BookSet book) {
12
    this.books.remove(book);
13
    book.getAuthors().remove(this);
14
  }  
15
}


Java
x
 
1
@Entity
2
public class BookSet implements Serializable {
3
  ...
4
  @ManyToMany(mappedBy = "books")
5
  private Set<AuthorSet> authors = new HashSet<>();
6
  ...
7
}


This time, calling alicia.removeBook(oneDay) will trigger the following SQL DELETE statement:

SQL
 




xxxxxxxxxx
1


 
1
DELETE FROM author_book_set
2
WHERE author_id = ?
3
  AND book_id = ?
4
Binding: [1, 2]



The source code is available on GitHub. This is much better since a single DELETE statement is needed to accomplish the job.

Preserving the Order of the ResultSet

It’s a well-known fact that java.util.ArrayList preserves the order of inserted elements (it has precise control over where in the list each element is inserted), while java.util.HashSet doesn’t. In other words, java.util.ArrayList has a predefined entry order of elements, while java.util.HashSet is, by default, unordered.

There are at least two ways to order the result set by the given columns defined by JPA specification:

  • Use @OrderBy to ask the database to order the fetched data by the given columns (appends the ORDER BY clause in the generated SQL query to retrieve the entities in a specific order) and Hibernate to preserve this order.
  • Use @OrderColumn to permanently order this via an extra column (in this case, stored in the junction table).

This annotation (@OrderBy) can be used with @OneToMany/@ManyToMany associations and @ElementCollection. Adding @OrderBy without an explicit column will result in ordering the entities ascending by their primary key (ORDER BY author1_.id ASC). Ordering by multiple columns is possible as well (e.g., order descending by age and ascending by name, @OrderBy("age DESC, name ASC"). Obviously, @OrderBy can be used with java.util.List as well.

Using @OrderBy

Consider the data snapshot from the below figure:

author lists

There is a book written by six authors. The goal is to fetch the authors in descending order by name via Book#getAuthors(). This can be done by adding @OrderBy in Book, as shown here:

Java
 




xxxxxxxxxx
1


 
1
@ManyToMany(mappedBy = "books")
2
@OrderBy("name DESC")
3
private Set<Author> authors = new HashSet<>();



When getAuthors() is called, the @OrderBy will:

  • Attach the corresponding ORDER BY clause to the triggered SQL. This will instruct the database to order the fetched data.
  • Signal to Hibernate to preserve the order. Behind the scenes, Hibernate will preserve the order via a LinkedHashSet.

So, calling getAuthors() will result in a Set of authors conforming to the @OrderBy information. The triggered SQL is the following SELECT containing the ORDER BY clause:

SQL
 




xxxxxxxxxx
1
12


 
1
SELECT
2
  authors0_.book_id AS book_id2_1_0_,
3
  authors0_.author_id AS author_i1_1_0_,
4
  author1_.id AS id1_0_1_,
5
  author1_.age AS age2_0_1_,
6
  author1_.genre AS genre3_0_1_,
7
  author1_.name AS name4_0_1_
8
FROM author_book authors0_
9
INNER JOIN author author1_
10
  ON authors0_.author_id = author1_.id
11
WHERE authors0_.book_id = ?
12
ORDER BY author1_.name DESC



Displaying Set will output the following (via Author#toString()):

Plain Text
 




xxxxxxxxxx
1


 
1
Author{id=2, name=Quartis Young, genre=Anthology, age=51},
2
Author{id=6, name=Qart Pinkil, genre=Anthology, age=56},
3
Author{id=5, name=Martin Leon, genre=Anthology, age=38},
4
Author{id=1, name=Mark Janel, genre=Anthology, age=23},
5
Author{id=4, name=Katy Loin, genre=Anthology, age=56},
6
Author{id=3, name=Alicia Tom, genre=Anthology, age=38}



The source code is available on GitHub.

Using @OrderBy with HashSet will preserve the order of the loaded/fetched Set, but this is not consistent across the transient state. If this is an issue, to get consistency across the transient state as well, consider explicitly using LinkedHashSet instead of HashSet. So, for full consistency, use:

Java
 




xxxxxxxxxx
1


 
1
@ManyToMany(mappedBy = "books")
2
@OrderBy("name DESC")
3
private Set<Author> authors = new LinkedHashSet<>();




If you liked this article then you'll my book containing 150+ performance items - Spring Boot Persistence Best Practices. This book helps every Spring Boot developer to squeeze the performances of the persistence layer. 

spring boot

Conclusion

When using the @ManyToMany annotation, always use a java.util.Set. Do not use the java.util.List. In the case of other associations, use the one that best fits your case. If you go with List, do not forget to be aware of the HHH-58557 issue that was fixed starting with Hibernate 5.0.8.

Database sql Spring Framework Relational database Junctions

Opinions expressed by DZone contributors are their own.

Related

  • Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 1)
  • Why Should Databases Go Natural?
  • SQL Interview Preparation Series: Mastering Questions and Answers Quickly
  • Keep Calm and Column Wise

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!