Anand
Anand

Reputation: 21320

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?

Upvotes: 45

Views: 88378

Answers (7)

Rahul Vasa
Rahul Vasa

Reputation: 1

Here is what I did:

@Autowired SessionFactory (org.hibernate.SessionFactory)

Session currentSession = sessionFactory.getCurrentSession();
Session newSession;
if(null == currentSession) {
    newSession = sessionFactory.openSession();
    user= newSession.get(User.class, userId);
    Hibernate.initialize(user.getAddress());
    newSession.close();
} else {
    user = currentSession.get(User.class, userId);
    Hibernate.initialize(user.getAddress());
}

Asssuming the session is already open then we force initialize (also called force join) the collection/proxy (1-m/1-1). In case the session doesn't exist then we open a new session, perform the operation and close it again.

Let me know if you find any flaw in this

Upvotes: 0

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153810

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`

Upvotes: 20

L for Light
L for Light

Reputation: 29

Consider you have a table which may have relations with other 4 tables. In this case if you use eager then, all the corresponding relations from all the four related tables will be fetched in every fetch operation.

But consider you may be in such a requirement in which you need the data from only one table among the related tables, so in this case you can fetch only the required relationship instead of lading the entire four related table's data using the Hibernate.initialize facility.

Upvotes: 0

chepaiytrath
chepaiytrath

Reputation: 718

Consider the following example:

I have an entity LoanApplication (which in this case is a very heavy object) which has various fields inside of it (which may also be large). Consider for example SubLoan field in LoanApplication .

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "application_fk")
@Index(name = "appl_fk_idx_subloansLoanApp")
private Set<SubLoan> subLoans;

FetchType is LAZY in this example. Now if in some controller's method while doing some operations, you encounter LoanApplication, the subLoans set will initially be null unless you wish to use it. In that case you use Hibernate.initialize like following:

Hibernate.initialize(loanApplication.getSubLoans());

This helps to mainly improve performance because each time you retrieve LoanApplication, the large set of objects i.e. 'subLoan' will be initially empty unless you really wanted them.

Upvotes: 6

Aliti
Aliti

Reputation: 2095

Actually if you use EAGER, when you have big collections it really affects your performance. So, it's a good idea in the situations like this using Hibernate.initialize.

Take a look at this: Hibernate Lazy Fetch vs Eager Fetch Type

Upvotes: 1

Tomasz Przybylski
Tomasz Przybylski

Reputation: 495

Consider @Don Ruby answer

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.

Upvotes: 3

Don Roby
Don Roby

Reputation: 41137

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.

Upvotes: 48

Related Questions