Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags
Asked Answered
A

18

614

Hibernate throws this exception during SessionFactory creation:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

This is my test case:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 // @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
 private List<Child> children;

}

Child.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

How about this problem? What can I do?


EDIT

OK, the problem I have is that another "parent" entity is inside my parent, my real behavior is this:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private AnotherParent anotherParent;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<Child> children;

}

AnotherParent.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}

Hibernate doesn't like two collections with FetchType.EAGER, but this seems to be a bug, I'm not doing unusual things...

Removing FetchType.EAGER from Parent or AnotherParent solves the problem, but I need it, so real solution is to use @LazyCollection(LazyCollectionOption.FALSE) instead of FetchType (thanks to Bozho for the solution).

Aristocracy answered 2/12, 2010 at 12:28 Comment(3)
I would ask, what SQL query are you hoping to generate that will retrieve two separate collections simultaneously? The kinds of SQL that would be able to achieve these would either require a cartesian join (potentially highly inefficient) or a UNION of disjoint columns (also ugly). Presumably the inability to achieve this in SQL in a clean & efficient manner influenced the API design.Sahaptin
@ThomasW These are the sql queries it should generate: select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_idMongoloid
You can get a simillar error if you have more than one List<child> with fetchType defined for more than one List<clield>Lubric
P
691

I think a newer version of hibernate (supporting JPA 2.0) should handle this. But otherwise you can work it around by annotating the collection fields with:

@LazyCollection(LazyCollectionOption.FALSE)

Remember to remove the fetchType attribute from the @*ToMany annotation.

But note that in most cases a Set<Child> is more appropriate than List<Child>, so unless you really need a List - go for Set

Use with caution

Remember that using a Set won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Pubis answered 2/12, 2010 at 13:30 Comment(15)
i'm using hibernate 3.6, i tried your suggest but the problem remains.Aristocracy
odd, it has worked for me. Did you remove the fetchType from the @*ToMany ?Pubis
oh, sorry, ok now works. However what is the reason of this problem? I used fetchtype EAGER a lot of times in equal situation without problem, why now i have this trouble? Thank you.Aristocracy
the problem is that the JPA annotations are parsed not to allow more than 2 eagerly loaded collection. But the hibernate-specific annnotations allow it.Pubis
The need for more than 1 EAGER seems totally realistic. Is this limitation just a JPA oversight? What are the concerns I should look for when having muliple EAGERs?Naji
the thing is, hibernate can't fetch the two collections with one query. So when you query for the parent entity, it will need 2 extra queries per result, which is normally something you don't want.Pubis
This problem is exacerbated by JSF that only implements List in its UIData components. Even wrapping a Set in an ArrayList in JSF causes yet other problems, hence Lists linked directly to JPA is what JSF requires.Tokay
I only ran into this problem in my JUnit tests. It was unable to build EntityManagerFactory because of this MultipleBagFetchException. This answer solved it!Boatbill
It'd be great to have an explanation as to why this resolves the issue.Chrysoprase
to be honest, I don't know precisely - hibernate has some internal implementation details that make it possible. Probably different ways of handling (or simply validating?) the two annotationsPubis
@Webnet maybe this will make it clearer for you: github.com/introproventures/graphql-jpa-query/issues/2Lavenialaver
Hibernate is a leaky abstraction that just does not work.Acropetal
But I am trying to fetch them lazily, what's the problem of having two collections? Lazily means NOT at the same time, so what's the problem with two lists with no eager setted?Grau
This doesn't work if you are fetching the lists with an Entity GraphGrau
There's undefined behavior for fetching two Sets at the same time. Other users have different errors, but I was getting an StackOverflow error when hibernate was building a HashSet. I had to revert to List just to get it query calls to return.Coddle
M
331

Simply change from List type to Set type.

Use with caution

This solution is not recommended as it won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Mckown answered 3/5, 2011 at 5:55 Comment(10)
A List and a Set are not the same thing: a set does not preserve orderWellgroomed
LinkedHashSet preserves orderMontpelier
This is an important distinction and, when you think about it, entirely correct. The typical many-to-one implemented by a foreign key in the DB is really not a List, it's a Set because order is not preserved. So Set is really more appropriate. I think that makes the difference in hibernate, though I don't know why.Ballast
I was having the same cannot simultaneously fetch multiple bags but not because of annotations. In my case, I was doing left joins and disjunctions with the two *ToMany. Changing the type to Set solved my problem too. Excellent and neat solution. This should be the official answer.Deactivate
If you want to preserver order you should use SortedSet.Volz
@OndrejBozek But what order are you preserving? We are talking about a foreign key relation in the database. The DB can (and will) return the items in the order that happens to be most convenient to it. If you really want to control order, you need to add some ordering rule to the fetch query (not sure if/how it can be done with JPA), and maybe add some index field to sort on.Maddux
@StijndeWitt yes you can sort yout foreign key relation using @OrderBy or (in case of Hibernate) @SortComparator annotation. The you need to have SortedSet.It's quite common to have your associations sorted.Volz
I liked the answer, but the million dollar question is: Why? Why with Set don't show exceptions? ThanksGreenhouse
I have a perfectly valid use case that was blowing up with this same exceptions when using List. I changed the code as Ahmad suggested and now I'm all "Set". :)Rhachis
I had a similar problem, but I had Collection instead of List. Anyway, after I changed it to Set, the problem was gone. So, thanks!Mcgaha
R
213

Considering we have the following entities:

enter image description here

And, you want to fetch some parent Post entities along with all the comments and tags collections.

If you are using more than one JOIN FETCH directives:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    left join fetch p.tags
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

Hibernate will throw the infamous:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]

Hibernate doesn't allow fetching more than one bag because that would generate a Cartesian product.

The worst "solution"

Now, you will find lots of answers, blog posts, videos, or other resources telling you to use a Set instead of a List for your collections.

That's terrible advice. Don't do that!

Using Sets instead of Lists will make the MultipleBagFetchException go away, but the Cartesian Product will still be there, which is actually even worse, as you'll find out the performance issue long after you applied this "fix".

The proper Hibernate 6 solution

If you're using Hibernate 6, then you can fix this issue like this:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.tags t
    where p in :posts 
    """, Post.class)
.setParameter("posts", posts)
.getResultList();

As long as you fetch at most one collection using JOIN FETCH per query, you will be fine.

By using multiple queries, you will avoid the Cartesian Product since any other collection, but the first one is fetched using a secondary query.

The proper Hibernate 5 solution

You can do the following trick:

List<Post> posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.tags t
    where p in :posts 
    """, Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

In the first JPQL query, distinct DOES NOT go to the SQL statement. That's why we set the PASS_DISTINCT_THROUGH JPA query hint to false.

DISTINCT has two meanings in JPQL, and here, we need it to deduplicate the Java object references returned by getResultList on the Java side, not the SQL side.

By using multiple queries, you will avoid the Cartesian Product since any other collection, but the first one is fetched using a secondary query.

There's more you could do

If you're using the FetchType.EAGER strategy at mapping time for @OneToMany or @ManyToMany associations, then you could easily end up with a MultipleBagFetchException.

You are better off switching from FetchType.EAGER to Fetchype.LAZY since eager fetching is a terrible idea that can lead to critical application performance issues.

Conclusion

Avoid FetchType.EAGER and don't switch from List to Set just because doing so will make Hibernate hide the MultipleBagFetchException under the carpet. Fetch just one collection at a time, and you'll be fine.

As long as you do it with the same number of queries as you have collections to initialize, you are fine. Just don't initialize the collections in a loop, as that will trigger N+1 query issues, which are also bad for performance.

Recreation answered 27/6, 2018 at 6:1 Comment(43)
Thanks for the shared knowledge. However, DISTINCT is performance killer in this solution. Is there a way to get rid of distinct? (tried to return Set<...> instead, didn't help much)Annecy
Vlad, thanks for the help I find it really usuful. However, the issue was related to hibernate.jdbc.fetch_size (eventually I set it to 350). By the chance, do you know how to optimize nested relations? E.g. entity1 -> entity2 -> entity3.1, entity 3.2 (where entity3.1 / 3.2 are @OneToMany relations)Annecy
Can't we avoid Cartesian Product by using DISTINCT in one single jpql query with Set?Sycee
No, you can not. Think about it in terms of SQL. You cannot JOIN multiple one-to-many associations without generating a Cartesian Product.Recreation
What if comments has another collection that you want load with EAGER loading (List<CommentReviews>). I tried and had the same MultipleBagFetchException problemColene
You only get a MultipleBagFetchException if you issue JOIN FETCH twice in a JPQL query. However, the solution in this answer doesn't tell you to do that. If you read the answer carefully, you will see that you achieve the same goal using a single JOIN FETCH and an additional secondary query.Recreation
@VladMihalcea In your recommended solution, is there any point to assign posts = .getResultList() in your second query? It worked without this assignment in my case.Chapen
@İsmailYavuz It depends on the JIT implementation. If you return the posts variable from the currently executing method, then, if you don't reassign the posts variable, maybe the JIT will consider that call useless as it doesn't seem to have any side-effect.Recreation
@VladMihalcea If we have deep nesting like post has many postComments has many likes, Can we use list for first layer(postComments) and set for second layer(Likes) and dedup? This worked fine for me. is there an issue? I mean dedup with with set will work for single one to many but not multiple so we can leverate with set in nexted fetchesLardaceous
If you are using spring data jpa and writing this query using custom implementation of repository, don't forget to add Transactional annotation to the method. @VladMihalcea Any reason why it gives LazyInitializationException if it's not annotatated with transactional?Merell
You should always havd @Transactional on the service methods calling a Spring Data Jpa Repository. Not doing so is a terrible mistake.Recreation
Should the methods inside custom repository implementation also be annotated with Transactional? reference: docs.spring.io/spring-data/jpa/docs/current/reference/html/…Merell
Those are already transactional, but you want to set the Tx at the service layer so all repositories called from a service use the same Tx.Recreation
Is there any benefit for using additional secondary query and not FetchMode.SUBSELECTHardhack
Of course. The query is more obvious and doesn't have the IN clause limit issue.Recreation
It would be really helpful if you describe how to do this with the JPA entities, not just in plain hibernate project.Grau
The MultipleBagFetchException is a Hibernate exception. Other JPA provides have other exceptions.Recreation
I was attempting to use this with @NamedEntityGraph annotated entity classes. It does not work, unless I remove the graph logic. Is there any merit to the entity graph technique?Arse
I think it can work with entity graphs. I'll create a prototype when I'll have some time.Recreation
@VladMihalcea For me it's the same: Not working with EntityGraph attributePaths, for now I'm using the Set aproach (I don't know if it creates cartesian product when used with Entity Graphs) do you think they do?Grau
If you fetch two sibling Set associations using any strategy, you'll get a Cartesian Product. If the associations are related, like child amd grandchildren, then you won't get a Cartesian Product.Recreation
@VladMihalcea Then, it is safe to say that if I only have ONE attribute path in my EntityGraph, there's no way I'm getting a cartesian product, right?Grau
@CarlosLópezMarí JPA associations are fetched via LEFT or INNER joins. So, the only way to generate a Cartesian Product is the same as with plain SQL. After all, it all boils done to SQL. And, when using INNER and LEFT joins, the only way to generate a Cartesian Product is to generate two virtual tables (from intermediate joins) that cannot be associated by following the JOIN condition.Recreation
What if you had an additional collection on the tags object which you also wanted to initialise? Is it possible (or maybe unwise) to go another level deep?Vocalize
Another best option is to use MyBatis for this kind of sceanarioOverdye
@harshakumarReddy If you think it's that easy to fetch a graph of N-level relations using MyBatis, feel free to add a new answer and show it to us. I'm afraid your "best solution" will end up being more complex than using 2 JPQL queries and relying on the Persistence Context to bind the associations.Recreation
@VladMihalcea I was just suggesting hybrid approach not completely replacing hibernate entirely . i remember that you have mentioned in your blog that for ManytoMany relationship its not good to use List and use Set instead because when we delete one child . all Childs will be deleted along with it and reinserted except the one deleted with is not efficient . can you shed more light on this,here you are saying Sets are not efficientOverdye
@harshakumarReddy Here, I'm not saying that Sets are not efficient. It's just that if you use Sets you don't get the exception, but you still get the Cartesian Product. You can also get the Cartesian Product with MyBatis and fetching a List of sibling children. Also, don't take one piece of advice from a context and generalize it for everything, as it's a bad idea. Use every piece of advice in its original context and you'll be fine.Recreation
Your answer is quite opinionated. It can make sense to do a cartesian product or it can be the worst solution, it depends on the data and use case. Additionally using List is not always the best options as you mentioned it :)Lepley
@NicolasLabrot All of my answers are opinionated. That's because I've actually worked on the Hibernate ORM, so I'm very biased about it. As for that article, I suppose you missed the The HHH-5855 issue was fixed in Hibernate 5.0.8, so another reason to update. box on the article ;)Recreation
I did not miss it :), does it mean that List should be preferred over Set?Lepley
It depends. For embeddable, Set are better. For bidirectional one-to-many associations, it doesn't matter, but List are more versatile. For Many-to-Many, Sets are better. You can find a detailed explanation in my High-Performance Java Persistence book.Recreation
@VladMihalcea if i have bidirectional one-to-many can i switch from list to set and that's ok since you say it doesn't matter? Cartesian product will not be a mistake? however, are you saying that I can use the set but still download each collection separately? I have an object that has an on-to-many join and then each has five one-to-many joins. There are many records in the table, but there are single records that meet the join conditions. Is it better to loop through each of the collections separately for the first level join, or change to SET and concatenate all the joins?Dichromaticism
In this example it seems to overwrite the first posts variable, how should we combine the results from each child collection on to the parent?Paxwax
@VladMihalcea great answer how to do this with query annotations on repository methods in spring boot?Baillargeon
@Baillargeon I'll blog about that in the future.Recreation
@VladMihalcea cheers mate looking forward to itBaillargeon
It's a good approach, using Spring Data JPA, in service call methods like invoice = invoiceRepository.finById(2L), then invoiceItemList = invoiceItemsRepository.findAllByInvoice(invoice); receiptList = receiptRepository.findAllByInvoice(invoice); then set all together.Cruickshank
Per the Hibernate 6 migration guide: Starting with Hibernate ORM 6 it is no longer necessary to use distinct in JPQL and HQL to filter out the same parent entity references when join fetching a child collection. The returning duplicates of entities are now always filtered by Hibernate. Which means that for instance it is no longer necessary to setQueryHints#HINT_PASS_DISTINCT_THROUGH to false in order to skip the entity duplicates without producing a distinct in the SQL query.Alpestrine
@Alpestrine I updated the answer for Hibernate 6 too.Recreation
For those using a spring jpa repository, @VladMihalcea wrote a great blog post here: vladmihalcea.com/spring-data-jpa-multiplebagfetchexception His solution worked very well with findBy... queries and EntityGraph annotation.Aerography
That's not working for me, the second list is filled with "nulls"... strange, Hibernate 6, String Boot 3.2.0Baronetage
The code is available on GitHub and works like a charm with both Spring and Hibernate 6.Recreation
R
186

Add a Hibernate-specific @Fetch annotation to your code:

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

This should fix the issue, related to Hibernate bug HHH-1718

Record answered 29/11, 2011 at 10:46 Comment(11)
@DaveRlz why subSelect solves this problem. I tried your solution and its working, but dont know how the problem was solved by using this ?Pongee
This is the best answer unless a Set really makes sense. Having a single OneToMany relationship using a Set results in 1+<# relationships> queries, where as using FetchMode.SUBSELECT results in 1+1 queries. Also, using the annotation in the accepted answer (LazyCollectionOption.FALSE) causes even more queries to be executed.Titian
FetchType.EAGER is not a proper solution for this. Need to proceed with Hibernate Fetch Profiles and need to solve itFaustina
Every time I forget that annotation name I come back to this answer. ...Every time :)Stanleystanly
Oh MAN! My controller was getting duplicate object list-nodes back from responses and this answer fixed it! Thank you! @DaveRlz Im using JPA2.2 .Arteriole
The two other top answers did not solve my problem. This one did. Thank you!Dopey
Does anyone know why does SUBSELECT fix it, but JOIN does not?Brawn
Now I get: LazyInitializationException: failed to lazily initialize a collection of role: xxxx could not initialize proxy - no Session error. I had to use EAGER or LazyCollection approach.Housman
Worth mentioning this is org.hibernate.annotations.FetchMode (not org.hibernate.FetchMode)Disinterest
This should be tha accepted answer, it is not only easy to implement and understand, It is also really well performing!Grau
I would like to integrate the comment from @SteveChambers in the answer but now the edit queue is full, as usualFortress
F
49

After trying with every single option describe in this posts and others, I came to the conclusion that the the fix is a follows.

In every XToMany place @XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) and intermediately after

@Fetch(value = FetchMode.SUBSELECT)

This worked for me

Fremantle answered 27/2, 2014 at 14:3 Comment(4)
adding @Fetch(value = FetchMode.SUBSELECT) was enoughKnothole
This is a Hibernate only solution. What if you are using a shared JPA library?Stampede
I'm sure you didnt mean to, but DaveRlz already wrote the same thing 3 years earlierAurelea
This is probably duplicate of the above answer: https://mcmap.net/q/64117/-hibernate-throws-multiplebagfetchexception-cannot-simultaneously-fetch-multiple-bagsFortress
L
18

To fix it simply take Set in place of List for your nested object.

@OneToMany
Set<Your_object> objectList;

And don't forget to use fetch=FetchType.EAGER, It'll work.

There is one more concept CollectionId in Hibernate if you want to stick with list only.

Use with caution

Remember that you won't eliminate the underlying Cartesian Product as described by Vlad Mihalcea in his answer, featured as "The worst solution"!

Lights answered 4/9, 2014 at 20:29 Comment(1)
Both recommendations you made are very bad in terms of performance.Grau
P
7

you can keep booth EAGER lists in JPA and add to at least one of them the JPA annotation @OrderColumn (with obviously the name of a field to be ordered). No need of specific hibernate annotations. But keep in mind it could create empty elements in the list if the chosen field does not have values starting from 0

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

in Children then you should add the orderIndex field

Pallium answered 25/4, 2017 at 8:53 Comment(1)
this is a very underrated solution to this problem, deserves more attention.Shelbyshelden
M
3

When you have too complex objects with saveral collection could not be good idea to have all of them with EAGER fetchType, better use LAZY and when you really need to load the collections use: Hibernate.initialize(parent.child) to fetch the data.

Mantra answered 15/10, 2015 at 13:38 Comment(0)
H
2

We tried Set instead of List and it is a nightmare: when you add two new objects, equals() and hashCode() fail to distinguish both of them ! Because they don't have any id.

typical tools like Eclipse generate that kind of code from Database tables:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

You may also read this article that explains properly how messed up JPA/Hibernate is. After reading this, I think this is the last time I use any ORM in my life.

I've also encounter Domain Driven Design guys that basically say ORM are a terrible thing.

Hermosa answered 10/1, 2018 at 13:52 Comment(0)
B
1

For me, the problem was having nested EAGER fetches.

One solution is to set the nested fields to LAZY and use Hibernate.initialize() to load the nested field(s):

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
Bumpkin answered 12/1, 2019 at 18:9 Comment(1)
I would like to set the Java syntax highlight but the edit queue is full now, as usualFortress
L
1

At my end, this happened when I had multiple collections with FetchType.EAGER, like this:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

Additionally, the collections were joining on the same column.

To solve this issue, I changed one of the collections to FetchType.LAZY since it was okay for my use-case.

Goodluck! ~J

Lied answered 11/1, 2020 at 21:14 Comment(0)
C
1

You can also try to make fetch=FetchType.LAZY and just add @Transactional(readOnly = true) to method where you get child

Comose answered 22/10, 2021 at 10:49 Comment(0)
L
0

Commenting both Fetch and LazyCollection sometimes helps to run project.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
Labialized answered 27/2, 2020 at 5:47 Comment(0)
H
0

One good thing about @LazyCollection(LazyCollectionOption.FALSE) is that several fields with this annotation can coexist while FetchType.EAGER cannot, even in the situations where such coexistence is legit.

For example, an Order may have a list of OrderGroup(a short one) as well as a list of Promotions(also short). @LazyCollection(LazyCollectionOption.FALSE) can be used on both without causing LazyInitializationException neither MultipleBagFetchException.

In my case @Fetch did solve my problem of MultipleBacFetchException but then causes LazyInitializationException, the infamous no Session error.

Housman answered 14/4, 2020 at 15:43 Comment(0)
P
0

I solved by annotating:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Peeler answered 29/1, 2021 at 10:12 Comment(0)
G
0

Ok so here's my 2 cents. I had the Fetch Lazy annotations in my Entity but I also duplicated the fetch lazy in the session bean thus causing a multiple bag issue. So I just removed the lines in my SessionBean

criteria.createAlias("FIELD", "ALIAS", JoinType.LEFT_OUTER_JOIN); //REMOVED

And I used Hibernate.initialize on the list I wanted to retrieve after the List parent = criteria.list was called. Hibernate.initialize(parent.getChildList());

Graehme answered 26/8, 2021 at 12:11 Comment(0)
F
0

TLDR: Don't use EAGER. Always fetch things only when you really need them!


A lot of mentions here about FetchType.EAGER and I wanted to go a bit more into detail why this is a bad idea and on what to use alternatively. Hopefully after reading this you realize that really you should absolutely NEVER use FetchType.EAGER.

There is just no good reason to! It's a mayor pitfall that can bite you (or even worse: someone else) down the road. Imagine you chose FetchType.EAGER for a Student -> Teacher relation, because you think every time you need to fetch a Student you also need his Teachers. Now even if you are 100% sure you never need students without teachers right now, you simply can't foresee how requirements change. FetchType.EAGER violates the Open-closed principle! Your code is no longer open for extension - if later the need for loading Students without Teachers arise it's difficult do to that without reworking (and possibly breaking) existing code!

An even bigger problem you have is that you basically created an n+1 select problem for possible future queries, that (rightfully) already fetched another bag in the basic query. Let's say in the future someone wants to load all Students with their grades and there are 30000 of them. Since you told Hibernate to EAGER fetch all Teachers it has to do that. But since you already fetched another "bag" (the grades) within the same query this actually results in 30001 queries - for data that wasn't even needed in that scenario! The first query for loading all Students+Grades and then a separate query for each Student to fetch his teachers. Needless to say that this is horrendous for performance. In my opinion this is the sole reason for people to believe that "Hibernate is slow" - they just don't realize how incredible inefficient it might query stuff in some situations. You really have to be careful with 1:n relations.

3 Alternatives (manually fetch stuff when needed):

  1. Use JOIN FETCH to fetch a collection. To stick with the example SELECT stud FROM STUDENT stud JOIN FETCH stud.teachers would do the trick. This will always fire a single query to fetch students AND teachers. Just be mindful to only fetch one collection that way (explanation would go too far for this answer).
  2. If you use Spring-JPA you can use @EntityGraph(attributePaths = {"teachers"}) to do the same.
  3. You could call Hibernate.initialize(oneStudent.getTeachers()) with the Hibernate-proxy to fetch the relation manually. This will always create a separate query, so don't do it in a loop or you just created a n+1 select problem yourself.

Also one final tip in general: turn the logging for org.hibernate.SQL to DEBUG to see when/what queries are fired and make sure your local dev setup has a reasonable amount of data. When you test your code with only 2 students and don't check the queries that are fired you might miss something like that and end up having issues on real systems with more data.

Flycatcher answered 1/5, 2023 at 12:28 Comment(0)
D
-5

You could use a new annotation to solve this:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

In fact, fetch's default value is FetchType.LAZY too.

Darla answered 14/6, 2017 at 3:21 Comment(1)
JPA3.0 does not exist.Raina

© 2022 - 2024 — McMap. All rights reserved.