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.
Data Engineering Design Patterns
Looking for a book that defines and solves most common data engineering problems? I'm currently writing
one on that topic and the first chapters are already available in π
Early Release on the O'Reilly platform
I also help solve your data engineering problems π contact@waitingforcode.com π©
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:
-
EAGER: Destined to small quantity of data.
LAZY: Should be used when the size of associated data is big or associated objects are, by definition, a big objects (BLOB, CLOB) - their construction time is more important than in the case of small objects. -
EAGER: Can be used when we know that we'll need associated data every time.
LAZY: Useful when we don't know if we'll need associated data. If it's not the case, we avoid to load and create unnecessary objects. If we need this data, it's not a tragedy - it will be loaded in background "at demand", by simple invocation of getter. -
EAGER: No need to care about data use - associated data will be always available.
LAZY:Associated data must be queries under current session. Otherwise lazy initialization exception will be thrown, as below in Hibernate:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.waitingforcode.model.StoreAddress.stores, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection. throwLazyInitializationException(AbstractPersistentCollection.java:575) at org.hibernate.collection.internal.AbstractPersistentCollection. withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
-
EAGER: No need to care about data use - associated data will be always available.
LAZY: Associated data must be queries under current session. Otherwise lazy initialization exception will be thrown, as below in Hibernate:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.waitingforcode.model.StoreAddress.stores, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection. throwLazyInitializationException(AbstractPersistentCollection.java:575) at org.hibernate.collection.internal.AbstractPersistentCollection. withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
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.