Hibernate.orgCommunity Documentation
Table of Contents
Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much data, in terms of width (values/columns) and/or depth (results/rows), adds unnecessary overhead in terms of both JDBC communication and ResultSet processing. Fetching too little data causes additional fetches to be needed. Tuning how an application fetches data presents a great opportunity to influence the application's overall performance.
The concept of fetching breaks down into two different questions.
When should the data be fetched? Now? Later?
How should the data be fetched?
"now" is generally termed eager or immediate. "later" is generally termed lazy or delayed.
There are a number of scopes for defining fetching:
static - Static definition of fetching strategies is done in the mappings. The statically-defined fetch strategies is used in the absence of any dynamically defined strategies [2].
dynamic (sometimes referred to as runtime) - Dynamic definition is really use-case centric. There are 2 main ways to define dynamic fetching:
fetch profiles - defined in mappings, but can be enabled/disabled on the Session.
HQL/JPQL and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query.
Starting in Hibernate 4.2 (JPA 2.1) you can also use JPA EntityGraphs.
The strategies
Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed). This is the strategy generally termed N+1.
Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL join.
Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner. Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
Let's consider these topics as it relates to an simple domain model and a few use cases.
Example 10.1. Sample domain model
@Entity public class Employee { @Id private Long id; @NaturalId private String userid; @Column( name="pswd" ) @ColumnTransformer( read="decrypt(pswd)" write="encrypt(?)" ) private String password; private int accessLevel; @ManyToOne( fetch=LAZY ) @JoinColumn private Department department; @ManyToMany(mappedBy="employees") @JoinColumn private Set<Project> projects; ... }
@Entity public class Department { @Id private Long id; @OneToMany(mappedBy="department") private List<Employees> employees; ... }
@Entity public class Project { @Id private Long id; @ManyToMany private Set<Employee> employees; ... }
The Hibernate recommendation is to statically mark all associations lazy and to use dynamic fetching strategies for eagerness. This is unfortunately at odds with the JPA specification which defines that all one-to-one and many-to-one associations should be eagerly fetched by default. Hibernate, as a JPA provider, honors that default.
For the first use case, consider the application's login process for an Employee. Lets assume that login only requires access to the Employee information, not Project nor Department information.
Example 10.2. No fetching example
String loginHql = "select e from Employee e where e.userid = :userid and e.password = :password"; Employee employee = (Employee) session.createQuery( loginHql ) ... .uniqueResult();
In this example, the application gets the Employee data. However, because all associations from Employee are declared as LAZY (JPA defines the default for collections as LAZY) no other data is fetched.
If the login process does not need access to the Employee information specifically, another fetching optimization here would be to limit the width of the query results.
Example 10.3. No fetching (scalar) example
String loginHql = "select e.accessLevel from Employee e where e.userid = :userid and e.password = :password"; Employee employee = (Employee) session.createQuery( loginHql ) ... .uniqueResult();
For the second use case, consider a screen displaying the Projects for an Employee. Certainly access to the Employee is needed, as is the collection of Projects for that Employee. Information about Departments, other Employees or other Projects is not needed.
Example 10.4. Dynamic query fetching example
String userid = ...; String hql = "select e from Employee e join fetch e.projects where e.userid = :userid"; Employee e = (Employee) session.createQuery( hql ) .setParameter( "userid", userid ) .uniqueResult();
String userid = ...; CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Employee> criteria = cb.createQuery( Employee.class ); Root<Employee> root = criteria.from( Employee.class ); root.fetch( Employee_.projects ); criteria.select( root ); criteria.where( cb.equal( root.get( Employee_.userid ), cb.literal( userid ) ) ); Employee e = entityManager.createQuery( criteria ).getSingleResult();
In this example we have an Employee and their Projects loaded in a single query shown both as an HQL query and a JPA Criteria query. In both cases, this resolves to exactly one database query to get all that information.
Suppose we wanted to leverage loading by natural-id to obtain the Employee information in the "projects for and employee" use-case. Loading by natural-id uses the statically defined fetching strategies, but does not expose a means to define load-specific fetching. So we would leverage a fetch profile.
Example 10.5. Fetch profile example
@FetchProfile( name="employee.projects", fetchOverrides={ @FetchOverride( entity=Employee.class, association="projects", mode=JOIN ) } )
String userid = ...; session.enableFetchProfile( "employee.projects" ); Employee e = (Employee) session.bySimpleNaturalId( Employee.class ) .load( userid );
Here the Employee is obtained by natural-id lookup and the Employee's Project data is fetched eagerly. If the Employee data is resolved from cache, the Project data is resolved on its own. However, if the Employee data is not resolved in cache, the Employee and Project data is resolved in one SQL query via join as we saw above.