Hibernate Fetch types
- 8th Feb 2017
- 0
- 28894
- What are the different fetch strategies in Hibernate What are the different ways to load an entity in Hibernate What are the different ways to load an object in Hibernate What is eager loading and lazy loading in hibernate What is fetch mode join select sub select and select with batch size in hibernate
Let us understand the different modes of Fetch and When to use them
In any ORM framework, it’s very important to understand how it loads the entity especially when entity has relations and collections in it.
In Hibernate we call it as Fetch mode or fetching strategy.
Yes Hibernate decides how to load the entity based on the fetch mode.
Example:
If User entity has a Collection of Address entity in it.
When we load User, Should we load Addresses as well ? Or We should load only User ?
Answer to this question in Hibernate is based on Fetch mode we specify.
When we load User,at the same time if we load Address also, then its called as EAGER loading.
If we delay the loading of Addresses while loading User until we require Addresses then it’s called Lazy loading.
Fetch mode helps us in customizing the number of queries generated and amount of data retrieved.
Different Fetch modes supported by Hibernate
1) FetchMode JOIN
Eager loading which loads all the collections and relations at the same time.
2) FetchMode SELECT(default)
Lazy loading which loads the collections and relations only when required.
3) FetchMode SELECT with Batch Size
Fetch upto “N”collections or entities(“Not number of records”)
4) FetchMode SUBSELECT
Group the collection of an entity into a Sub-Select query.
Now let us understand these fetch modes in detail with below example
Customer entity has a Collection of Address entity
It is a one to many relation (one Customer will have many addresses)
Code to define the fetching strategy
We can define Fetch mode either through XML mapping or through annotation mapping
1) Let’s see XML mapping for the same
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.kb.model">
- <class name="Customer" table="customers">
- <id name="id" type="int" column="Id">
- <generator class="increment" />
- </id>
- <property name="firstName">
- <column name="FirstName" />
- </property>
- <property name="lastName">
- <column name="LastName" />
- </property>
- <property name="city">
- <column name="City" />
- </property>
- <list name="addresses" cascade="all" inverse="true"
- table="address" fetch="select" batch-size="10">
- <key>
- <column name="CUSTOMER_ID" not-null="true" />
- </key>
- <list-index column="idx" />
- <one-to-many class="Address" />
- </list>
- </class>
- </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.kb.model"> <class name="Customer" table="customers"> <id name="id" type="int" column="Id"> <generator class="increment" /> </id> <property name="firstName"> <column name="FirstName" /> </property> <property name="lastName"> <column name="LastName" /> </property> <property name="city"> <column name="City" /> </property> <list name="addresses" cascade="all" inverse="true" table="address" fetch="select" batch-size="10"> <key> <column name="CUSTOMER_ID" not-null="true" /> </key> <list-index column="idx" /> <one-to-many class="Address" /> </list> </class> </hibernate-mapping>
We can see that while defining list of addresses using < list > tag, we have specified fetch attribute.
We have also specified batch-size as 10 which is required in Batch fetch mode.
2) Now Let’s see how can we achieve the same through Annotations
- @Entity
- @Table(name = "customers", catalog = "javainsimpleway")
- public class Customer implements Serializable{
- ...
- @OneToMany(fetch = FetchType.LAZY, mappedBy = "customers")
- @Cascade(CascadeType.ALL)
- @Fetch(FetchMode.SELECT)
- @BatchSize(size = 10)
- public List<Address> getAddresses() {
- return this.addresses;
- }
- ...
- }
@Entity @Table(name = "customers", catalog = "javainsimpleway") public class Customer implements Serializable{ ... @OneToMany(fetch = FetchType.LAZY, mappedBy = "customers") @Cascade(CascadeType.ALL) @Fetch(FetchMode.SELECT) @BatchSize(size = 10) public List<Address> getAddresses() { return this.addresses; } ... }
We can see that while defining One to Many relation using @OneToMany annotation, we have specified fetch attribute.
We have also specified batch-size as 10 using @BatchSize annotation which is required in Batch fetch mode.
We will use these 2 mapping files(XML and Annotation) to address each Fetch Mode as below
FetchMode JOIN
Modify above xml with fetch=”join”
(or)
Modify above annotated class with @Fetch(FetchMode.JOIN)
This is also called Eager loading which means load all the collections and relations no matter we use it or not.
This fetching strategy loads User and List of Address in a single query when we request only User.
It will load all the addresses when we load the User no matter whether we use the Address or not.
- Customer customer = session.get(Customer.class, customerId);
- List<Address> addresses = customer.getAddresses();
Customer customer = session.get(Customer.class, customerId); List<Address> addresses = customer.getAddresses();
Query generated from Hibernate
Hibernate: select customer0_.Id as Id1_1_0_, customer0_.FirstName as FirstNam2_1_0_, customer0_.LastName as LastName3_1_0_, customer0_.City as City4_1_0_, addresses1_.CUSTOMER_ID as CUSTOMER5_0_1_, addresses1_.Id as Id1_0_1_, addresses1_.idx as idx6_1_, addresses1_.Id as Id1_0_2_, addresses1_.Street as Street2_0_2_, addresses1_.Landmark as Landmark3_0_2_, addresses1_.PostalCode as PostalCo4_0_2_, addresses1_.CUSTOMER_ID as CUSTOMER5_0_2_ from customers customer0_ left outer join address addresses1_ on customer0_.Id=addresses1_.CUSTOMER_ID where customer0_.Id=?
Hibernate generated only one select statement, it retrieve all its related collections when the Customer is loaded using session.get(Customer.class, 1)
Select statement to retrieve the Customer records
Outer join to retrieve its related collections.
FetchMode SELECT(default)
Modify above xml with fetch=”select”
(or)
Modify above annotated class with @Fetch(FetchMode.SELECT)
This is also called Lazy loading which means loads the collections and relations only when required
It loads only Customer when we request the Customer, it will not load the addresses until we request for it.
It will load all the addresses only when we explicitly request the addresses to use in our application.
- Customer customer = session.get(Customer.class, customerId); //Select from Customer table only
- List<Address> addresses = customer.getAddresses();
- //Select from Address table will start here
- for (Address address : addresses) {
- System.out.println(address);
- }
Customer customer = session.get(Customer.class, customerId); //Select from Customer table only List<Address> addresses = customer.getAddresses(); //Select from Address table will start here for (Address address : addresses) { System.out.println(address); }
Query generated from Hibernate
Hibernate: select customer0_.Id as Id1_1_0_, customer0_.FirstName as FirstNam2_1_0_, customer0_.LastName as LastName3_1_0_, customer0_.City as City4_1_0_ from customers customer0_ where customer0_.Id=? Hibernate: select addresses0_.CUSTOMER_ID as CUSTOMER5_0_0_, addresses0_.Id as Id1_0_0_, addresses0_.idx as idx6_0_, addresses0_.Id as Id1_0_1_, addresses0_.Street as Street2_0_1_, addresses0_.Landmark as Landmark3_0_1_, addresses0_.PostalCode as PostalCo4_0_1_, addresses0_.CUSTOMER_ID as CUSTOMER5_0_1_ from address addresses0_ where addresses0_.CUSTOMER_ID=?
Hibernate generated 2 select statements
First Select statement to retrieve the Customer records – session.get(Customer.class, 1)
Second Select statement to retrieve its related collections – when we iterate addresses using enhanced for loop
FetchMode SELECT with Batch Size
No change in above xml as we have specified the batch size as 10 already
(or)
No change in above annotated class as we have specified the batch size as 10 already
Fetch upto “N” collections or entities(“Not number of records”)
The biggest misunderstanding of this fetch mode is, Batch size corresponds to number of records fetched per collection. This is Complete misunderstanding. Batch size decides the number of collections to be loaded when we load one entity.
Example:
We have given batch size as “10”
Now when we load one Customer, Hibernate loads the address collection for additional 10 Customers which are currently in the session.
It means 10 address collections are loaded one for each Customer.
Suppose, We have 20 Customers in the session and batch size is set as 10
In this case, when we load one Customer, 3 queries will be executed
1. One query to load all the 20 Customers.
2. One query to load the Address collections for 10 Customers.
3. Another query to load the Address collections for other 10 Customers
If we have only one User then queries generated with batch size is same as without batch size as below
Queries generated by Hibernate
Hibernate: select customer0_.Id as Id1_1_0_, customer0_.FirstName as FirstNam2_1_0_, customer0_.LastName as LastName3_1_0_, customer0_.City as City4_1_0_ from customers customer0_ where customer0_.Id=? Hibernate: select addresses0_.CUSTOMER_ID as CUSTOMER5_0_1_, addresses0_.Id as Id1_0_1_, addresses0_.idx as idx6_1_, addresses0_.Id as Id1_0_0_, addresses0_.Street as Street2_0_0_, addresses0_.Landmark as Landmark3_0_0_, addresses0_.PostalCode as PostalCo4_0_0_, addresses0_.CUSTOMER_ID as CUSTOMER5_0_0_ from address addresses0_ where addresses0_.CUSTOMER_ID=?
Suppose we have 10 users and we load all of them using below query
- List<Customer> customers = session.createQuery("from Customer").getResultList();
- for (Customer customer : customers) {
- List<Address> addresses = customer.getAddresses();
- for (Address address : addresses) {
- System.out.println(address);
- }
- }
List<Customer> customers = session.createQuery("from Customer").getResultList(); for (Customer customer : customers) { List<Address> addresses = customer.getAddresses(); for (Address address : addresses) { System.out.println(address); } }
Now see the queries generated by Hibernate with Batch size of 10
Hibernate: select customer0_.Id as Id1_1_, customer0_.FirstName as FirstNam2_1_, customer0_.LastName as LastName3_1_, customer0_.City as City4_1_ from customers customer0_ Hibernate: select addresses0_.CUSTOMER_ID as CUSTOMER5_0_1_, addresses0_.Id as Id1_0_1_, addresses0_.idx as idx6_1_, addresses0_.Id as Id1_0_0_, addresses0_.Street as Street2_0_0_, addresses0_.Landmark as Landmark3_0_0_, addresses0_.PostalCode as PostalCo4_0_0_, addresses0_.CUSTOMER_ID as CUSTOMER5_0_0_ from address addresses0_ where addresses0_.CUSTOMER_ID in ( ?, ?, ?,?,?,?,?,?,?,? )
Now Hibernate has generated 2 queries
First Select query to retrieve all the customer records
Second Select query with “IN” to retrieve its address collection for 10 customers(we can see 10 ‘?’ symbols ).
If we don’t use batch size in this case, we will end up with N+1 queries where “N” corresponds to the number of Customer records in the session which is 10 in this example.
So we will end up 10+1 = 11 queries.
1 query to retrieve all the 10 customers
1 query to retrieve its addresses for 1st customer
1 query to retrieve its address for 2nd customer
And so on up to 10 customers
In case,If we have 20 Customers in the session and batch size is 10
Then Hibernate will generate 3 queries
1.Select query to retrieve all the customer records
2.select query with “IN” to retrieve its address collection for first 10 customers.
3.select query with “IN” to retrieve its address collection for last 10 customers
Note:The batch-size fetching strategy is not specifying how many records in the collections are loaded. Instead, it specifies how many collections should be loaded at a time.
FetchMode SUBSELECT
Modify above xml with fetch=”subselect”
(or)
Modify above annotated class with @Fetch(FetchMode.SUBSELECT)
Group the collection of an entity into a Sub-Select query.
This strategy loads the collections using a sub query
Suppose we have 10 users and we load all of them using below query
- List<Customer> customers = session.createQuery("from Customer").getResultList();
- for (Customer customer : customers) {
- List<Address> addresses = customer.getAddresses();
- for (Address address : addresses) {
- System.out.println(address);
- }
- }
List<Customer> customers = session.createQuery("from Customer").getResultList(); for (Customer customer : customers) { List<Address> addresses = customer.getAddresses(); for (Address address : addresses) { System.out.println(address); } }
Hibernate generates 2 queries in this case
Hibernate: select customer0_.Id as Id1_1_, customer0_.FirstName as FirstNam2_1_, customer0_.LastName as LastName3_1_, customer0_.City as City4_1_ from customers customer0_ Hibernate: select addresses0_.CUSTOMER_ID as CUSTOMER5_0_1_, addresses0_.Id as Id1_0_1_, addresses0_.idx as idx6_1_, addresses0_.Id as Id1_0_0_, addresses0_.Street as Street2_0_0_, addresses0_.Landmark as Landmark3_0_0_, addresses0_.PostalCode as PostalCo4_0_0_, addresses0_.CUSTOMER_ID as CUSTOMER5_0_0_ from address addresses0_ where addresses0_.CUSTOMER_ID in ( select customer0_.Id from customers customer0_ )
First Select query is to retrieve all the Customers
Second Select query is to Select the address collections for all the Customers using a sub query.
Even if we have 30 customers , the same 2 queries will be generated.
Which Fetch mode should I Use?
The most common question is “What is the best fetching strategy to use in my application”
Answer to this question depends on so many factors like application , entity relationship,etc.
Lets see some of the general guidelines to choose the best one
FetchMode JOIN
In this case, we know that data will be loaded even before we use it but it creates a least number of queries.
Sometime Single Join is faster than multiple Selects but Joining will not be good choice if it involves too much of data.
We can prefer this mode whenever we have less data in the collections.
It will be faster while accessing the collections as its already loaded everything in one shot.
FetchMode SELECT
This is best to use when we want faster response on accessing single entity.
It loads additional data only when its required.
Example:
User with list of address
In many places of our application,we may need only User details not the address details
In this case, loading only User details will get the faster response.
Whenever we need addresses of an user in other parts of an application, It will be loaded only at that moment.
FetchMode SELECT with BatchSize
BatchSize is useful when have a fixed set of data.
Example :
When we have a batch processing of say 10 Users at a time.
BatchSize of 10 will drastically reduces the number of queries required.
Most important point to note here is, If batch size is small then its performance will be good otherwise it leads to more time and affect the performance.
We have to prefer this mode when we have small Batch size and have the requirement of processing the data batch wise.
FetchMode SUBSELECT
This mode will fetch all the related collections in a Sub query.
This should be used when we have an entity where most of them are loaded in the session.
Most important point to note here is that all the collections are fetched even if the parent is not in the session.
Example:
If we have 100 users in the database and only few users are in the session then using SubSelect is not a good choice as it loads the addresses of all the 100 users using sub query.
We should prefer it when we have one entity whose most of the records are already loaded in the session.
Note:
Using the right fetching strategy based on our requirement is important to optimize the Hibernate query performance.
If we use these strategies in a wrong way, then it will badly affect the performance of the query.
So use the right strategy with thorough understanding of the concept and the requirement.