程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Hibernnate延遲加載策略(這麼詳細你還看不懂),hibernnate延遲

Hibernnate延遲加載策略(這麼詳細你還看不懂),hibernnate延遲

編輯:JAVA綜合教程

Hibernnate延遲加載策略(這麼詳細你還看不懂),hibernnate延遲


好久沒有認真寫過博客了,今天就好好的寫一篇吧!!!!!!!!!

當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 模式 的解決方案!!

代碼示例:

HibernateUtil代碼

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

biz層代碼:

HibernateDao dao=new HibernateDao();
    public Object get(Class clazz,Serializable id){
           Object obj= dao.get(clazz, id);return obj;
    }

dao層代碼:

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中的配置:

<?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 模式。

在上面都已經用代碼做例子了,夠清楚了的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved