The best way to lazy load entity attributes using JPA and Hibernate

Introduction

When fetching an entity, all attributes are going to be loaded as well. This is because every entity attribute is implicitly marked with the @Basic annotation whose default fetch policy is FetchType.EAGER.

However, the attribute fetch strategy can be set to FetchType.LAZY, in which case the entity attribute is loaded with a secondary select statement upon being accessed for the first time.

@Basic(fetch = FetchType.LAZY)

This configuration alone is not sufficient because Hibernate requires bytecode instrumentation to intercept the attribute access request and issue the secondary select statement on demand.

Bytecode enhancement

When using the Maven bytecode enhancement plugin, the enableLazyInitialization configuration property must be set to true as illustrated in the following example:

With this configuration in place, all JPA entity classes are going to be instrumented with lazy attribute fetching. This process takes place at build time, right after entity classes are compiled from their associated source files.

The attribute lazy fetching mechanism is very useful when dealing with column types that store large amounts of data (e.g. BLOB, CLOB, VARBINARY). This way, the entity can be fetched without automatically loading data from the underlying large column types, therefore improving performance.

To demonstrate how attribute lazy fetching works, the following example is going to use an Attachment entity which can store any media type (e.g. PNG, PDF, MPEG).

Properties such as the entity identifier, the name or the media type are to be fetched eagerly on every entity load. On the other hand, the media file content should be fetched lazily, only when being accessed by the application code.

After the Attachment entity is instrumented, the class bytecode is changed as follows:

The content attribute fetching is done by the PersistentAttributeInterceptor object reference, therefore providing a way to load the underlying BLOB column only when the getter is called for the first time.

Because it is marked with the FetchType.LAZY annotation and lazy fetching bytecode enhancement is enabled, the content column is not fetched along with all the other columns that initialize the Attachment entity. Only when the data access layer tries to access the content property, Hibernate issues a secondary select to load this attribute as well.

Just like FetchType.LAZY associations, this technique is prone to N+1 query problems, so caution is advised. One slight disadvantage of the bytecode enhancement mechanism is that all entity properties, not just the ones marked with the FetchType.LAZY annotation, are going to be transformed, as previously illustrated.

Fetching subentities

Another approach to avoid loading table columns that are rather large is to map multiple subentities to the same database table.

Both the Attachment entity and the AttachmentSummary subentity inherit all common attributes from a BaseAttachment superclass.

SELECT a.id as id1_0_0_,
a.media_type as media_ty2_0_0_,
a.name as name3_0_0_
FROM attachment a
WHERE a.id = 1

However, when fetching the Attachment entity:

Attachment book = entityManager.find(
Attachment.class, bookId);

Hibernate is going to fetch all columns from the underlying database table:

SELECT a.id as id1_0_0_,
a.media_type as media_ty2_0_0_,
a.name as name3_0_0_,
a.content as content4_0_0_
FROM attachment a
WHERE a.id = 1

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

To lazy fetch entity attributes, you can either use bytecode enhancement or subentities. Although bytecode instrumentation allows you to use only one entity per table, subentities are more flexible and can even deliver better performance since they don’t involve an interceptor call whenever reading an entity attribute.

When it comes to reading data, subentities are very similar to DTO projections. However, unlike DTO projections, subentities can track state changes and propagate them to the database.

Subscribe to our Newsletter

* indicates required

Email Address *

10 000readers have found this blog worth following!

If you subscribeto my newsletter, you'll get:

A free sampleof my Video Course about running Integration tests at warp-speed using Docker and tmpfs

In IDEA, you write the source code while the bytecode enhancement mechanism is for the runtime part. Even in debug mode, it won’t be a a problem. So, just build the project with Maven and you’ll be fine.

I’m using the enhancer to allow lazy-loading of inverse-side toOne relations. Which means when the enhancer is not working, hibernate fallbacks to classic proxies and because it doesn’t know whether to put in the property a lazy-proxy-object or null it executes extra query.

This ensures the application works correctly even when enhancer is not used, but it also changes the behaviour a bit – executing queries when it shouldn’t be executing any.

We have a custom listener for devs, that makes sure no lazy-loading occurs and when there is a query in unexpected place in wrong application layer. Thanks to this listener, now when I run tests from IDEA, it glows with WARN’s that tell me about unexpected queries, but when the tests run in CI from maven, everything is as expected.

I want it to not only work correctly but also consistently no matter the environment. I’m unable to optimize the app, if it behaves differently in dev and prod 😦

I think you can tell Idea to build the project and run the tests through Maven so that you get the same outcome. If you can’t do it, then you should contact Jetbrains and ask them how that can be done.