LazyInitializationException的四种解决方案–第1部分

在今天的帖子中,我们将讨论常见的LazyInitializationException错误。 我们将看到四种避免该错误的方法,以及每种方法的优缺点。在本文的最后,我们将讨论EclipseLink如何处理该异常。

为了看到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 also
 public 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 {

 @EJB
 private 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 also
 public 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 {

 @Override
 public void destroy() {

 }

 @Resource
 private UserTransaction utx;

 @Override
 public 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();
  }

 }

 @Override
 public 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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
org.hibernate.LazyInitializationException是一个在Hibernate中常见的异常。它表示在尝试访问延迟加载的属性或关联对象时,Hibernate会话已经关闭或不可用,导致无法初始化代理对象。 通常情况下,Hibernate会使用懒加载机制延迟加载关联对象。这意味着当你读取一个实体对象时,它的关联对象并不会立即加载,而是在需要访问关联对象时才会从数据库中加载。然而,如果在Session关闭后尝试访问延迟加载的属性或关联对象,就会抛出org.hibernate.LazyInitializationException异常。 解决org.hibernate.LazyInitializationException的方法有几种: 1. 保持会话打开:一种常见的解决方法是在访问延迟加载属性或关联对象之前保持Hibernate会话打开状态。这可以通过在事务中延长会话的生命周期或者在需要时重新加载实体对象来实现。这样做的目的是确保在访问延迟加载属性或关联对象时,会话仍然有效。 2. 使用Eager加载:可以将关联对象的加载方式从延迟加载改为立即加载(Eager加载)。这样做可以在加载实体对象时同时将关联对象一起加载,避免在访问时出现LazyInitializationException异常。但需要注意的是,如果关联对象的数据量很大或者关联对象之间存在循环引用,使用Eager加载可能会导致性能问题。 3. 手动初始化关联对象:在某些情况下,你可以在Hibernate会话仍然打开时手动初始化延迟加载的关联对象,以避免LazyInitializationException异常。你可以通过调用关联对象的getter方法或使用Hibernate.initialize()方法来实现。 需要根据具体情况选择合适的解决方法。如果你在使用Hibernate时遇到了org.hibernate.LazyInitializationException异常,可以根据上述方法尝试解决该异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值