1. 程式人生 > >Hibernate深入理解----03操作Session快取方法(flush、refresh、clear,事務隔離級別)

Hibernate深入理解----03操作Session快取方法(flush、refresh、clear,事務隔離級別)

    Session 介面是 Hibernate 嚮應用程式提供的操縱資料庫的最主要的介面, 它提供了基本的儲存, 更新, 刪除和載入 Java 物件的方法.
    Session 具有一個快取, 位於快取中的物件稱為持久化物件, 它和資料庫中的相關記錄對應. Session 能夠在某些時間點, 按照快取中物件的變化來執行相關的 SQL 語句, 來同步更新資料庫, 這一過程被稱為重新整理快取(flush)
    站在持久化的角度, Hibernate 把物件分為 4 種狀態: 持久化狀態, 臨時狀態, 遊離狀態, 刪除狀態. Session 的特定方法能使物件從一個狀態轉換到另一個狀態

1、Session快取

    在 Session 介面的實現中包含一系列的 Java 集合, 這些 Java 集合構成了 Session 快取. 只要 Session 例項沒有結束生命週期, 且沒有清理快取,則存放在它快取中的物件也不會結束生命週期
    Session 快取可減少 Hibernate 應用程式訪問資料庫的頻率。

    @Test
    public void testSessionCache(){
        News news = (News) session.get(News.class, 1);
        System.out.println(news);

        News news2 = (News) session.get(News.class, 1);
        System.out.println(news2);

        //只發了一條select語句
        System.out.println(news == news2); //true
    }

2、操作Session快取


2-1.flush
    /**
     * flush(): 使資料表中的記錄和 Session 快取中的物件的狀態保持一致. 為了保持一致, 則可能會發送對應的 SQL 語句.
     * 1. 在 Transaction 的 commit() 方法中: 先呼叫 sessionPojo 的 flush 方法(隱式flush), 再提交事務
     * 2. 顯示flush() 方法會可能會發送 SQL 語句, 但不會提交事務!!!
     *
     * 3. 注意: 在未提交事務或顯式的呼叫 sessionPojo.flush() 方法之前, 也有可能會進行 flush() 操作.
     * 1). 執行 HQL 或 QBC 查詢, 會先進行 flush() 操作, 以得到資料表的最新的記錄
     * 2). 例外的情況:若記錄的 ID 是由底層資料庫使用自增的方式生成的,
     *      則在呼叫 save() 方法時, 就會在commit前!!立即傳送 INSERT 語句.
     *      因為 save 方法後, 必須保證物件的 ID 是存在的!
     */
    @Test
    public void testSessionFlush2(){
        //在commit提交前傳送一條Insert語句,
        News news = new News("Java", "SUN", new Date());
        session.save(news);
    }
    @Test
    public void testSessionFlush(){
        News news = (News) session.get(News.class, 1); //這裡會執行一條select語句
        //這裡會先flush,然後會執行一條update語句(預設執行update語句),但沒有提交事務
        news.setAuthor("Oracle");

//      sessionPojo.flush();  //顯示使用flush

        //HQL查詢,會執行flush,然後傳送update語句 ,但到這裡還沒提交事務
        News news2 = (News) session.createCriteria(News.class).uniqueResult();
        System.out.println(news2);
    }

    flush:Session 按照快取中物件的屬性變化來同步更新資料庫
    預設情況下 Session 在以下時間點重新整理快取:
      - 顯式呼叫 Session 的 flush() 方法
      - 當應用程式呼叫 Transaction 的 commit()方法的時, 該方法先 flush ,然後在向資料庫提交事務
      - 當應用程式執行一些查詢(HQL, Criteria)操作時,如果快取中持久化物件的屬性已經發生了變化,會先 flush 快取,以保證查詢結果能夠反映持久化物件的最新狀態
    flush 快取的例外情況: 如果物件使用 native 生成器生成 OID, 那麼當呼叫 Session 的 save() 方法儲存物件時, 會立即執行向資料庫插入該實體的 insert 語句.
    commit() 和 flush() 方法的區別:flush 執行一系列 sql 語句,但不提交事務;commit 方法先呼叫flush() 方法,然後提交事務. 意味著提交事務意味著對資料庫操作永久儲存下來。



2-2.reflush
    /**
     * refresh(): 會強制傳送 SELECT 語句, 以使 Session 快取中物件的狀態和資料表中對應的記錄保持一致!
     */
    @Test
    public void testRefresh(){
        //這個物件資料庫存在
        News news = (News) session.get(News.class, 1);
        System.out.println(news);

        /*如果沒有refresh方法,當程式執行到這裡時,如果資料庫的內容被修改(如除錯時到這裡時強制修改資料庫的內容)
          輸出的內容與上面的一樣,沒有得到最新的資料!!*/

        //sessionPojo.flush();   //這裡不會發送select語句
        session.refresh(news);  //這裡會強制傳送select語句
        System.out.println(news);
    }
如果使用了refresh,得到的兩個結果還是一樣,說明是使用了mysql的預設隔離級別“可重複讀”,修改為“讀已提交”測試就行
2-3.clear
    /**
     * 操作 Session 快取有三個方法:flush()、refresh()、clear()
     */
    /**
     * clear(): 清理快取
     */
    @Test
    public void testClear(){
        News news1 = (News) session.get(News.class, 1);

        session.clear();

        News news2 = (News) session.get(News.class, 1);
        //會有兩條select語句,如果沒有clear方法,只會傳送一條語句
    }
2-4.資料庫的隔離級別

    對於同時執行的多個事務, 當這些事務訪問資料庫中相同的資料時, 如果沒有采取必要的隔離機制, 就會導致各種併發問題:
      1).髒讀: 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但還沒有被提交的欄位. 之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的.
      2).不可重複讀: 對於兩個事物 T1, T2, T1 讀取了一個欄位, 然後 T2 更新了該欄位. 之後, T1再次讀取同一個欄位, 值就不同了.
      3).幻讀: 對於兩個事物 T1, T2, T1 從一個表中讀取了一個欄位, 然後 T2 在該表中插入了一些新的行. 之後, 如果 T1 再次讀取同一個表, 就會多出幾行.
    資料庫事務的隔離性: 資料庫系統必須具有隔離併發執行各個事務的能力, 使它們不會相互影響, 避免各種併發問題.
    一個事務與其他事務隔離的程度稱為隔離級別. 資料庫規定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 資料一致性就越好, 但併發性越弱

    Oracle 支援的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE. Oracle 預設的事務隔離級別為: READ COMMITED
    Mysql 支援 4 中事務隔離級別. Mysql 預設的事務隔離級別為: REPEATABLE READ
設定隔離級別
1).在 MySql 中設定隔離級別

    每啟動一個 mysql 程式, 就會獲得一個單獨的資料庫連線. 每個資料庫連線都有一個全域性變數 @@tx_isolation, 表示當前的事務隔離級別. MySQL 預設的隔離級別為 Repeatable Read
  檢視當前的隔離級別: SELECT @@tx_isolation;
  設定當前 mySQL 連線的隔離級別:  
    set transaction isolation level read committed;
  設定資料庫系統的全域性的隔離級別:
     set global transaction isolation level read committed;
2).在 Hibernate 中設定隔離級別
JDBC 資料庫連線使用資料庫系統預設的隔離級別. 在 Hibernate 的配置檔案中可以顯式的設定隔離級別. 每一個隔離級別都對應一個整數:
  1. READ UNCOMMITED
  2. READ COMMITED
  4. REPEATABLE READ
  8. SERIALIZEABLE
Hibernate 通過為 Hibernate .cfg.xml對映檔案指定 hibernate.connection.isolation 屬性來設定事務的隔離級別
<property name="connection.isolation">2</property>