`
nlslzf
  • 浏览: 1048663 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Four solutions to the LazyInitializationException

 
阅读更多

from http://www.javacodegeeks.com/2012/07/four-solutions-to-lazyinitializationexc_05.html

http://www.javacodegeeks.com/2012/07/four-solutions-to-lazyinitializationexc.html

 

In the post today we will talk about the common LazyInitializationException error. We will see four ways to avoid this error, the advantage and disadvantage of each approach and in the end of this post, we will talk about how the EclipseLink handles this exception.

 

To see the LazyInitializationException error and to handle it, we will use an application JSF 2 with EJB 3.

Topics of the post:

  • Understanding the problem, Why does LazyInitializationException happen?
  • Load collection by annotation
  • Load collection by Open Session in View (Transaction in View)
  • Load collection by Stateful EJB with PersistenceContextType.EXTENDED
  • Load collection by Join Query
  • EclipseLink and lazy collection initialization

At the end of this post you will find the source code to download.

Attention: In this post we will find an easy code to read that does not apply design patterns. This post focus is to show solutions to the LazyInitializationException.

The solutions that you will find here works for web technology like JSP with Struts, JSP with VRaptor, JSP with Servlet, JSF with anything else.

Model Classes

In the post today we will use the class Person and Dog:

01 package com.model;
02  
03 import javax.persistence.*;
04  
05 @Entity
06 public class Dog {
07  
08  @Id
09  @GeneratedValue(strategy = GenerationType.AUTO)
10  private int id;
11  
12  private String name;
13  
14  public Dog() {
15  
16  }
17  
18  public Dog(String name) {
19   this.name = name;
20  }
21  
22  //get and set
23 }
01 package com.model;
02  
03 import java.util.*;
04  
05 import javax.persistence.*;
06  
07 @Entity
08 public class Person {
09  
10  @Id
11  @GeneratedValue(strategy = GenerationType.AUTO)
12  private int id;
13  
14  private String name;
15  
16  @OneToMany
17  @JoinTable(name = 'person_has_lazy_dogs')
18  private List<Dog> lazyDogs;
19  
20  public Person() {
21  
22  }
23  
24  public Person(String name) {
25   this.name = name;
26  }
27  
28  // get and set
29 }

Notice that with this two classes we will be able to create the LazyInitializationException. We have a class Person with a Dog list.

We also will use a class to handle the database actions (EJB DAO) and a ManagedBean to help us to create the error and to handle it:

01 package com.ejb;
02  
03 import java.util.List;
04  
05 import javax.ejb.*;
06 import javax.persistence.*;
07  
08 import com.model.*;
09  
10 @Stateless
11 public class SystemDAO {
12  
13  @PersistenceContext(unitName = 'LazyPU')
14  private EntityManager entityManager;
15  
16  private void saveDogs(List<Dog> dogs) {
17   for (Dog dog : dogs) {
18    entityManager.persist(dog);
19   }
20  }
21  
22  public void savePerson(Person person) {
23    saveDogs(person.getLazyDogs());
24    saveDogs(person.getEagerDogs());
25    entityManager.persist(person);
26  }
27  
28  // you could use the entityManager.find() method also
29  public Person findByName(String name) {
30   Query query = entityManager.createQuery('select p from Person p where name = :name');
31   query.setParameter('name', name);
32  
33   Person result = null;
34   try {
35    result = (Person) query.getSingleResult();
36   catch (NoResultException e) {
37    // no result found
38   }
39  
40   return result;
41  }
42 }
01 package com.mb;
02  
03 import javax.ejb.EJB;
04 import javax.faces.bean.*;
05  
06 import com.ejb.SystemDAO;
07 import com.model.*;
08  
09 @ManagedBean
10 @RequestScoped
11 public class DataMB {
12  
13  @EJB
14  private SystemDAO systemDAO;
15  
16  private Person person;
17  
18  public Person getPerson() {
19   return systemDAO.findByName('Mark M.');
20  }
21 }

Why does LazyInitializationException happen?

The Person class has a Dog list. The easier and fattest way to display a person data would be, to use the entityManager.find() method and iterate over the collection in the page (xhtml).

All that we want was that the code bellow would do this…

01 <b> // you could use the entityManager.find() method also
02  public Person findByName(String name) {
03   Query query = entityManager.createQuery('select p from Person p where name = :name');
04   query.setParameter('name', name);
05  
06   Person result = null;
07   try {
08    result = (Person) query.getSingleResult();
09   catch (NoResultException e) {
10    // no result found
11   }
12  
13   return result;
14  }</b>
1 <b> public Person getPerson() {
2   return systemDAO.findByName('Mark M.');
3  }</b>
01 <b><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
03 <html xmlns='http://www.w3.org/1999/xhtml'
07 <h:head>
08  
09 </h:head>
10 <h:body>
11  <h:form>
12   <h:dataTable var='dog' value='#{dataMB.personByQuery.lazyDogs}'>
13    <h:column>
14     <f:facet name='header'>
15      Dog name
16     </f:facet>
17     #{dog.name}
18    </h:column>
19   </h:dataTable>
20  </h:form>
21 </h:body>
22 </html></b>

Notice that in the code above, all we want to do is to find a person in the database and display its dogs to an user. If you try to access the page with the code above you will see the exception bellow:

01 <b>[javax.enterprise.resource.webcontainer.jsf.application] (http–127.0.0.1-8080-2) Error Rendering View[/getLazyException.xhtml]: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.model.Person.lazyDogs, no session or session was closed                 
02  
03  
04 at org.hibernate.collection.internal.AbstractPersistentCollection.
05 throwLazyInitializationException(AbstractPersistentCollection.java:393)
06 [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
07  
08  
09 at org.hibernate.collection.internal.AbstractPersistentCollection.
10 throwLazyInitializationExceptionIfNotConnected
11 (AbstractPersistentCollection.java:385) [
12 hibernate-core-4.0.1.Final.jar:4.0.1.Final]
13  
14  
15  
16 at org.hibernate.collection.internal.AbstractPersistentCollection.
17 readSize(AbstractPersistentCollection.java:125) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
18 </b>

To understand better this error let us see how the JPA/Hibernate handles the relationship.

Every time we do a query in the database the JPA will bring to all information of that class. The exception to this rule is when we talk about list (collection). Image that we have an announcement object with a list of 70,000 of emails that will receive this announcement. If you want just to display the announcement name to the user in the screen, imagine the work that the JPA would have if the 70,000 emails were loaded with the name.

The JPA created a technology named Lazy Loading to the classes attributes. We could define Lazy Loading by: “the desired information will be loaded (from database) only when it is needed”.

Notice in the above code, that the database query will return a Person object. When you access the lazyDogs collection, the container will notice that the lazyDogs collection is a lazy attribute and it will “ask” the JPA to load this collection from the database.

In the moment of the query (that will bring the lazyDogs collection) execution, an exception will happen. When the JPA/Hibernate tries to access the database to get this lazy information, the JPA will notice that there is no opened collection. That is why the exception happens, the lack of an opened database connection.

Every relationship that finishes with @Many will be lazy loaded by default: @OneToMany and @ManyToMany. Every relationship that finishes with @One will be eagerly loaded by default: @ManyToOne and @OneToOne. If you want to set a basic field (E.g. String name) with lazy loading just do: @Basic(fetch=FetchType.LAZY).

Every basic field (E.g. String, int, double) that we can find inside a class will be eagerly loaded if the developer do not set it as lazy.

A curious subject about default values is that you may find each JPA implementation (EclipseLink, Hibernate, OpenJPA) with a different behavior for the same annotation. We will talk about this later on.

Load collection by annotation

The easier and the fattest way to bring a lazy list when the object is loaded is by annotation. But this will not be the best approach always.

In the code bellow we will se how to eagerly load a collection by annotation:

1 <b>@OneToMany(fetch = FetchType.EAGER)
2 @JoinTable(name = 'person_has_eager_dogs')
3 private List<Dog> eagerDogs;</b>
1 <b><h:dataTable var='dog' value='#{dataMB.person.eagerDogs}'>
2  <h:column>
3   <f:facet name='header'>
4    Dog name
5   </f:facet>
6   #{dog.name}
7  </h:column>
8 </h:dataTable></b>

Pros and Cons of this approach:

Pros

Cons

Easy to set up

If the class has several collections this will not be good to the server performance

The list will always come with the loaded object

If you want to display only a basic class attribute like name or age, all collections configured as EAGER will be loaded with the name and the age

This approach will be a good alternative if the EAGER collection have only a few items. If the Person will only have 2, 3 dogs your system will be able to handle it very easily. If later the Persons dogs collection starts do grow a lot, it will not be good to the server performance.

This approach can be applied to JSE and JEE.

Load collection by Open Session in View (Transaction in View)

Open Session in View (or Transaction in View) is a design pattern that you will leave a database connection opened until the end of the user request. When the application access a lazy collection the Hibernate/JPA will do a database query without a problem, no exception will be threw.

This design pattern, when applied to web applications, uses a class that implements a Filter, this class will receive all the user requests. This design pattern is very easy to apply and there is two basic actions: open the database connection and close the database connection.

You will need to edit the “web.xml” and add the filter configurations. Check bellow how our code will look like:

1 <b> <filter>
2   <filter-name>ConnectionFilter</filter-name>
3   <filter-class>com.filter.ConnectionFilter</filter-class>
4  </filter>
5  <filter-mapping>
6   <filter-name>ConnectionFilter</filter-name>
7   <url-pattern>/faces/*</url-pattern>
8  </filter-mapping></b>
01 <b>package com.filter;
02  
03 import java.io.IOException;
04  
05 import javax.annotation.Resource;
06 import javax.servlet.*;
07 import javax.transaction.UserTransaction;
08  
09 public class ConnectionFilter implements Filter {
10  
11  @Override
12  public void destroy() {
13  
14  }
15  
16  @Resource
17  private UserTransaction utx;
18  
19  @Override
20  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throwsIOException, ServletException {
21   try {
22    utx.begin();
23    chain.doFilter(request, response);
24    utx.commit();
25   catch (Exception e) {
26    e.printStackTrace();
27   }
28  
29  }
30  
31  @Override
32  public void init(FilterConfig arg0) throws ServletException {
33  
34  }
35 }</b>
1 <b><h:dataTable var='dog' value='#{dataMB.person.lazyDogs}'>
2  <h:column>
3   <f:facet name='header'>
4    Dog name
5   </f:facet>
6   #{dog.name}
7  </h:column>
8 </h:dataTable></b>

Pros and Cons of this approach:

Pros

Cons

The model classes will not need to be edited

All transaction must be handled in the filter class

 

The developer must be very cautious with database transaction errors. A success message can be sent by the ManagedBean/Servlet, but when the database commits the transacion an error may happen

 

N+1 effect may happen (more detail bellow)

The major issue of this approach is the N+1 effect. When the method returns a person to the user page, the page will iterate over the dogs collection. When the page access the lazy collection a new database query will be fired to bring the dog lazy list. Imagine if the Dog has a collection of dogs, the dogs children. To load the dogs children list other database query would be fired. But if the children has other children, again the JPA would fire a new database query… and there it goes…

This is the major issue of this approach. A query can create almost a infinity number of other queries.

 

Load collection by Stateful EJB with PersistenceContextType.EXTENDED

This approach can be applied only to applications that works with Full JEE environments: to use a EJB with PersistenceContextType.EXTENDED.

Check the code below how the DAO would look like:

01 <b>package com.ejb;
02  
03 import javax.ejb.Stateful;
04 import javax.persistence.*;
05  
06 import com.model.Person;
07  
08 @Stateful
09 public class SystemDAOStateful {
10  @PersistenceContext(unitName = 'LazyPU', type=PersistenceContextType.EXTENDED)
11  private EntityManager entityManager;
12  
13  public Person findByName(String name) {
14   Query query = entityManager.createQuery('select p from Person p where name = :name');
15   query.setParameter('name', name);
16  
17   Person result = null;
18   try {
19    result = (Person) query.getSingleResult();
20   catch (NoResultException e) {
21    // no result found
22   }
23  
24   return result;
25  }
26 }</b>
01 <b>public class DataMB {
02  // other methods and attributes
03  
04  @EJB
05  private SystemDAOStateful daoStateful;
06  
07  public Person getPersonByStatefulEJB() {
08   return daoStateful.findByName('Mark M.');
09  }
10 }</b>
1 <b><h:dataTable var='dog' value='#{dataMB.personByStatefulEJB.lazyDogs}'>
2  <h:column>
3   <f:facet name='header'>
4    Dog name
5   </f:facet>
6   #{dog.name}
7  </h:column>
8 </h:dataTable></b>

Pros and Cons of this approach:

Pros

Cons

The container will control the database transaction

Works only for JEE

The model classes will not need to be edited

N+1 effect may happen

 

A great amount of Stateful EJBs may affect the container memory.

This approach may create the N+1 effect and the Stateful EJB has a characteristic of not being removed/destroyed while the its session is not expired or until it lost its reference.

Warning: It is not a good practice to hold a reference to an injected EJB in objects that remain in a Pool. The JSF will create a ManagedBean pool to handle better the user requests; when it is necessary the container will increase or decrease the number of ManagedBeans in the pool. In the code of this post imagine if the container create 100 instances of ManagedBeans in the pool, the server will hold 100 Stateful EJBs in memory. The solution to this problem would be a JNDI LookUp to the Stateful EJB.

Load collection by Join Query

This solution is easy to understand and to apply.

See the code below:

01 <b> public Person findByNameWithJoinFech(String name) {
02   Query query = entityManager.createQuery('select p from Person p join fetch p.lazyDogs where p.name = :name');
03   query.setParameter('name', name);
04  
05   Person result = null;
06   try {
07    result = (Person) query.getSingleResult();
08   catch (NoResultException e) {
09    // no result found
10   }
11  
12   return result;
13  }</b>
1 <b> public Person getPersonByQuery() {
2   return systemDAO.findByNameWithJoinFech('Mark M.');
3  }</b>
1 <b> <h:dataTable var='dog' value='#{dataMB.personByQuery.lazyDogs}'>
2   <h:column>
3    <f:facet name='header'>
4     Dog name
5    </f:facet>
6    #{dog.name}
7   </h:column>
8  </h:dataTable></b>

Pros and Cons of this approach:

Pros

Cons

Just one query will be fired in the database

It would be necessary one query for each accessed collection/lazy attribute

The model classes will not need to be edited

 

Will bring only the desired data

 

The N+1 effect will not happen

 

This approach has the disadvantage of the need of a new query to access each model class collection/lazy attribute. If we need to query only the Person dogs we would need of a specific query. Imagine that we would need to query for the Person emails, it would be necessary a different query.

This approach can be applied to JSE and JEE.

EclipseLink and lazy collection initialization

The relationships default values are:

Relationship

Fetch

@OneToOne

EAGER

@OneToMany

LAZY

@ManyToOne

EAGER

@ManyToMany

LAZY

But the JPA Spec* says that:

The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified. In particular, lazy fetching might only be available for Basic mappings for which property-based access is used.

As you can see in the text above, the JPA implementation may ignore the hint strategy if it wants to. The EclipseLink has a behavior with JEE and other behavior to JSE. You can see each behavior here:http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#What_You_May_Need_to_Know_About_EclipseLink_JPA_Lazy_Loading

We can find in the internet some people saying that even with a lazy collection the EclipseLink does the n+1 queries when the entity is loaded. And we can find this behavior of users with Glassfish and EJB.

Below you will see some tips to use correctly lazy load with EclipseLink:

* JSR-000220 Enterprise JavaBeans 3.0 Final Release (persistence) 9.1.18 and will repeat to the otters JPA relationships.

The end!

In my opinion the best solution is the Join Fetch Query. It is up to you to choose the best solution to your application.

Click here to download the source code of this post. If you want to run the code of this post you will need to create a database named LazyExceptionDB and the JBoss module. Attached to the source code is the Postgres module. If you want to see how to set up the datasource and the Postgres or MySQL module you can see it here: Full WebApplication JSF EJB JPA JAAS.

I hope this post might help you.

If you have any comment or doubt just post it.

See you soon.

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics