Your 2 best options to fix Hibernate's MultipleBagFetchException
Aug 15, 2023
Your 2 best options to fix Hibernate's MultipleBagFetchException
You probably learned that you should use FetchType.LAZY for all of your associations. It ensures that Hibernate initializes an association when you use it and doesn’t spend any time getting data you don’t need. Unfortunately, this introduces a new issue. You now need to use a JOIN FETCH clause or an EntityGraph to fetch the association if you need it. Otherwise, you will experience the n+1 select issue, which causes severe performance issues or a LazyInitializationException. If you do that for multiple associations, Hibernate might throw a MultipleBagFetchException. In this video, I will explain when Hibernate throws this exception and show you your 2 best options to fix it. One of them is a great fit for associations with a small cardinality and the other one for associations that contain lots of elements. So, let’s take a look at both of them, and you pick the one that fits your application. Like my channel? Subscribe! ➜ http://bit.ly/2cUsid8 Join the free Member Library:https://goo.gl/dtyIIC Read the accompanying post: https://thorben-janssen.com/fix-multi … Want to connect with me? Blog: https://thorben-janssen.com/ Twitter: https://twitter.com/thjanssen123 Facebook: https://www.facebook.com/thoughtsonjava #Hibernate #JPA #MultipleBagFetchException
Content
1.08 -> You probably learned that you should use FetchType.LAZY for all of your associations.
5.36 -> It ensures that Hibernate initializes an association
8.3 -> when you use it and doesn’t spend any time getting data you don’t need.
12.56 -> Unfortunately, this introduces a new issue.
15.74 -> You now need to use a JOIN FETCH clause or an EntityGraph
19.58 -> to fetch the association if you need it.
21.82 -> Otherwise, you will experience the n+1 select issue,
24.92 -> which causes severe performance issues
27.34 -> or a LazyInitializationException.
30.24 -> If you do that for multiple associations,
Hibernate might throw a MultipleBagFetchException.
35.54 -> Hi, I’m Thorben Janssen and I solve Java
persistence problems for a living.
39.78 -> In this video, I will explain when Hibernate throws this exception
43.72 -> and show you your 2 best options to fix it.
46.62 -> One of them is a great fit for associations
with a small cardinality
50.34 -> and the other one for associations that contain lots of elements.
54.32 -> So, let’s take a look at both of them,
56.8 -> and you pick the one that fits your application.
60.22 -> But before we start: If you are new here and you want to learn
63.12 -> how to create your entity mappings with ease,
65.38 -> build incredible efficient persistence layers with Hibernate and Spring
69.28 -> and all types of other Java persistence related stuff,
72.44 -> subscribe and click the bell to get new videos every week.
76.18 -> And also don’t forget to like this video
if you found it helpful.
79.22 -> By doing so you are helping me to reach more
people with it.
82.88 -> As I explained in a previous article about
the most efficient data type for a to-many association,
88.64 -> Hibernate’s internal naming of the collection types is pretty confusing.
93.68 -> Hibernate calls it a Bag, if the elements
in your java.util.List are unordered.
99.08 -> If they are ordered, it’s called a List.
101.62 -> So, depending on your mapping, a java.util.List
can be treated as a Bag or a List.
107.22 -> But don’t worry, in real life, this isn’t as confusing as it might seem.
111.72 -> Defining the order of an association
113.68 -> requires an additional annotation an overhead.
117.5 -> That’s why you should avoid it and why at
least 90% of the association mappings
121.94 -> that use a java.util.List
123.8 -> and that I’ve seen in real projects are unordered.
127.44 -> So, Hibernate treats them as a Bag.
133.12 -> Here is a simple domain model in which Hibernate
treats the Reviews
136.4 -> and the Authors of a Book as Bags.
139.7 -> If you try to fetch multiple of these bags
in a JPQL query, you create a cartesian product.
145.76 -> This can create performance problems.
148 -> Hibernate also struggles to differentiate
between information that is supposed to be duplicated
153.86 -> and information that was duplicated because of the cartesian product.
158.12 -> Because of that, Hibernate throws a MultipleBagFetchException.
164.9 -> You can find lots of questions about this
exception and various solutions to avoid it.
169.66 -> But a lot of them come with unexpected side effects.
172.36 -> The only 2 fixes between which you should choose
175.2 -> are the ones that I will describe in the following sections.
178.12 -> Which one of them is the best for you
179.94 -> depend on the size of the cartesian product that your queries might create:
184.36 -> If all of your associations only contain a
small number of elements,
188.12 -> the created cartesian product will be relatively small.
192.18 -> In these situations, you can change the types of the attributes
195.48 -> that map your associations to a java.util.Set.
198.8 -> Hibernate can then fetch multiple associations in 1 query.
203.1 -> If at least one of your associations contains a lot of elements,
206.9 -> your cartesian product will become too big to fetch it efficiently in 1 query.
211.56 -> You should then use multiple queries that
get different parts of the required result.
216.54 -> As always, optimizing the performance of your application
219.58 -> requires you to choose between different trade-offs,
222.38 -> and there is no one-size-fits-all approach.
225.12 -> The performance of each option depends on the size of the cartesian product
228.88 -> and the number of queries you’re executing.
231.6 -> For a relatively small cartesian product, getting all information with 1 query
235.82 -> provides you with the best performance.
238.06 -> If the cartesian product reaches a certain size,
240.46 -> you should better split it into multiple queries.
243.34 -> That’s why I will show you both options
245.58 -> so that you can pick the one that fits your application.
249.26 -> Option 1: Use a Set instead of a List
251.98 -> The easiest approach to fix the MultipleBagFetchException
255.46 -> is to change the type of the attributes
257.94 -> that map your to-many associations to a java.util.Set.
264.68 -> This is just a small change in your mapping,
and you don’t need to change your business code.
269.42 -> As explained earlier, if you now perform the same query as I showed you before
274.14 -> to get the Book with all its Authors and Reviews,
276.9 -> your result set will contain a cartesian product.
279.78 -> The size of that product depends on the number of Books you select
283.52 -> and the number of associated Authors and Reviews.
287.02 -> Here you can see the generated SQL query.
289.82 -> To get all the requested associations,
291.82 -> Hibernate has to select all columns mapped by these entities.
295.48 -> In combination with the cartesian product
created by the 3 INNER JOINs,
299.26 -> this can become a performance problem.
302.6 -> Whenever you write such a query, you also need to keep in mind that Hibernate doesn’t
306.78 -> hide that the result set contains a product.
309.76 -> This query returns each Book multiple times.
312.58 -> The number of references to the same Book object is calculated by the number of Authors
317.56 -> multiplied by the number of Reviews.
319.8 -> You can avoid that by adding the DISTINCT
keyword to your select clause
323.7 -> and by setting the query hint hibernate.query.passDistinctThrough to false.
331.48 -> In this example, my query only selects 1 Book,
334.72 -> and most Books have been written by 1-3 Authors.
339.2 -> So, even if the database contains several Reviews for this Book,
342.54 -> the cartesian product will still be relatively small.
346.08 -> Based on these assumptions, it might be faster
to accept the inefficiency of the cartesian product
351.44 -> to reduce the number of queries.
353.44 -> This might change if your cartesian product
becomes bigger because
357.34 -> you select a huge number of Books or if your average Book has been written by a few dozen Authors.
363.84 -> Option 2: Split it into multiple queries
367.32 -> Fetching huge cartesian products in 1 query is inefficient.
371.12 -> It requires a lot of resources in your database and puts unnecessary load on your network.
377.06 -> Hibernate and your JDBC driver also need to
spend more resources to handle the query result.
382.76 -> You can avoid that by performing multiple queries
385.3 -> that fetch different parts of the required graph of entities.
392.06 -> In the example of this post, I would fetch the Books with all their Authors in 1 query
397.1 -> and the Books with all their Reviews in a 2nd query.
400.92 -> If your graph of required entities is more complex,
403.88 -> you might need to use more queries or fetch more associations with each of them.
408.9 -> As I explained in a previous post, Hibernate
ensures that within each Session,
413.26 -> there is only 1 entity object that represents a specific
record in the database.
418.38 -> You can use that to resolve foreign key references efficiently
422.04 -> or to let Hibernate merge the results of multiple queries.
426.36 -> If you take a look at the following log output,
you can see that the Lists returned by both
430.88 -> queries contain exactly the same object.
434.14 -> In both cases, the Book objects have the same reference.
438.02 -> When Hibernate processed the result of the 2nd query, it checked for each record if the
443.36 -> 1st level cache already contained an object for that Book entity.
446.96 -> It then reused that object and added the returned Review to the mapped association.
454.62 -> If you use multiple queries to get the required graph of entities,
458.02 -> you avoid the creation of a huge cartesian product.
461.12 -> This reduces the load on all involved systems
463.9 -> and makes it easier to ensure a good performance for all queries.
468.24 -> But that not necessarily means that this approach
is faster than option one
473.12 -> You now perform more queries than before.
475.88 -> Each of them requires a database roundtrip
478.32 -> and creates some management overhead in the database,
480.7 -> for example, to create an execution plan.
482.88 -> Due to that, this option is only faster than option 1,
486.36 -> if the size of the cartesian product
488.54 -> creates a bigger overhead than the execution of multiple queries.
492.64 -> As you have seen in this video, you can solve
Hibernate’s MultipleBagFetchException
496.92 -> in 2 ways:
498.48 -> You can change the data type of the attribute
that maps the associations
502.26 -> and retrieve all information in 1 query.
505.18 -> The result of that query is a cartesian product.
507.16 -> As long as this product doesn’t get too big, this approach is simple and efficient.
512.84 -> You can use multiple queries to fetch the required graph of entities.
516.46 -> This avoids a huge cartesian product
518.3 -> and is the better approach if you need to fetch a huge amount of data.
523.1 -> OK, that’s it for today.
524.6 -> If you want to learn more about Hibernate, you should join the free Member Library.
528.42 -> It gives you free access to a lot of member-only
content like a cheat for this video
532.76 -> and an ebook about using native queries with JPA
and Hibernate.
536.8 -> I’ll add the link to it to the video description below.
539.52 -> And if you like today’s video, please give it a thumbs up and subscribe below.
543.389 -> Bye
Source: https://www.youtube.com/watch?v=8IHUFYVgRe4