Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Hibernate.initialize() works

I know to use lazily load objects/collections outside the session, we do Hibernate.initialize(Object obj) so that object that is passed as an argument to initialize() method is initialized and can be used outside of the scope of the session.

But what I am not able to understand how this works. I mean if we are doing then we end up in having eager fetching so why we did lazy in the configuration and end up in the eager fetching while runtime.

In other words, I want to know the difference between using Hibernate.initialize() and eagerly loading that object.

Did I get it wrong or miss something?

like image 723
Anand Avatar asked Jun 26 '13 11:06

Anand


People also ask

What is the use of Hibernate initialize?

The Hibernate. initialize(proxy) is useful only if you are using the second-level cache. Otherwise, you are going to issue a second query which is less efficient than just initializing the proxy with the initial query.

How does Hibernate proxy work?

Hibernate uses a proxy object to implement lazy loading. When we request to load the Object from the database, and the fetched Object has a reference to another concrete object, Hibernate returns a proxy instead of the concrete associated object.

What is lazy proxy in Hibernate mapping?

Hibernate implements lazy initializing proxies for persistent objects using runtime bytecode enhancement (via the excellent CGLIB library). By default, Hibernate3 generates proxies (at startup) for all persistent classes and uses them to enable lazy fetching of many-to-one and one-to-one associations.


2 Answers

The difference is in scope of application.

The reason for making a collection association lazy is to avoid having it load the collection every time the parent object is loaded if you don't really need it.

If you are lazy-loading a collection normally, but for a particular use, you need to ensure the collection has been loaded before the session is closed, you can use Hibernate.initialize(Object obj) as you noted.

If you in fact always need the collection loaded, you should indeed load it eagerly. In most software though, that isn't the case.

like image 195
Don Roby Avatar answered Sep 28 '22 00:09

Don Roby


The Hibernate.initialize(proxy) is useful only if you are using the second-level cache. Otherwise, you are going to issue a second query which is less efficient than just initializing the proxy with the initial query.

Risking N+1 query issues

So, when executing the following test case:

LOGGER.info("Clear the second-level cache");  entityManager.getEntityManagerFactory().getCache().evictAll();   LOGGER.info("Loading a PostComment");   PostComment comment = entityManager.find(     PostComment.class,     1L );   assertEquals(     "A must read!",     comment.getReview() );   Post post = comment.getPost();   LOGGER.info("Post entity class: {}", post.getClass().getName());   Hibernate.initialize(post);   assertEquals(     "High-Performance Java Persistence",     post.getTitle() ); 

First, we are going to clear the second-level cache since, unless you explicitly enable the second-level cache and configure a provider, Hibernate is not going to use the second-level cache.

When running this test case, Hibernate executes the following SQL statements:

-- Clear the second-level cache   -- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post -- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment   -- Loading a PostComment   SELECT pc.id AS id1_1_0_,        pc.post_id AS post_id3_1_0_,        pc.review AS review2_1_0_ FROM   post_comment pc WHERE  pc.id=1   -- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$5LVxadxF   SELECT p.id AS id1_0_0_,        p.title AS title2_0_0_ FROM   post p WHERE  p.id=1 

We can see that the second-level cache was properly evicted and that, after fetching the PostComment entity, the post entity is represented by a HibernateProxy instance which only contains the Post entity identifier that was retrieved from the post_id column of the post_comment database table row.

Now, due to the call to the Hibernate.initialize method, a secondary SQL query is executed to fetch the Post entity, and that’s not very efficient and can lead to N+1 query issues.

Using JOIN FETCH with JPQL

In the previous case, the PostComment should be fetched along with its post association using the JOIN FETCH JPQL directive.

LOGGER.info("Clear the second-level cache");   entityManager.getEntityManagerFactory().getCache().evictAll();   LOGGER.info("Loading a PostComment");   PostComment comment = entityManager.createQuery(     "select pc " +     "from PostComment pc " +     "join fetch pc.post " +     "where pc.id = :id", PostComment.class) .setParameter("id", 1L) .getSingleResult();   assertEquals(     "A must read!",     comment.getReview() );   Post post = comment.getPost();   LOGGER.info("Post entity class: {}", post.getClass().getName());   assertEquals(     "High-Performance Java Persistence",     post.getTitle() ); 

This time, Hibernate execute a single SQL statement, and we no longer risk to bump into N+1 query issues:

-- Clear the second-level cache   -- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post -- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment   -- Loading a PostComment   SELECT pc.id AS id1_1_0_,        p.id AS id1_0_1_,        pc.post_id AS post_id3_1_0_,        pc.review AS review2_1_0_,        p.title AS title2_0_1_ FROM   post_comment pc INNER JOIN post p ON pc.post_id=p.id WHERE  pc.id=1   -- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post 

Using the 2nd-level cache with Hibernate.initialize

So, to see when the Hibernate.initialize is really worth using, you need to use the second-level cache:

LOGGER.info("Loading a PostComment");   PostComment comment = entityManager.find(     PostComment.class,     1L );   assertEquals(     "A must read!",     comment.getReview() );   Post post = comment.getPost();   LOGGER.info("Post entity class: {}", post.getClass().getName());   Hibernate.initialize(post);   assertEquals(     "High-Performance Java Persistence",     post.getTitle() ); 

This time, we are no longer evicting the second-level cache regions, and, since we are using the READ_WRITE cache concurrency strategy, the entities are cached right after they get persisted, hence no SQL query is needed to be executed when running the test case above:

-- Loading a PostComment   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1`   -- Proxy class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$rnxGtvMK   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1` 

Both the PostComment and the post association are fetched from the second-level cache as illustrated by the Cache hit log messages.

So, if you are using the second-level cache, it’s fine to use the Hibernate.initiaize to fetch extra associations that you need to fulfill your business use case. In this case, even if you have N+1 cache calls, each call should run very quickly since the second-level cache is configured properly and data is returned from the memory.

Hibernate.initialize and proxy collection

The Hibernate.initialize can be used for collections as well. Now, because second-level cache collections are read-through, meaning that they are stored in the cache the first time they get loaded when running the following test case:

LOGGER.info("Loading a Post");   Post post = entityManager.find(     Post.class,     1L );   List<PostComment> comments = post.getComments();   LOGGER.info("Collection class: {}", comments.getClass().getName());   Hibernate.initialize(comments);   LOGGER.info("Post comments: {}", comments); 

Hibernate executes an SQL query to load the PostComment collection:

-- Loading a Post   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1`   -- Collection class: org.hibernate.collection.internal.PersistentBag   - Cache hit, but item is unreadable/invalid :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1`   SELECT pc.post_id AS post_id3_1_0_,        pc.id AS id1_1_0_,        pc.id AS id1_1_1_,        pc.post_id AS post_id3_1_1_,        pc.review AS review2_1_1_ FROM   post_comment pc WHERE  pc.post_id=1   -- Post comments: [     PostComment{id=1, review='A must read!'},      PostComment{id=2, review='Awesome!'},      PostComment{id=3, review='5 stars'} ] 

However, if the PostComment collection is already cached:

doInJPA(entityManager -> {     Post post = entityManager.find(Post.class, 1L);       assertEquals(3, post.getComments().size()); }); 

When running the previous test case, Hibernate can fetch all data from the cache only:

-- Loading a Post   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1`   -- Collection class: org.hibernate.collection.internal.PersistentBag   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1`   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1`   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#2`   -- Cache hit :  region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`,  key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#3` 
like image 27
Vlad Mihalcea Avatar answered Sep 28 '22 02:09

Vlad Mihalcea