好久沒有認真寫過博客了,今天就好好的寫一篇吧!!!!!!!!!
當Hibernate 從數據庫中加載某個對象(例如:Dept對象)時,如果同時自動加載所有的關聯的某個對象(例如:Emp對象),而程序實際上僅僅需要訪問Dept對象,那麼這些關聯的Emp對象就白白浪費了許多內存空間。發生這種情況的原因是:就是立即加載的問題。
1.什麼是立即加載呢?
Hibernate 查詢 Dept 對象時,立即加載並加載與之關聯的Emp對象,這種查詢策略稱為 立即加載。
立即加載存在兩大不足:
1.select 語句的數目太多,需要頻繁地訪問數據庫,會影響查詢性能。
2.在應用程序只需要訪問Dept對象時,而不需要訪問Emp對象的場合,加載Emp對象完全是多余的操作,這些多余的Emp對象就白白的浪費了許多內存空間。
那麼我們必須要解決這個問題,所以就要談到了“延遲加載”的知識了,延遲加載策略能避免加載應用程序不需要訪問的關聯對象,以優化查詢性能。
不過我們要知道有多種查詢策略,接下來我們就一起來分析每一種查詢策略的加載問題。
第一種:類級別的查詢策略
類級別可選的加載策略包括立即加載和延遲加載,默認是延遲加載,如果,<class>元素的lazy的屬性為true,表示采用延遲加載;如果lazy 屬性 為 false,表示采用立即加載。
我們現在以代碼來解釋是最好的辦法。
1.立即加載策略
我們在Dept.hbm.xml文件中 加 lazy=“false” 屬性,即可。
表結構:
測試代碼:
Session session = HibernateUtil.currentSession(); session.beginTransaction(); Dept dept = (Dept)session.load(Dept.class, 1); System.out.println("部門名稱"+dept.getdName()); System.out.println("==================="); Dept dept2 = (Dept)session.load(Dept.class, 1); System.out.println("部門名稱"+dept2.getdName()); session.getTransaction().commit(); HibernateUtil.closeSessio();
測試結果:
我們知道使用Load方法加載的是代理對象,只會在屬性裡保存一個OID,但是如果在Dept映射文件中配置了類級別的lazy為false就代表加載該對象時立即加載,也就是立即檢索一次數據庫,發出了一條sql語句。
2.延遲加載
類級別的默認加載策略就是延遲加載。在在Dept.hbm.xml文件中 ,以下兩種方式都表示延遲加載策略。
或是
如果程序加載一個持久化對象的目的是為了訪問它的屬性,這是我們可以采用立即加載,但是如果程序加載一個持久化對象的目的是為了獲得它的引用,這是我們可以采用延遲加載,無須訪問Dept對象的屬性。
看例子:
Dept dept = (Dept)session.load(Dept.class, 1); System.out.println("部門名稱"+dept.getdName()); Employee emp=new Employee(); emp.setEname("李四"); emp.setDept(dept); session.save(emp);
這段代碼向數據庫保存了 一個Employee 對象,它與已經存在的一個Dept持久化對象關聯。如果在Dept 類級別 采用延遲加載,則 session.load()方法不會執行訪問DEPT 表的select 語句,只返回一個Dept的代理對象,它的deptNo的屬性值為1,其余屬性都為NULL。session.save()方法執行的sql語句:
所以當,<class>元素的lazy屬性為true時,會影響session.load()方法的各種運行時行為。舉例說明:
1.如果加載的Dept對象在數據庫中不存在時,不會拋出異常,只有運行dept.getxxx()時,才會拋出異常。
測試代碼:
Session session = HibernateUtil.currentSession(); session.beginTransaction(); Dept dept = (Dept)session.load(Dept.class, 3); System.out.println("部門名稱"+dept.getdName()); Employee emp=new Employee(); emp.setEname("李四"); emp.setDept(dept); session.save(emp);
當 deptNo為3不存在時,會拋出以下異常:
2.如果在在整個Session范圍內,應用程序沒有訪問過的Dept對象,那麼Dept代理類的實例一直不會被初始化,Hibernater 不會執行任何的select語句。以下代碼試圖在關閉Session後訪問的Dept游離對象:
測試代碼:
Session session = HibernateUtil.currentSession(); session.beginTransaction(); Dept dept = (Dept)session.load(Dept.class, 3); HibernateUtil.closeSessio(); System.out.println("部門名稱"+dept.getdName()); session.getTransaction().commit(); HibernateUtil.closeSessio();
從代碼中我們可以看出,session被提前關閉,所以dept引用的Dept代理類的實例在Session范圍內始終沒有被初始化,所以當執行到 System.out.println("部門名稱"+dept.getdName())時,會拋出以下異常:
由此可見,Dept代理類的實例只有在當前的Session范圍內才能被初始化。
3.import org.hibernate.Initialized()靜態方法,用於在Session范圍內顯式初始化代理類實例,isInitialized()方法用於判斷代理類實例是否已經被初始化。
代碼:
Dept dept = (Dept)session.load(Dept.class, 1); if(!Hibernate.isInitialized(dept)){ Hibernate.initialize(dept); HibernateUtil.closeSessio(); System.out.println("部門名稱"+dept.getdName()); }
以上代碼在Session范圍內通過Hibernate 類的Initialized()方法顯式初始化了Dept代理類實例,因此關閉Session關閉後,可以正常訪問Dept的游離對象。
4.當程序訪問代理類實例的getDeptNo()方法時,不會觸發Hibernate 初始化 代理類實例的行為。例如:
代碼:
Dept dept = (Dept)session.load(Dept.class, 1); System.out.println("編號:"+ dept.getDeptNo()); HibernateUtil.closeSessio(); System.out.println("部門名稱"+dept.getdName());
當程序訪問dept.getDeptNo()方法時,該方法直接返回Dept代理類的實例OID值,無須查詢數據庫。由於變量dept始終引用的是沒有初始化的Dept代理類的實例,因此當Session關閉後再執行dept.getdName()方法,會拋出以下異常。
但是值得我們注意的是:不管Dept.hbm.xml文件的<class>元素的屬性是true還是false,Session 的get方法及Query對象的list方法在Dept類級別總是使用立即加載策略。舉例說明:
1.Session的get方法總是立即到數據庫中查詢Dept查詢對象,如果在數據庫中不存在相應的數據,就會返回NULL,例如:
代碼:
Session session = HibernateUtil.currentSession(); session.beginTransaction(); Dept dept = (Dept)session.get(Dept.class, 3); System.out.println(dept);
結果:
由此可知,get方法永遠不會執行Dept的代理類實例。
2.Query的list方法總是立即到數據庫中查詢Dept對象
代碼:
List<Dept> query = session.createQuery("from Dept").list(); for (Dept dept : query) { System.out.println(dept.getdName()); }
結果:
到了這裡,算是把第一種類級別的查詢策略寫的差不多了。
第二種:一對多和多對一關聯的查詢策略
添加一個小知識點:
01.一對多或者多對多檢索策略由lazy和fetch共同確定
02.fetch取值
Join:迫切 Lazy:決定關聯對象初始化時機
左外連接
Select:多條簡單SQL(默認值)
Subselect:子查詢
03.fetch和lazy組合
解析:fetch=”join” lazy會被忽略,迫切左外連接的立即檢索
Fetch=”s Fetch:決定SQL語句構建形式
elect” lazy=”false” 多條簡單SQL立即檢索
Fetch=”select” lazy=”true” 多條語句延遲檢索
Fetch=”select” lazy=”extra” 多條語句及其懶惰檢索
Fetch=”subselect” lazy=”false” 子查詢立即檢索
Fetch=”subselect” lazy=”true” 子查詢延遲檢索
Fetch=”subselect” lazy=”extra” 子查詢及其懶惰檢索
Extra:及其懶惰,只有訪問集合對象的屬性時才會加載,訪問集合本身的屬性時(例如,集合大小,生成count),不會立即加載。
注意:query的list()會忽略映射文件配置的左外連接查詢,fetch,此時lazy屬性重新生效。
在映射文件中,用<SET>元素來配置一對多關聯及多對一關聯關系的加載策略。Dept.hbm.xml文件中的一下代碼用於配置Dept和Employee類的一對多關聯關系:
<!-- 雙向 cascade:級聯 inverse:反轉 --> <!--set表明Dept類的emps屬性為set集合類型 --> <!--order-by 對集合排序 order-by="dName asc order-by="dName desc--> <set name="emps" inverse="true" lazy="true"> <!--employee表的外鍵 deptNo --> <key column="deptNo"></key> <!--一對多 class 屬性設定與所關聯的持久化類 為employee --> <one-to-many class="Employee"/> </set>
這裡的<set>元素有lazy屬性,主要取決於emps集合被初始化的時機,到底是在加載Dept對象時就被初始化,還是在程序訪問emps集合時被初始化。
1.立即加載
Dept.hbm.xml的配置文件:
測試代碼:
Dept dept = (Dept)session.get(Dept.class, 1); System.out.println(dept.getdName());
結果:執行Session的get方法時,對於Dept對象采用類級別的立即加載策略,對於Dept對象的emps集合(Dept關聯所有的employee對象),采用一對多關聯的立即加載策略。
從這個結果我們可以看到,Hibernate加載了一個Dept對象和Employee對象,但是我們知道很多情況下,不需要訪問Employee對象,所以我們就得用了 延遲加載策略。
2.延遲加載
對於<set>元素,應該優先考慮使用的默認延遲加載策略。
測試代碼:
Dept dept = (Dept)session.get(Dept.class, 1); System.out.println(dept.getdName());
結果:
很明顯,只執行了一條sql語句,即僅僅加載了Dept對象。
Session的get方法,返回的是Dept對象的emps屬性引用一個沒有被初始化的集合代理類實例。換句話說,此時的emps集合中沒有存放任何Emp對象,只有emps集合代理類實例被初始化時,才回到數據庫查詢所有與Dept關聯的Emp對象。
測試代碼:
Dept dept = (Dept)session.get(Dept.class, 1); System.out.println(dept.getdName()); for (Employee emp : dept.getEmps()) { System.out.println(emp.getEname()); }
結果:
那麼,Dept對象的emps屬性引用的集合代理類實例何時被初始化呢?主要包括以下兩種情況:
01.當應用程序第一次訪問它時,如調用 iterator(),size(),isEmpty(),或是 contains()方法時:
代碼:
Dept dept = (Dept)session.get(Dept.class, 1); Set<Employee> emp=dept.getEmps(); System.out.println(dept.getdName()); Iterator<Employee> itee=emp.iterator();//emps被初始化
02.通過hibernate的靜態方法initialize()來初始化它。
Dept dept = (Dept)session.get(Dept.class, 1); Set<Employee> emp=dept.getEmps(); System.out.println(dept.getdName()); Hibernate.initialize(emp);//emps被初始化
3.增強延遲加載
lazy="extra"
配置如下:
增強延遲加載策略能進一步延遲Dept對象的emps集合代理類實例初始化時機。當應用程序第一次訪問emps屬性的iterator()時,會導致emps集合代理類的實例初始化。但是當當應用程序第一次size(),isEmpty(),或是 contains()方法時,emps不會初始化emps集合代理實例。僅僅通過查詢select語句必要信息。
測試代碼:
Dept dept = (Dept)session.get(Dept.class, 1);
//不會初始化emps集合代理類實例
int size = dept.getEmps().size();
System.out.println(size);
//會初始化emps集合代理類實例
Iterator<Employee> iterator = dept.getEmps().iterator();
System.out.println(iterator);
結果:
現在是第三種:多對一關聯的查詢策略
lazy=proxy
Employee.hbm.xml中但我配置:
1.延遲加載策略。
測試代碼:
Employee em=(Employee) session.get(Employee.class, 21);//僅僅執行em對象的sql語句 Dept dept = em.getDept(); System.out.println(dept.getdName());//執行Dept對象
當Sesson執行get()方法時,僅僅立即執行查詢Employee對象的select語句。當Employee 對象引用Dept代理類實例,這個代理類實例的IOD由Employee 表的DeptNo外鍵值決定。 當執行dept.getdName()時,hibernate 初始化Dept代理類實例,執行以下select語句到數據庫中加載Dept對象。
結果:
無代理延遲加載:
lazy="no-proxy"
測試代碼:
Employee em=(Employee) session.get(Employee.class, 21);//僅僅執行em對象的sql語句 Dept dept = em.getDept(); System.out.println(dept.getdName());//執行Dept對象
如果Employee對象的dept屬性使用無代理延遲加載,即<many-to-many>元素的lazy屬性為no-proxy,當執行 get方法時,加載的Employee的dept屬性為NULL,當執行到 em.getDept()時,將觸發hibernate執行查詢Dept 表的select 語句,從而加載Dept對象。
結果:
由此可見,當lazy為proxy 時,可以延長延遲加載 Dept對象的時間,而當lazy屬性為no-proxy時,則可以避免使用由hibernate 提供的Dept代理類實例,使用hibernate 對程序 提供更加透明的持久化服務。
立即加載:
lazy=“false”
測試:
Employee em=(Employee) session.get(Employee.class, 21);
結果:
可以看到,執行了兩條sql語句。
Open Session In View 模式9
Open Session In View 模式的作用:
Hibernate 允許對關聯對象、屬性進行延遲加載,但是必須保證延遲加載的操作限於同一個 Hibernate Session 范圍之內進行。如果 Service 層返回一個啟用了延遲加載功能的領域對象給 Web 層,當 Web 層訪問到那些需要延遲加載的數據時,由於加載領域對象的 Hibernate Session 已經關閉,這些導致延遲加載數據的訪問異常。
在Java Web 應用中,通常需要調用ibernate API 獲取到顯示的某個要顯示的某個對象並傳給相應但的視圖JSP, 並在JSP中從這個對象導航到與之關聯的對象或集合數據。這些關聯對象或集合數據如果是被延遲加載的,hibernate 就會拋出以下異常:
這是因為在調用完hibernate完之後,Session 對象已經關閉了。針對這個問題,hibernate 社區提供了Open Session In View 模式 的解決方案!!
代碼示例:
private static final ThreadLocal<Session> sessionTL=new ThreadLocal<Session>(); //私有的靜態的配置對象 private static Configuration configuration; //私有的靜態的工廠對象 private final static SessionFactory sessionFactory; //靜態代碼塊,負責給成員變量賦值 static{ configuration=new Configuration().configure(); sessionFactory=configuration.buildSessionFactory(); } //從SessionFactory 連接池 獲取一個和當前thread bind session public static Session currentSession(){ //2.返回當前的線程其對應的線程內部變量
//sessionTL的get()方法根據當前線程返回其對應的線程內部變量,
//也就是我們需要的Session,多線程情況下共享數據庫連接是不安全的。
//ThreadLocal保證了每個線程都有自己的Session.
Session session=sessionTL.get(); //如果當前線程是session 為空=null ,則打開一個新的Session if(session==null){ //創建一個session對象 session=sessionFactory.openSession(); //保存該Sessioon對象到ThreadLocal中 sessionTL.set(session); } return session; } //關閉Session public static void closeSessio(){ Session session=sessionTL.get(); sessionTL.set(null); session.close(); }
HibernateDao dao=new HibernateDao(); public Object get(Class clazz,Serializable id){ Object obj= dao.get(clazz, id);return obj; }
public Object get(Class clazz,Serializable id){ Object result= HibernateUtils.currentSession().load(clazz, id); return result; }
filter層代碼:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Session session = null; Transaction tx = null; try { session = HibernateUtils.currentSession(); System.out.println("filter\t"+session.hashCode()); tx = session.beginTransaction(); // 執行請求處理鏈 雙向過濾 chain.doFilter(request, response); // 返回響應時,提交事務 tx.commit(); } catch (HibernateException e) { e.printStackTrace(); tx.rollback(); } finally { // 關閉session HibernateUtils.closeSession(); } }
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <!-- 過濾器 --> <filter> <filter-name>openSessionInView</filter-name> <filter-class>cn.happy.filter.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>openSessionInView</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
index.jsp頁面的代碼:
<body> <% HibernateBiz biz=new HibernateBiz(); Emp emp=(Emp)biz.get(Emp.class,1); %> <%=emp.getEmpName() %> </body>
這個代碼只是一個例子而已,你可以寫別樣的代碼。這樣就算完成了Open Session In View 模式。
再總結一遍:
關於No Session的這個問題,有了六種的解決方案:
方案一
在biz層 把load 改成get
方案二
/* if (!Hibernate.isInitialized(obj)) {
Hibernate.initialize(obj);
}*/
方案 三 :在 映射文件中 ,類級別 <set> 中加上 lazy =“false”
方案四: 在biz 層 先用一道 需要的UI使用 到的屬性 ,然後在biz關閉
方案五:把實體類 改成 用 final 修飾,我們知道,延遲加載的原因是 內存中 有代理對象 (其實是emp 類的子類),所以當我們設為 該類 不能 有子類
方案六:Open Session In View 模式。
在上面都已經用代碼做例子了,夠清楚了的。