Approaches to JPA Lazy Loading

The data schema used in my Spring-Data app on GitHub, like most apps, provides plenty of opportunities to retrieve relationship data with Lazy loading. In this post we’ll cover a few approaches found in v0.0.9 of the Spring-Data app that may help you avoid the “LazyInitializationException: failed to lazily initialize a collection” run-time error.

I described the Spring-Data app data schema before, so to quickly review it consists of a primary contacts table with a @OneToMany relationship to a contact_phones table and a @ManyToMany relationship to a hobbies table via a contact_hobby_ids lookup table.

A sample record output using lazy loading is shown below, where a contact record is displayed along with its phone numbers and the contact’s hobbies. The principal of lazy loading is to grab data only as it’s needed, so we only want to retrieve the phone and hobby data when we display the contact details.

Keyword: Fetch

The keyword to remember when retrieving sub-records only when you need them is the “fetch” keyword used in a JPA @NamedQuery (or @Query as we’ll see later). Here is the named query we create in our Contact Model class.

@NamedQueries({
@NamedQuery(name = "Contact.findByContactIdWithDetail",
     query = "select distinct c from Contact c left join fetch " +
        "c.contactPhones p left join fetch c.hobbies h " +
        "where c.contactId = ?1")
	...
}

While looking at the above @NamedQuery source, notice the “?1” parameter notation. If a :contactId parameter name is used here you’d see a “Parameter with that position [1] did not exist” exception. To avoid that we use the “?1” notation.

If you really, really want to use a named parameter here, it’s okay. You’ll need to configure the Query object in the Service layer.

@PersistenceContext
private EntityManager em;

@Transactional(value = "jpaTransactionManager", readOnly = true)
public Contact getContactByIdWithDetail(Long ID) {
	return em.createNamedQuery("Contact.findByContactIdWithDetail",
		Contact.class)
		.setParameter("contactId", ID)
		.getSingleResult();
}

Configuring the Query in the Repository

It’s interesting how there are several options when creating JPA queries. If we don’t want to create a @NamedQuery in the Model class as we saw earlier we can create a @Query in the repository.

@Query("select distinct c from Contact c left join fetch " +
    "c.contactPhones p left join fetch c.hobbies h " +
    "where c.contactId = ?1")
Contact findByContactIdWithDetail(Long ID);

Either way, we would call the query by its method name, “findByContactIdWithDetail” which would either precede the repository method as we just saw, or match the name of the @NamedQuery in the Contact @Entity class shown earlier.

@Transactional(value = "jpaTransactionManager", readOnly = true)
public Contact getContactByIdWithDetail(Long ID) {
	return contactRepository.findByContactIdWithDetail(ID);
}

For Something Entirely Different in the Contact Model

Just as there are different approaches to JPA queries, here’s an entirely different approach to populating our Contact object and its details using the Contact Entity Relationship get() methods. Here’s what the Service Layer method would look like if we took that approach.

@Transactional(value = "jpaTransactionManager", readOnly = true)
public Contact getContactByIdWithDetail(Long ID) {
	Contact contact = contactRepository.findOne(ID);
	contact.getContactPhones();
	contact.getHobbies();
	return contact;
}