Sunday, December 5, 2010

JPA - Lazy Loading and View rendering

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.

8 comments:

  1. Then what does EclipseLink do if the session has already been closed? just open a new one? is that an expected behavior?

    ReplyDelete
  2. Same question: what does EcliseLink ? Behavior of Hibernate and OpenJPA seem logical for me (expect the nasty "no content" from OpenJPA which can be tricky). What happen when you use OpenEntityManagerInViewFilter ? Does it open a hibernate session for the whole request scope (which could mean mean opening a whole transaction for this request scope) ?

    ReplyDelete
  3. Nice Post. I assume that it is obvious that the resulting lazy loading queries are not optimal (N+1 queries). Typically you would either want batch lazy loading to make it 2 sql queries or a fetch join on the original query to make it 1 sql query.

    "Autofetch" is an ORM feature that can profile your object graph use and automatically tune your queries. In this example that would result in a single sql query and only fetch the properties your application used. "Autofetch" is built into Ebean ORM and there is also some Hibernate support.

    Cheers, Rob.

    ReplyDelete
  4. Nice post. It would be interesting to check if the same problems occur in an EJB3 world, where Springs OpenEntityManagerInViewFilter can not be applied as a solution. Do you have any plans on trying this out ?

    ReplyDelete
  5. Finally I have few minutes ;) - for the EclipseLink session handling (F.Lozano and Kartoch notes) - what I see in logs/debugger is that there is Server Session opened during the whole request handling, and even more - between the requests. It looks like for the concrete request handling - Client Session is derived from it. Similar to the Hibernate and OpenJPA without the filter usage, Entity Manager is closed before the view rendering (along with Client Session), but missing entities are fetched anyway using probably the shared Server Session. We need either some EclipseLink Guru here, or more digging in the code ;) to precise it. Lectures which can help are http://wiki.eclipse.org/Introduction_to_EclipseLink_Sessions_(ELUG)#Server_and_Client_Sessions and http://wiki.eclipse.org/Introduction_to_Mappings_(ELUG)#Indirection_.28Lazy_Loading.29

    ReplyDelete
  6. For the OpenEntityManagerInViewFilter usage (Kartoch note) - this filter prepares the Entity Manager before the request will reach Spring Framework machinery, and closes it after the response is prepared (after the view rendering). So, yes, it does open the Entity Manager for the whole request scope - this doesn't mean that the transaction is opened for the whole scope, because using entity manager you can begin/commit/rollback transactions as needed - I usually use the @Transactional annotation for it - which is good subject for next post ;)

    ReplyDelete
  7. Rob,

    I can only agree with you :) - Lazy Loading can lead to n+1 queries - it usually does, if you don't tune the JPA provider's behavior - I plan one of the next posts about it ;)

    Thanks for the "Autofetch" hint - I'll take a look at it soon :)

    ReplyDelete
  8. For the EJB3 (Stlecho note) - added to my to-post list ;) - will return later to it.

    All,

    thank you for the remarks :) - I owe you next posts ;)

    ReplyDelete