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?
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.
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.
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.
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.
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.
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 thePost
entity, and that’s not very efficient and can lead to N+1 query issues.
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
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 collectionThe 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`
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With