Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate: initialization of complex object

I have problems with full loading of very complex object from DB in a reasonable time and with reasonable count of queries.

My object has a lot of embedded entities, each entity has references to another entities, another entities references yet another and so on (So, the nesting level is 6)

So, I've created example to demonstrate what I want: https://github.com/gladorange/hibernate-lazy-loading

I have User.

User has @OneToMany collections of favorite Oranges,Apples,Grapevines and Peaches. Each Grapevine has @OneToMany collection of Grapes. Each fruit is another entity with just one String field.

I'm creating user with 30 favorite fruits of each type and each grapevine has 10 grapes. So, totally I have 421 entity in DB - 30*4 fruits, 100*30 grapes and one user.

And what I want: I want to load them using no more than 6 SQL queries. And each query shouldn't produce big result set (big is a result set with more that 200 records for that example).

My ideal solution will be the following:

  • 6 requests. First request returns information about user and size of result set is 1.

  • Second request return information about Apples for this user and size of result set is 30.

  • Third, Fourth and Fifth requests returns the same, as second (with result set size = 30) but for Grapevines, Oranges and Peaches.

  • Sixth request returns Grape for ALL grapevines

This is very simple in SQL world, but I can't achieve such with JPA (Hibernate).

I tried following approaches:

  1. Use fetch join, like from User u join fetch u.oranges .... This is awful. The result set is 30*30*30*30 and execution time is 10 seconds. Number of requests = 3. I tried it without grapes, with grapes you will get x10 size of result set.

  2. Just use lazy loading. This is the best result in this example (with @Fetch= SUBSELECT for grapes). But in that case that I need to manually iterate over each collection of elements. Also, subselect fetch is too global setting, so I would like to have something which could work on query level. Result set and time near ideal. 6 queries and 43 ms.

  3. Loading with entity graph. The same as fetch join but it also make request for every grape to get it grapevine. However, result time is better (6 seconds), but still awful. Number of requests > 30.

  4. I tried to cheat JPA with "manual" loading of entities in separate query. Like:

    SELECT u FROM User where id=1;
    SELECT a FROM Apple where a.user_id=1;
    

This is a little bit worse that lazy loading, since it requires two queries for each collection: first query to manual loading of entities (I have full control over this query, including loading associated entities), second query to lazy-load the same entities by Hibernate itself (This is executed automatically by Hibernate)

Execution time is 52, number of queries = 10 (1 for user, 1 for grape, 4*2 for each fruit collection)

Actually, "manual" solution in combination with SUBSELECT fetch allows me to use "simple" fetch joins to load necessary entities in one query (like @OneToOne entities) So I'm going to use it. But I don't like that I have to perform two queries to load collection.

Any suggestions?

like image 612
EvilOrange Avatar asked Nov 30 '17 13:11

EvilOrange


People also ask

What is the use of Hibernate initialize?

next difference is that Hibernate. initialize generates and executes additional sql for fetching data. So you can use it after session is closed. When you use Eager fetch in entity it's always fetch that collections during finding data (under the connection session ) in database, but not after it.

What is lazy loading in Hibernate?

Lazy loading in Hibernate means fetching and loading the data, only when it is needed, from a persistent storage like a database. Lazy loading improves the performance of data fetching and significantly reduces the memory footprint.

What is transient object in Hibernate?

Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session . It has no persistent representation in the database and no identifier value has been assigned.

How does Hibernate detect changes?

The persistence context enqueues entity state transitions that get translated to database statements upon flushing. For managed entities, Hibernate can auto-detect incoming changes and schedule SQL UPDATES on our behalf. This mechanism is called automatic dirty checking.


2 Answers

I usually cover 99% of such use cases by using batch fetching for both entities and collections. If you process the fetched entities in the same transaction/session in which you read them, then there is nothing additionally that you need to do, just navigate to the associations needed by the processing logic and the generated queries will be very optimal. If you want to return the fetched entities as detached, then you initialize the associations manually:

User user = entityManager.find(User.class, userId);
Hibernate.initialize(user.getOranges());
Hibernate.initialize(user.getApples());
Hibernate.initialize(user.getGrapevines());
Hibernate.initialize(user.getPeaches());
user.getGrapevines().forEach(grapevine -> Hibernate.initialize(grapevine.getGrapes()));

Note that the last command will not actually execute a query for each grapevine, as multiple grapes collections (up to the specified @BatchSize) are initialized when you initialize the first one. You simply iterate all of them to make sure all are initialized.

This technique resembles your manual approach but is more efficient (queries are not repeated for each collection), and is more readable and maintainable in my opinion (you just call Hibernate.initialize instead of manually writing the same query that Hibernate generates automatically).

like image 160
Dragan Bozanovic Avatar answered Oct 23 '22 22:10

Dragan Bozanovic


I'm going to suggest yet another option on how to lazily fetch collections of Grapes in Grapevine:

@OneToMany
@BatchSize(size = 30)
private List<Grape> grapes = new ArrayList<>();

Instead of doing a sub-select this one would use in (?, ?, etc) to fetch many collections of Grapes at once. Instead ? Grapevine IDs will be passed. This is opposed to querying 1 List<Grape> collection at a time.

That's just yet another technique to your arsenal.

like image 26
Stanislav Bashkyrtsev Avatar answered Oct 23 '22 20:10

Stanislav Bashkyrtsev