Reputation: 21320
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
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
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.
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`
Upvotes: 20
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
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
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
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
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