How to lazily load non-relational attributes in a portable way

By Thorben Janssen

Mapping

JPA’s @Basic annotation supports a fetch attribute that you can use to define the FetchType of an entity attribute. That seems to be the same approach as you use to define the fetching behavior of an entity association. So, lazy loading of non-relational entity attributes should be easy, right?

Well, unfortunately, it isn’t that easy. The JPA specification defines the LAZY strategy as a hint to the persistence provider:

Whether the value of the field or property should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the value must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime.

JPA 2.2 Specification p. 432

In practices, that means that depending on your JPA implementation, annotating an attribute with @Basic(fetch=FetchType.LAZY) isn’t enough. If you’re using Hibernate, you need to configure bytecode enhancement, as I explain in the Hibernate Performance Tuning Online Training. And if you’re using EclipseLink, you either need to activate static or dynamic weaving for your entity class.

This not only makes lazy loading of non-relational attributes harder than it has to be, but it also makes it an unportable feature

But there is a different approach to achieve the same result without any bytecode manipulation, that works with all available JPA implementations. But it also has a few downsides, which I will discuss at the end of this article.

Let’s first take a look at alternative approach and start with the table model.

The table model

You can see the table model in the following diagram. The review table stores all customer reviews. The message of an extensive review can be pretty long, and I modeled it with PostgreSQL’s text type. It supports variable content lengths up to 1GB.

OK, so how can you map this table in a way that it supports lazy loading of the message column in a portable way?

Lazy attribute loading

A good and easy way to implement lazy loading of non-relational attributes is to model an inheritance hierarchy using the Mapped Superclass strategy.

You just need to create a superclass that models all attributes you want to fetch eagerly and two subclasses, that map the same database table. One of the subclasses extends the superclass without adding any attributes. The sole purpose of this class is to implement an entity that will get managed by your persistence context. You can use this class whenever you do not need the lazily loaded attribute. The second subclass maps the lazily fetched attribute, and you can use it when you need all the information stored in that database table.

Modeling an inheritance hierarchy

Let’s apply this concept to the mapping of the review table. The message of a review can be relatively huge, and I want to be able to load a review with and without its message. So, we need 3 classes:

  1. The BaseReview class is the superclass of the inheritance hierarchy.
  2. The ShortReview class extends the BaseReview class, and I annotate it with @Entity and @Table to make it an entity that maps the review table.
  3. And the DetailedReview class extends the BaseReview class, adds the mapping definition of the message attribute, and I also annotate it with @Entity and @Table.

OK, enough theory. Let’s take a look at the code. If you’re familiar with JPA’s different inheritance strategies, the implementation of all 3 classes is relatively simple.

The superclass

The BaseReview class is the superclass of this small hierarchy. I annotate it with @MappedSuperclass so that all subclasses inherit its mapping definitions. But the BaseReview class itself doesn’t become an entity.

@MappedSuperclass
public class BaseReview {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "review_seq")
	protected Long id;

	@Enumerated
	private Rating rating;

	private ZonedDateTime postedAt;

	@Version
	private int version;

	...
}

As you can see, there is nothing special about the attribute mappings:

The id attribute maps the primary key column, and I use the sequence review_seq to generate primary key values. Since version 5.3, you can skip the definition of a @SequenceGenerator, if it has the same name as the database sequence.

The rating and postedAt attributes use the default mappings to map an enum to an ordinal value and a ZonedDateTime object to a timestamp.

And the version attribute is used for optimistic locking, which I explain in more details in my Advanced Hibernate Online Training.

An entity for all eagerly fetched attributes

The ShortReview entity extends the BaseReview class and only adds an @Entity and a @Table annotation. You can use it for all use cases that don’t read or change the message attribute.

>@Entity
@Table(name = "review")
public class ShortReview extends BaseReview {

	...
}

The @Table annotation specifies the mapping to a database table. You don’t need this annotation when you want to map the entity to a database table with the same name. But in this case, you need to map the ShortReview and the DetailedReview entity to the same database table and you, therefore, can’t rely on the default mapping.

An entity for all eagerly and lazily fetched attributes

The message column gets exclusive mapped by the DetailedReview entity, and you should only use this class if you need to read or change that information. In all other cases, you should use the ShortReview instead.

@Entity
@Table(name = "review")
public class DetailedReview extends BaseReview {

	private String message;

	...
}

With the exception of the message attribute, the mapping of the DetailedReview entity is identical to the mapping of the previously discussed ShortReview entity.

Things you should know before you use this approach

As you have seen, you can map the review table to 2 entities. One of them maps all columns and the other one only maps the columns you want to load eagerly. While this approach doesn’t provide real lazy loading, you can use it to achieve the same result: You can map a database record with and without a specific set of columns.

But this approach also has 2 main drawbacks:

  1. The message attribute is only mapped by the DetailedReview entity. If you loaded a ShortReview entity, you can’t fetch the message attribute without loading a DetailedReview entity which contains lots of redundant information. This creates an overhead you should try to avoid.
  2. You can read the same database record as a ShortReview and a DetailedReview. If you do that within the same Hibernate Session, you get 2 managed entities that map the same database record. That can become an issue if you change both entities and Hibernate tries to persist them. Hibernate doesn’t know that both entities represent the same database record and will create 2 SQL UPDATE statements for them. The second one will fail with an OptimisticLockException.
    You might consider modeling the ShortReview entity as an immutable entity, similar to the view mapping I explained in a previous article. You then need to implement all write operations using a DetailedReview entity.

Conclusion

Lazy loading of non-relational attributes is supported by the JPA specification, but it’s only a hint. It depends on the implementation of your persistence provider, if it follows this hint or if it loads the attribute eagerly.

If you need a portable implementation that doesn’t rely on any provider-specific features and configurations, you should use an inheritance hierarchy to model the database table:

  • The superclass gets annotated with @MappedSuperclass and provides the mapping definitions for all eagerly fetched attributes.
  • One subclass only adds an @Entity and a @Table annotation, so that you get an entity that maps all eagerly fetched attributes.
  • The other subclass also gets annotated with an @Entity and a @Table annotation, and it also specifies the mapping of all lazily fetched attributes.

Tags

Mapping


About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

Leave a Repl​​​​​y

Your email address will not be published. Required fields are marked

This site uses Akismet to reduce spam. Learn how your comment data is processed.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}