为了看到LazyInitializationException错误并进行处理,我们将使用带有EJB 3的应用程序JSF 2。
帖子主题:
- 了解问题后,为什么会发生LazyInitializationException?
- 通过注释加载集合
- 通过View中的Open Session加载收集(View中的事务)
- 使用PersistenceContextType.EXTENDED的有状态EJB加载收集
- 通过联接查询加载集合
- EclipseLink和惰性集合初始化
在本文的结尾,您将找到要下载的源代码。
注意 :在本文中,我们将找到一个简单的代码,该代码不适用设计模式。 本文的重点是展示LazyInitializationException的解决方案。
您将在这里找到的解决方案适用于Web技术,例如带Struts的JSP,带VRaptor的JSP,带Servlet的JSP,带其他功能的JSF。
模型类
在今天的帖子中,我们将使用“人与狗”类:
package com.model;import javax.persistence.*;@Entity
public class Dog {@Id@GeneratedValue(strategy = GenerationType.AUTO)private int id;private String name;public Dog() {}public Dog(String name) {this.name = name;}//get and set
}
package com.model;import java.util.*;import javax.persistence.*;@Entity
public class Person {@Id@GeneratedValue(strategy = GenerationType.AUTO)private int id;private String name;@OneToMany@JoinTable(name = 'person_has_lazy_dogs')private List<Dog> lazyDogs;public Person() {}public Person(String name) {this.name = name;}// get and set
}
注意,通过这两个类,我们将能够创建LazyInitializationException。 我们有一个带狗名单的人类。
我们还将使用一个类来处理数据库操作(EJB DAO),并使用ManagedBean来帮助我们创建错误并进行处理:
package com.ejb;import java.util.List;import javax.ejb.*;
import javax.persistence.*;import com.model.*;@Stateless
public class SystemDAO {@PersistenceContext(unitName = 'LazyPU')private EntityManager entityManager;private void saveDogs(List<Dog> dogs) {for (Dog dog : dogs) {entityManager.persist(dog);}}public void savePerson(Person person) {saveDogs(person.getLazyDogs());saveDogs(person.getEagerDogs());entityManager.persist(person);}// you could use the entityManager.find() method alsopublic Person findByName(String name) {Query query = entityManager.createQuery('select p from Person p where name = :name');query.setParameter('name', name);Person result = null;try {result = (Person) query.getSingleResult();} catch (NoResultException e) {// no result found}return result;}
}
package com.mb;import javax.ejb.EJB;
import javax.faces.bean.*;import com.ejb.SystemDAO;
import com.model.*;@ManagedBean
@RequestScoped
public class DataMB {@EJBprivate SystemDAO systemDAO;private Person person;public Person getPerson() {return systemDAO.findByName('Mark M.');}
}
为什么会发生LazyInitializationException?
Person类具有一个Dog列表。 显示人员数据的最简单,最胖的方法是使用entityManager.find()方法并遍历页面(xhtml)中的集合。
我们想要的只是让代码波纹管做到这一点……
// you could use the entityManager.find() method alsopublic Person findByName(String name) {Query query = entityManager.createQuery('select p from Person p where name = :name');query.setParameter('name', name);Person result = null;try {result = (Person) query.getSingleResult();} catch (NoResultException e) {// no result found}return result;}
public Person getPerson() {return systemDAO.findByName('Mark M.');}
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN''http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml'xmlns:f='http://java.sun.com/jsf/core'xmlns:h='http://java.sun.com/jsf/html'xmlns:ui='http://java.sun.com/jsf/facelets'>
<h:head></h:head>
<h:body><h:form><h:dataTable var='dog' value='#{dataMB.personByQuery.lazyDogs}'><h:column><f:facet name='header'>Dog name</f:facet>#{dog.name}</h:column></h:dataTable></h:form>
</h:body>
</html>
注意,在上面的代码中,我们要做的就是在数据库中找到一个人并将其狗显示给用户。 如果您尝试使用上面的代码访问该页面,则会看到以下异常:
[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 at org.hibernate.collection.internal.AbstractPersistentCollection.
throwLazyInitializationException(AbstractPersistentCollection.java:393)
[hibernate-core-4.0.1.Final.jar:4.0.1.Final]at org.hibernate.collection.internal.AbstractPersistentCollection.
throwLazyInitializationExceptionIfNotConnected
(AbstractPersistentCollection.java:385) [
hibernate-core-4.0.1.Final.jar:4.0.1.Final]at org.hibernate.collection.internal.AbstractPersistentCollection.
readSize(AbstractPersistentCollection.java:125) [hibernate-core-4.0.1.Final.jar:4.0.1.Final]
为了更好地理解此错误,让我们看看JPA / Hibernate如何处理这种关系。
每次我们在数据库中进行查询时,JPA都会带入该类的所有信息。 这个规则的例外是当我们谈论列表(集合)时。 我们拥有一个公告对象的图像,其中包含70,000封将接收此公告的电子邮件列表。 如果您只想在屏幕上向用户显示公告名称,请想象一下,如果将70,000封电子邮件加载了该名称,JPA的工作就可以了。
JPA为类属性创建了一种名为“延迟加载”的技术。 我们可以通过以下方式定义延迟加载:“仅在需要时才从数据库加载所需的信息”。
注意,在上面的代码中,数据库查询将返回一个Person对象。 当您访问lazyDogs集合时,容器将注意到lazyDogs集合是一个lazy属性,它将“询问” JPA以从数据库加载该集合。
在执行查询的那一刻( 将带来lazyDogs集合 ),将发生异常。 当JPA / Hibernate尝试访问数据库以获取此惰性信息时,JPA将注意到没有打开的集合。 这就是为什么发生异常(缺少打开的数据库连接)的原因。
默认情况下,每个以@Many结尾的关系都会被延迟加载:@OneToMany和@ManyToMany。 默认情况下,将急切加载以@One结尾的每个关系:@ManyToOne和@OneToOne。 如果要设置延迟加载的基本字段(例如,字符串名称),请执行:@Basic(fetch = FetchType.LAZY)。
如果开发人员未将每个基本字段(例如,String,int,double)放在类中,我们将立即加载它们。
关于默认值的一个有趣主题是,对于同一批注,您可能会发现每个JPA实现(EclipseLink,Hibernate,OpenJPA)具有不同的行为。 我们将在稍后讨论。
通过注释加载集合
加载对象时,最简单,最胖的方法是通过注释添加惰性列表。 但这永远不是最好的方法 。
在下面的代码中,我们将介绍如何通过注释热切地加载集合:
@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = 'person_has_eager_dogs')
private List<Dog> eagerDogs;
<h:dataTable var='dog' value='#{dataMB.person.eagerDogs}'><h:column><f:facet name='header'>Dog name</f:facet>#{dog.name}</h:column>
</h:dataTable>
这种方法的优点和缺点:
优点 | 缺点 |
易于设置 | 如果该类具有多个集合,则这将不利于服务器性能 |
该列表将始终与加载的对象一起提供 | 如果只想显示名称或年龄之类的基本类属性,则将所有配置为EAGER的集合加载名称和年龄 |
如果EAGER集合只有几个项目,则此方法将是一个很好的选择。 如果此人只有2条,3条狗,则您的系统将能够非常轻松地处理它。 如果稍后“ Persons狗”收集开始确实增长很多,那么这对服务器性能将不会有好处。
这种方法可以应用于JSE和JEE。
通过View中的Open Session加载收集(View中的事务)
在视图中打开会话(或在视图中打开事务)是一种设计模式,您将使数据库连接保持打开状态,直到用户请求结束。 当应用程序访问一个惰性集合时,Hibernate / JPA会进行数据库查询而不会出现问题,不会引发任何异常。
当将此设计模式应用于Web应用程序时,将使用实现Filter的类,该类将接收所有用户请求。 此设计模式非常容易应用,并且有两个基本操作:打开数据库连接和关闭数据库连接。
您将需要编辑“ web.xml ”并添加过滤器配置。 在下面检查我们的代码如何:
<filter><filter-name>ConnectionFilter</filter-name><filter-class>com.filter.ConnectionFilter</filter-class></filter><filter-mapping><filter-name>ConnectionFilter</filter-name><url-pattern>/faces/*</url-pattern></filter-mapping>
package com.filter;import java.io.IOException;import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;public class ConnectionFilter implements Filter {@Overridepublic void destroy() {}@Resourceprivate UserTransaction utx;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {utx.begin();chain.doFilter(request, response);utx.commit();} catch (Exception e) {e.printStackTrace();}}@Overridepublic void init(FilterConfig arg0) throws ServletException {}
}
<h:dataTable var='dog' value='#{dataMB.person.lazyDogs}'><h:column><f:facet name='header'>Dog name</f:facet>#{dog.name}</h:column>
</h:dataTable>
这种方法的优点和缺点:
优点 | 缺点 |
模型类别将不需要编辑 | 所有交易必须在过滤器类中处理 |
开发人员必须对数据库事务错误非常谨慎。 可以通过ManagedBean / Servlet发送成功消息,但是当数据库提交事务时,可能会发生错误。 | |
可能会发生N + 1效应(如下所示) |
这种方法的主要问题是N + 1效应。 当该方法将一个人返回到用户页面时,该页面将迭代dogs集合。 当页面访问惰性集合时,将触发新的数据库查询以显示狗的惰性列表。 想象一下,如果狗有狗的集合,那么狗就是孩子。 为了加载狗子列表,将触发其他数据库查询。 但是,如果孩子有其他孩子,那么JPA再次会触发一个新的数据库查询……然后就可以了……
这是这种方法的主要问题。 一个查询几乎可以创建无限多个其他查询。
这种方法可以应用于JSE和JEE。
继续本教程的第二部分 。
参考: uaiHebert博客上的JCG合作伙伴 Hebert Coelho 对LazyInitializationException的四个解决方案 。
翻译自: https://www.javacodegeeks.com/2012/07/four-solutions-to-lazyinitializationexc_05.html