Eager and lazy collections loading

Relation database management systems (RDBMS) are based on relational model. Naturally, all mapping systems must be able to represent these relationships on application layer. JPA defines several ways to do that, but all have some common points. One of them is the technique of fetching associated data.

This article covers two different types of loading of associated collections of data: EAGER and LAZY. In its first part, we'll define both of them and try to find the appropriated use-cases for them. At the second part we'll focus on JUnit test case, illustrating the sample use of EAGER and LAZY loading.

EAGER and LAZY fetching

As you remember, JPA manages SQL JOINS through @ManyToOne, @OneToMany and @OneToOne annotations. Each of these annotations accept several parameters and one which is interesting for us, is called fetch. It indicates the strategy used to JPA implementation to load associated objects. And it's here where we can define our two types: EAGER or LAZY. Both of them are a part of javax.persistence.FetchType enumeration.

The definition of these two types is quite simple. EAGER means that associated data will be loaded in the same time as entity holding the relationship. In the other side, LAZY is used to load associated data after loading the entity. To illustrate that in SQL queries, EAGER will evidently use JOIN clause to get associated data in the same moment as the main entity. LAZY will make a simple SELECT query and get associated data through primary key of the main entity.

Below you can find summary table which shows other differences between these two types:

Example of EAGER fetching

Let's begin by simpler case, EAGER loading. This is mapping used to illustrate this FetchType:

// Category.java
@Entity
@Table(name = "category")
public class Category {
  private List<Product> products;

  @OneToMany(mappedBy = "category", fetch = FetchType.EAGER)
  public List<Product> getProducts() {
    return this.products;
  }

  public void setProducts(List<Product> products) {
    this.products = products;
  }
  // another fields here
}

// Product.java
@Entity
@Table(name = "product")
public class Product {
  private Category category;

  @ManyToOne
  @JoinColumn(name = "category_id")
  public Category getCategory() {
    return this.category;
  }

  public void setCategory(Category category) {
    this.category = category;
  }
  // another fields here
}

Below you can find a simple test case to illustrate SQL querying with EAGER FetchType:

/**
 * Test case for collections loaded with LAZY or EAGER loading. The main difference is the way of getting associated data: in
 * the case of LAZY, we'll launch the second query to get it only on explicit user's demand (call of method invoking
 * retrieving of associated data). In the case of EAGER, we'll load associated data on main query execution.
 */
public class LazyEagerLoadingTest extends AbstractJpaTester {
  @Test
  public void testEagerLoading() {
    Category drink = entityManager.find(Category.class, 2l);

    // Eager loading of products associated to this Category will produce a query with LEFT OUTER JOIN, as following:
    // select category0_.id as id1_4_0_, category0_.name as name2_4_0_, category0_.seo_desc as seo_desc3_4_0_,
    // category0_.seo_keywords as seo_keyw4_4_0_, category0_.seo_title as seo_titl5_4_0_,
    // products1_.category_id as category7_4_1_, products1_.id as id1_10_1_, products1_.id as id1_10_2_,
    // products1_.category_id as category7_10_2_, products1_.name as name2_10_2_, products1_.price as price3_10_2_,
    // products1_.meta_description as meta_des4_10_2_, products1_.meta_keywords as meta_key5_10_2_,
    // products1_.meta_title as meta_tit6_10_2_ from category category0_ left outer join product products1_ on
    // category0_.id=products1_.category_id where category0_.id=2
    assertEquals("3 products associated for given Category ("+drink+") should be found", 3, drink.getProducts().size());
  }
}

Example of LAZY fetching

To show LAZY loading, we also begin by mapping:

// Store.java
@Entity
@Table(name = "store")
public class Store {
  
  private StoreAddress storeAddress;
  @ManyToOne
  @JoinColumn(name = "store_address_id")
  public StoreAddress getStoreAddress() {
    return this.storeAddress;
  }

  public void setStoreAddress(StoreAddress storeAddress) {
    this.storeAddress = storeAddress;
  }

}

// StoreAddress.java
@Entity
@Table(name = "store_address")
public class StoreAddress {
  private List<Store> stores;

  @OneToMany(mappedBy = "storeAddress", fetch = FetchType.LAZY)
  public List<Store> getStores() {
    return this.stores;
  }

  public void setStores(List<Store> stores) {
    this.stores = stores;
  }
}

And the test case looks like below (accompanied by on common test for loading on closed session):

@Test
public void testLazyLoading() {
  StoreAddress storeAddress = entityManager.find(StoreAddress.class, 1);
  // Here, normally only query on store_address table is made:
  // select storeaddre0_.id as id1_15_0_, storeaddre0_.city_name as city_nam2_15_0_, storeaddre0_.place as place3_15_0_,
  // storeaddre0_.street as street4_15_0_, storeaddre0_.zip_code as zip_code5_15_0_ from store_address storeaddre0_
  // where storeaddre0_.id=1

  // Only when we try to get stores associated with given address, we launch a second query getting only stores through
  // store_address foreign key:
  // select storeaddre0_.id as id1_15_0_, storeaddre0_.city_name as city_nam2_15_0_, storeaddre0_.place as place3_15_0_,
  // storeaddre0_.street as street4_15_0_, storeaddre0_.zip_code as zip_code5_15_0_ from store_address storeaddre0_
  // where storeaddre0_.id=1
  List<Store> stores = storeAddress.getStores();
  assertEquals("3 stores should be associated for this store address ("+storeAddress+")", 3, stores.size());
}


@Test
public void testOnClosedContext() {
  StoreAddress storeAddress = entityManager.find(StoreAddress.class, 1);
  Category drink = entityManager.find(Category.class, 2l);

  entityManager.close();

  // should be accessed correctly - data was loaded eagerly
  System.out.println("Products are "+drink.getProducts());

  // should produce a LazyInitializationException
  boolean wasLie = false;
  try {
    List<Store> stores = storeAddress.getStores();
    // Simple assign doesn't produce LazyInitializationException. It's why we make a print after that.
    System.out.println("Stores are "+stores);
    fail("Accessing LAZY loaded data outside a session should fail");
  } catch (LazyInitializationException lie) {
    wasLie = true;
  }
  assertTrue("LazyInitializationException should be thrown", wasLie);
}

In this article we learned about FetchTypes available for association annotations. From two existing types, EAGER and LAZY, each one is adapted to accurate need. LAZY should be used with a big amount of associated objects. Thanks to it, they'll be loaded at demand and not overload unnecessary JVM and network bandwidth. In the other side, EAGER, is destined to work with smaller collections, when we know that they'll be used every time.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!