LazyInitializationException的四种解决方案–第1部分
為了看到LazyInitializationException錯(cuò)誤并進(jìn)行處理,我們將使用帶有EJB 3的應(yīng)用程序JSF 2。
帖子主題:
- 了解問(wèn)題后,為什么會(huì)發(fā)生LazyInitializationException?
- 通過(guò)注釋加載集合
- 通過(guò)View中的Open Session加載收集(View中的事務(wù))
- 使用PersistenceContextType.EXTENDED的有狀態(tài)EJB加載收集
- 通過(guò)聯(lián)接查詢加載集合
- EclipseLink和惰性集合初始化
在本文的結(jié)尾,您將找到要下載的源代碼。
注意 :在本文中,我們將找到一個(gè)簡(jiǎn)單的代碼,該代碼不適用設(shè)計(jì)模式。 本文的重點(diǎn)是展示LazyInitializationException的解決方案。
您將在這里找到的解決方案適用于Web技術(shù),例如帶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 }注意,通過(guò)這兩個(gè)類,我們將能夠創(chuàng)建LazyInitializationException。 我們有一個(gè)帶狗名單的人類。
我們還將使用一個(gè)類來(lái)處理數(shù)據(jù)庫(kù)操作(EJB DAO),并使用ManagedBean來(lái)幫助我們創(chuàng)建錯(cuò)誤并進(jìn)行處理:
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.');} }為什么會(huì)發(fā)生LazyInitializationException?
Person類具有一個(gè)Dog列表。 顯示人員數(shù)據(jù)的最簡(jiǎn)單,最胖的方法是使用entityManager.find()方法并遍歷頁(yè)面(xhtml)中的集合。
我們想要的只是讓代碼波紋管做到這一點(diǎn)……
// 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>注意,在上面的代碼中,我們要做的就是在數(shù)據(jù)庫(kù)中找到一個(gè)人并將其狗顯示給用戶。 如果您嘗試使用上面的代碼訪問(wèn)該頁(yè)面,則會(huì)看到以下異常:
[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]為了更好地理解此錯(cuò)誤,讓我們看看JPA / Hibernate如何處理這種關(guān)系。
每次我們?cè)跀?shù)據(jù)庫(kù)中進(jìn)行查詢時(shí),JPA都會(huì)帶入該類的所有信息。 這個(gè)規(guī)則的例外是當(dāng)我們談?wù)摿斜?#xff08;集合)時(shí)。 我們擁有一個(gè)公告對(duì)象的圖像,其中包含70,000封將接收此公告的電子郵件列表。 如果您只想在屏幕上向用戶顯示公告名稱,請(qǐng)想象一下,如果將70,000封電子郵件加載了該名稱,JPA的工作就可以了。
JPA為類屬性創(chuàng)建了一種名為“延遲加載”的技術(shù)。 我們可以通過(guò)以下方式定義延遲加載:“僅在需要時(shí)才從數(shù)據(jù)庫(kù)加載所需的信息”。
注意,在上面的代碼中,數(shù)據(jù)庫(kù)查詢將返回一個(gè)Person對(duì)象。 當(dāng)您訪問(wèn)lazyDogs集合時(shí),容器將注意到lazyDogs集合是一個(gè)lazy屬性,它將“詢問(wèn)” JPA以從數(shù)據(jù)庫(kù)加載該集合。
在執(zhí)行查詢的那一刻( 將帶來(lái)lazyDogs集合 ),將發(fā)生異常。 當(dāng)JPA / Hibernate嘗試訪問(wèn)數(shù)據(jù)庫(kù)以獲取此惰性信息時(shí),JPA將注意到?jīng)]有打開(kāi)的集合。 這就是為什么發(fā)生異常(缺少打開(kāi)的數(shù)據(jù)庫(kù)連接)的原因。
默認(rèn)情況下,每個(gè)以@Many結(jié)尾的關(guān)系都會(huì)被延遲加載:@OneToMany和@ManyToMany。 默認(rèn)情況下,將急切加載以@One結(jié)尾的每個(gè)關(guān)系:@ManyToOne和@OneToOne。 如果要設(shè)置延遲加載的基本字段(例如,字符串名稱),請(qǐng)執(zhí)行:@Basic(fetch = FetchType.LAZY)。
如果開(kāi)發(fā)人員未將每個(gè)基本字段(例如,String,int,double)放在類中,我們將立即加載它們。
關(guān)于默認(rèn)值的一個(gè)有趣主題是,對(duì)于同一批注,您可能會(huì)發(fā)現(xiàn)每個(gè)JPA實(shí)現(xiàn)(EclipseLink,Hibernate,OpenJPA)具有不同的行為。 我們將在稍后討論。
通過(guò)注釋加載集合
加載對(duì)象時(shí),最簡(jiǎn)單,最胖的方法是通過(guò)注釋添加惰性列表。 但這永遠(yuǎn)不是最好的方法 。
在下面的代碼中,我們將介紹如何通過(guò)注釋熱切地加載集合:
@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>這種方法的優(yōu)點(diǎn)和缺點(diǎn):
優(yōu)點(diǎn) | 缺點(diǎn) |
易于設(shè)置 | 如果該類具有多個(gè)集合,則這將不利于服務(wù)器性能 |
該列表將始終與加載的對(duì)象一起提供 | 如果只想顯示名稱或年齡之類的基本類屬性,則將所有配置為EAGER的集合加載名稱和年齡 |
如果EAGER集合只有幾個(gè)項(xiàng)目,則此方法將是一個(gè)很好的選擇。 如果此人只有2條,3條狗,則您的系統(tǒng)將能夠非常輕松地處理它。 如果稍后“ Persons狗”收集開(kāi)始確實(shí)增長(zhǎng)很多,那么這對(duì)服務(wù)器性能將不會(huì)有好處。
這種方法可以應(yīng)用于JSE和JEE。
通過(guò)View中的Open Session加載收集(View中的事務(wù))
在視圖中打開(kāi)會(huì)話(或在視圖中打開(kāi)事務(wù))是一種設(shè)計(jì)模式,您將使數(shù)據(jù)庫(kù)連接保持打開(kāi)狀態(tài),直到用戶請(qǐng)求結(jié)束。 當(dāng)應(yīng)用程序訪問(wèn)一個(gè)惰性集合時(shí),Hibernate / JPA會(huì)進(jìn)行數(shù)據(jù)庫(kù)查詢而不會(huì)出現(xiàn)問(wèn)題,不會(huì)引發(fā)任何異常。
當(dāng)將此設(shè)計(jì)模式應(yīng)用于Web應(yīng)用程序時(shí),將使用實(shí)現(xiàn)Filter的類,該類將接收所有用戶請(qǐng)求。 此設(shè)計(jì)模式非常容易應(yīng)用,并且有兩個(gè)基本操作:打開(kāi)數(shù)據(jù)庫(kù)連接和關(guān)閉數(shù)據(jù)庫(kù)連接。
您將需要編輯“ web.xml ”并添加過(guò)濾器配置。 在下面檢查我們的代碼如何:
<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>這種方法的優(yōu)點(diǎn)和缺點(diǎn):
優(yōu)點(diǎn) | 缺點(diǎn) |
模型類別將不需要編輯 | 所有交易必須在過(guò)濾器類中處理 |
開(kāi)發(fā)人員必須對(duì)數(shù)據(jù)庫(kù)事務(wù)錯(cuò)誤非常謹(jǐn)慎。 可以通過(guò)ManagedBean / Servlet發(fā)送成功消息,但是當(dāng)數(shù)據(jù)庫(kù)提交事務(wù)時(shí),可能會(huì)發(fā)生錯(cuò)誤。 | |
可能會(huì)發(fā)生N + 1效應(yīng)(如下所示) |
這種方法的主要問(wèn)題是N + 1效應(yīng)。 當(dāng)該方法將一個(gè)人返回到用戶頁(yè)面時(shí),該頁(yè)面將迭代dogs集合。 當(dāng)頁(yè)面訪問(wèn)惰性集合時(shí),將觸發(fā)新的數(shù)據(jù)庫(kù)查詢以顯示狗的惰性列表。 想象一下,如果狗有狗的集合,那么狗就是孩子。 為了加載狗子列表,將觸發(fā)其他數(shù)據(jù)庫(kù)查詢。 但是,如果孩子有其他孩子,那么JPA再次會(huì)觸發(fā)一個(gè)新的數(shù)據(jù)庫(kù)查詢……然后就可以了……
這是這種方法的主要問(wèn)題。 一個(gè)查詢幾乎可以創(chuàng)建無(wú)限多個(gè)其他查詢。
這種方法可以應(yīng)用于JSE和JEE。
繼續(xù)本教程的第二部分 。
參考: uaiHebert博客上的JCG合作伙伴 Hebert Coelho 對(duì)LazyInitializationException的四個(gè)解決方案 。
翻譯自: https://www.javacodegeeks.com/2012/07/four-solutions-to-lazyinitializationexc_05.html
總結(jié)
以上是生活随笔為你收集整理的LazyInitializationException的四种解决方案–第1部分的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 京东黄金卖出钱去哪了?
- 下一篇: 终极JPA查询和技巧列表–第2部分