Alessandro Argentieri
Alessandro Argentieri

Reputation: 3215

Theoretical question on Hibernate Lazy Loading

I've learned that Hibernate allows the child elements to be queried only when it's necessary through the use of Lazy Loading.

So if we have:

@Entity
public class Payment {

 ...

 @ManyToOne(fetch = FetchType.LAZY)
 private Debtor debtor;

}

I expect that when I fetch a Payment from the Database Hibernate set a placeholder in the debtor attribute and fetches the Debtor only when it's strictly required.

So if I use a getter method in order to get a Debtor from my Payment Object:

Debtor debtor = payment.getDebtor();

I expect the thread blocks until Hibernate performs the SELECT query and return the Debtor object.

So why the heck I always get an HibernateLazyLoading exception which obliges me to write a custom fetch query in the PaymentRepository, slowing down my initial query AS I would have used an EAGER FetchType?

So why does this FetchType.LAZY exists if it doesn't work as naturally expected?

Upvotes: 1

Views: 161

Answers (2)

v.ladynev
v.ladynev

Reputation: 19956

I would like to specify @Andronicus answer because the answer is not accurate.

LazyInitializationException

It is not strictly related to @Transactional, transactions or open / closed connections. Behavior is pretty simply (there is pseudo code below)

Without LazyInitializationException

Context context = Hibernate.openPersistentContext();

Payment payment = context.getById(1L, Payment.class);
Debtor debtor = payment.getDebtor();

Hibernate.closePersistentContext();

With LazyInitializationException

    Context context = Hibernate.openPersistentContext();

    Payment payment = context.getById(1L, Payment.class);

    Hibernate.closePersistentContext();

    Debtor debtor = payment.getDebtor();

Questions

So why the heck I always get an HibernateLazyLoading exception which obliges me to write a custom fetch query in the PaymentRepository, slowing down my initial query AS I would have used an EAGER FetchType?

Because Hibernate.closePersistentContext() happened somewhere before.

So why does this FetchType.LAZY exists if it doesn't work as naturally expected?

Because we don't always need the entirely net of an entity graph. We can use JPQL (HQL), criteria and projections to load parts of the entity. We need to explain to Hibernate how the entities are related, so we need to add the associations, like @ManyToOne.

There is a little problem here: mapping serves for two purposes

  1. Explain to Hibernate how the entities are related
  2. Save / Load entities

So the simplest way to disconnect loading from objects mapping is FetchType.LAZY.

The simple rule

Always use FetchType.LAZY everywhere and fetch necessary parts of the entity graph in the place where it is neeed.

Upvotes: 4

Andronicus
Andronicus

Reputation: 26026

So why the heck I always get an HibernateLazyLoading exception which obliges me to write a custom fetch query in the PaymentRepository, slowing down my initial query AS I would have used an EAGER FetchType?

It's because this sentence is not entirely true:

I expect that when I fetch a Payment from the Database Hibernate set a placeholder in the debtor attribute and fetches the Debtor only when it's stricly required.

Debtor indeed will be fetched, if it's accessed, but only if it can be fetched. If you run it in a transaction (for example by annotation the method with @Transactional), the error will not occur, because the connection to the database is not returned to the pool / closed.

In your case the data is fetched, Debtor is wrapped in proxy and the connection is lost. If you try to access it, the LazyInitializationException is thrown.

P.S.: It's not recommended to use transactions to avoid LazyInitializationException, because of performance issues that might occut. If you fetch the parent and then iterate over multiple (say N) lazily fetched children, N + 1 queries to the database are shot.

Upvotes: 3

Related Questions