4

Multiple Entities are associated with @OnetoMany relations and Eager Loaded with an Entity. While fetching data using Hibernate criteria, the related Eager column is initialized which is causing performance issue for my project as it is huge and there are many Eager loaded relationships.

What would be the best way to avoid initialization of Eager column while using hibernate criteria to fetch data.

  • 1
    The Hibernate mappings fetching information which you define over the entity forms the global fetch plan. At query time, we may override the global fetch plan, but only for LAZY associations. You cannot override Eager fetch plan at the time of query. EAGER fetching is a code smell when using JPA and Hibernate. [check this aticle](https://vladmihalcea.com/eager-fetching-is-a-code-smell/). Best approach would be to define fetch = FetchType.Lazy on entity mapping and override it in the query to eager whenever eager load is required. – Mohd Waseem Nov 12 '19 at 10:17

6 Answers6

1

You could use a projection. If you are using Spring Data, it includes some nice helpers see Baeldung: Spring Data JPA Projections and Spring Data JPA Reference for details. If you are using Hibernate directly, see Baeldung: JPA/Hibernate Projection and Hibernate JavaDoc Projection

Hibernate (direct quote from Baeldung)

To project on multiple columns using JPQL, we only have to add all the required columns to the select clause:

Query query = session.createQuery("select id, name, unitPrice from Product"); List resultList = query.getResultList();

But, when using a CriteriaBuilder, we'll have to do things a bit differently:

CriteriaBuilder builder = session.getCriteriaBuilder(); CriteriaQuery<Object[]> query = builder.createQuery(Object[].class); Root product = query.from(Product.class); query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice")); List<Object[]> resultList = entityManager.createQuery(query).getResultList(); Here, we've used the method multiselect() instead of select(). Using this method, we can specify multiple items to be selected.

Another significant change is the use of Object[]. When we select multiple items, the query returns an object array with value for each item projected. This is the case with JPQL as well.

Let's see what the data looks like when we print it:

[1, Product Name 1, 1.40] [2, Product Name 2, 4.30] [3, Product Name 3, 14.00] [4, Product Name 4, 3.90] As we can see, the returned data is a bit cumbersome to process. But, fortunately, we can get JPA to populate this data into a custom class.

Also, we can use CriteriaBuilder.tuple() or CriteriaBuilder.construct() to get the results as a list of Tuple objects or objects of a custom class respectively.

I suggest reading the Baeldung article, it goes into more detail.

Jean Marois
  • 1,510
  • 11
  • 19
0

If you are using @OneToMany annotation then there is an attribute called fetch= FetchType.LAZY.

You can use it. It will notify hibernate for Lazy loading. For more info you can refer Baeldung.com and search for Eager and Lazy Loading

  • I can't update the Entity as it is been used all over the project and many of the implementations require data to be initialized Eagerly. For one the implementation due to performance, i need to avoid loading of Eagerly initialized columns. – Farheen Patel Nov 12 '19 at 07:17
  • May be I couldn't explain it properly, I wanted to say. for example you have Student entity and Teacher entity, suppose both of them have address entity as OneToMany Relationship. In Student you want lazy loading Lazy loading and in teacher entity you want Eager loading. Then as per my answer you have to only do the changes in Student entity, add @OneToMany(fetch=FetchType.LAZY) List
    addressList; it will make address lazy loading only for dtudent. For Teacher entity address will remain Eager loading. I hope You get what wanted to explain. Thanks
    – Mayuree Budhe Nov 12 '19 at 11:08
  • 1
    MayureeBudhe i think SO is saying that they cannot change already defined entities which may be part of legacy code base. For @OneToMany default fetch type is LAZY no need to mark explicitly. It sets Global fetch plan for that association. FarheenPatel you might need to convince others to change fetch type over OneToMany associations as i mentioned in my comment it is a code smell. – Mohd Waseem Nov 13 '19 at 16:16
0

I would also suggest the same that has been mentioned by Mayuree. In further, see if you can play below condition:

  • reduced batch size could also help there if so that the pagination helps you there.
  • it will increase the boot time but the Db performance trade-off is immense. You can eagerly load the necessary table that you require.

Source

0

I would suggest using fetch= FetchType.LAZY but you have to check code for transactions. If they are created then you're fine, otherwise you'll get LazyInitializationException when trying to use that data.

If you don't want it to be eagerly fetched every time you fetch parent entities there is two solutions without changing legacy code. First one is to create entity graph and including that in graph. So you would fetch data with your graph and that would be it. No more additional sql queries to fetch eager collection. Second solution is to create entity for same table but with everything marked lazy.

Conrad
  • 536
  • 5
  • 13
0

Which JPA version are you using? JPA 2.1 has introduced Dynamic fetching via JPA entity graph Or you may probably try with Dynamic fetching via Hibernate profile

Refer https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/fetching/Fetching

https://docs.jboss.org/hibernate/orm/5.0/userguide/en-US/html/ch09.html

0

You can explicitly define properties with criteria to be fetched in lazy mode.

criteria.setFetchMode("property_name", FetchMode.SELECT); 

In hibernate code SELECT is Lazy.

public static final FetchMode LAZY = SELECT;

Hibernate document link

https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/querycriteria.html#querycriteria-dynamicfetching

Amit Vyas
  • 790
  • 3
  • 10