為了看到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