JPA would be unusable without
Lazy Loading, which is a feature allowing fetching of correlated entities from the database when they are referenced for the first time, rather than immediately when you perform the database query. Imagine that you cannot use
Lazy Loading ... One query for the single result can return the whole database in this case, if you are lucky enough ;)
Each time when you use
@OneToMany or
@ManyToMany annotations,
Lazy Loading is active by default, without any effort. For all other types of associations you may control it, using
fetch attribute of annotation defining the association.
Sounds wonderful, isn't it? Everything is done automagically without my intervention, required entities are fetched from the database when they are needed, the world is beautiful and free of violence ... Well, not quite, my dear Watson ...
You have to be aware, that some entities may be referenced for the first time at the View rendering level. For
Spring Framework based application with JSP used for View layer (but not only in this setup) this can lead to miscellaneous problems, which depends on used JPA provider.
Let's take a deeper look at the potential problems, using usual Employer - Employee - Benefit example from my previous
JPA related posts. Suppose that we want to fetch the Employer information from the database, but the Employees and Benefits only when they are referenced. Associations between Employer, Employees and Benefits look like this:
@Entity
@Table(name = "EMPLOYERS")
public class Employer implements Serializable {
...
private List<Employee> employees;
...
@OneToMany(mappedBy = "employer")
public List<Employee> getEmployees() {
return employees;
}
...
}
@Entity
@Table(name = "EMPLOYEES")
public class Employee implements Serializable {
...
private List<Benefit> benefits;
private Employer employer;
...
@OneToMany(mappedBy = "employee")
public List<Benefit> getBenefits() {
return benefits;
}
@ManyToOne
@JoinColumn(name = "EMPLOYER_ID")
public Employer getEmployer() {
return employer;
}
...
}
@Entity
@Table(name = "BENEFITS")
public class Benefit implements Serializable {
...
private Employee employee;
@ManyToOne
@JoinColumn(name = "EMPLOYEE_ID")
public Employee getEmployee() {
return employee;
}
...
}
DAO fetching the Employer data for Model:
@Repository
public class DefaultEmployerDAO implements EmployerDAO {
@PersistenceContext
private EntityManager entityManager;
@Override
public Employer get(Long identifier) {
return entityManager.find(Employer.class, identifier);
}
...
}
And the JSP view displaying the Employer information, along with the Employees, and their Benefits:
<h1><c:out value="${employer.businessName}" /></h1>
...
<c:forEach items="${employer.employees}" var="e">
<tr>
<td><c:out value="${e.firstName}" /></td>
<td>
<ul>
<c:forEach items="${e.benefits}" var="b">
<li><c:out value="${b.name}" /></li>
</c:forEach>
</ul>
</td>
</tr>
</c:forEach>
Let's examine how it will behave for different JPA providers, starting from
EclipseLink 2.1.1. When we try to open desired view in browser, we will see something like this:
Application activity while preparing the Model (seen from the
Spring Insight):
and while rendering the view itself:
All the SQL queries visible at the view rendering level are caused by
Lazy Loading.
As you see the
EclipseLink and
Lazy Loading works without any special effort :)
Now it's time to check
Hibernate 3.4.0.GA. For this provider, when we try to open the desired view in browser, we will see beautiful
LazyInitializationException: failed to lazily initialize a collection of role: [...].entities.domain.Employer.employees, no session or session was closed
Sweet, isn't it? :) - old hibernate
session closed
problem strikes again, this time in JPA ;) - but believe me, this is not the worst problem you may encounter using JPA ;), because with a little help from
OpenEntityManagerInViewFilter it can be quickly corrected (if you are using hibernate for a little longer, you are probably well aware of very useful
OpenSessionInViewFilter used in similar situations with "pure" hibernate). Let's add this filter to the web.xml:
<filter>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
<init-param>
<param-name>entityManagerFactoryBeanName</param-name>
<param-value>persistence.EntityManagerFactory</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The
entityManagerFactoryBeanName parameter should match the name of your Entity Manager factory bean in
Spring Framework configuration.
With this filter we can get finally the desired results. This time Application activity while preparing the Model looks like this:
and while the view rendering:
Finally
OpenJPA 2.0.1 - first try without the
OpenEntityManagerInViewFilter, and we will see in browser:
Good Lord! No data loaded at all ... no exceptions reported ... the worst situation, because you don't know if there are no data to view in the database, or there is something wrong with the application! :(
Let's add the
OpenEntityManagerInViewFilter, and try again:
Now it looks as expected!
Application activity while preparing the Model:
and the view rendering:
As you see above,
Lazy Loading requires for both
Hibernate and
OpenJPA OpenEntityManagerInViewFilter to be used, while
EclipseLink work without any special effort.