1. 程式人生 > >Hibernate Session快取

Hibernate Session快取

今天來講講 Hibernate 的靈魂所在——> Session 快取

session快取是由一系列的Java集合構成的。當一個物件被加入到Session快取中,這個物件的引用就加入到了java的集合中,以後即使應用程式中的引用變數不再引用該物件,只要Session快取不被清空,這個物件一直處於生命週期中。

Session快取的作用:

1)減少訪問資料庫的頻率。

2)保證快取中的物件與資料庫中的相關記錄保持同步。

Session清理快取的時機:

 1)當呼叫Transaction的commit()方法時,commit()方法先清理快取(前提是FlushMode.COMMIT/AUTO),然後再向資料庫提交事務。

 2)當應用程式呼叫Session的createQuery().list()或者createQuery().iterate()時,如果快取中的持久化物件的屬性發生了變化,就會先清理快取,以保證查詢結果能反映持久化物件的最新狀態。

    hibernate2中Session.find()對應於3中的session.createQuery().list()
    hibernate2中Session.iterate()對應於3中的session.createQuery().iterate()

 3)當應用程式顯示呼叫Session的flush()方法的時候。

Session的setFlushMode()方法用於設定清理快取的時間點。FlushMode類定義了三種不同的清理模式:FlushMode.AUTO、FlushMode.COMMIT和FlushMode.NEVER。 
FlushMode.AUTO是預設值:

session.setFlushMode(FlushMode.COMMIT);

Session清理模式執行清理快取操作的時間點:

這裡寫圖片描述

假設現在想把兩個客戶資訊插入到資料庫中:

        Session session = HibernateSessionFactory.getSession();
        Transaction transaction = session.beginTransaction
(); Customer customer1 = new Customer(null, "張三", "78784854", "南昌"); Customer customer2 = new Customer(null, "李四", "45646463", "北京"); session.save(customer1); session.save(customer2); transaction.commit();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

首先建立兩個客戶物件,這時會在棧區有兩個引用指向堆區的兩個客戶物件即

    Customer customer1 = new Customer(null, "張三", "78784854", "南昌");
    Customer customer2 = new Customer(null, "李四", "45646463", "北京");

然後執行save()方法,會在session快取即session物件中 建立兩個引用指向堆區的這兩個客戶物件。並且會生成兩條 insert (插入)語句保留在session中。

最後 等執行到 commit() 的時候就將事務提交。 這時因為 session 的清理快取的時機預設為 FlushMode.AUTO ,所以當 commit() 的時候會先呼叫 session.flush()—>是將剛剛保留的 insert 語句傳送到 資料庫中。等 commit() 完之後就將 insert 語句的資料永久儲存在資料庫中了。

這裡寫圖片描述

在剛才的基礎上,現在來看看這段程式碼:

Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.load(Customer.class, 1L);
        customer.setName("流川楓");
        transaction.commit();
  • 1
  • 2
  • 3
  • 4

這段程式碼是要將 id 為 1 的使用者的使用者名稱更新為“流川楓”。很奇怪這段程式碼並沒有呼叫 update()方法,那能更新到嗎?答案是肯定的。現在我們來看看記憶體圖:

這裡寫圖片描述

來分析下:

首先執行 load()方法,會將資料庫的對應資料載入到記憶體中即地址B01所對應的內容。這時session 和 棧區都指向 地址B01。

因為bo1是持久態物件,而持久態物件發生變化時,session會監聽到這種變化,並且產生出對應sql保證在事務提交之後讓記憶體中的物件和資料庫中的資料保持一致。執行到 coustomer.setName() 方法時,持久態物件發生變化,隨即 session 產生一條 update 語句。當 commit 之後就將update 語句傳送到資料庫並更新。

所以這個物件並不需要呼叫 update()方法去更新也會更新。

那麼我們就得來了解下什麼是持久態物件了。

在 Hibernate 中判斷物件的狀態,可以從以下兩個方面去判斷:

a.物件與 session 之間的關係

b.物件與資料庫資料之間的關係

Hibernate物件的狀態:

1)瞬時態Transient

由new操作符建立,且尚未與Hibernate Session關聯的物件。處於瞬時態的java物件成為臨時物件。

特點

    不處於Session的快取中,即不被任何一個Session例項關聯。

    在資料庫中沒有對應的記錄。

2)持久態Persistent

已經被持久化,加入到Session的快取中,處於持久化狀態的java物件被稱為持久化物件。

特點

位於一個Session例項的快取中。

持久化物件在資料庫中有相應的記錄

Session在清理快取時,會根據持久化物件的屬性變化來同步更新資料庫。

當一個持久化物件關聯一個臨時物件,在允許級聯儲存的情況下,Session在清理快取的時候會把這個臨時物件也轉變為持久化物件。

3)脫管態Detached

已經被持久化,但不再處於Session的快取中,處於脫管狀態的java物件稱為遊離物件。

比如在剛才的基礎上,新增如下程式碼:

Customer customer = new Customer(1L, "", "", "");
  • 1

因為 id 為 1 的使用者存在,但又和 session 沒有關聯,所以是遊離物件。

特點

不再位於Session的快取中,即不被Session關聯。

遊離物件是由持久化物件轉變過來的,因此在資料庫存在與之對應的記錄(前提是沒有其他程式刪除了這條記錄)

臨時物件VS遊離物件

相同:都不被Session關聯,Hibernate不會保證它們的屬性變化與資料庫保持同步。

不同:前者在資料庫中沒有與之對應的記錄。後者由持久化物件轉變而來,因此資料庫中可能還存在與之對應的記錄。

Hibernate 物件狀態轉換圖

這裡寫圖片描述

脫管狀態 
session中不維護脫管物件 
資料庫中有脫管物件對應的資料

save(obj)   
  obj 瞬態
    將瞬態轉化為持久態物件
  obj 脫管
    重新儲存一個新的物件
  obj 持久態
    沒意義

update(obj)
  obj 脫管
    將脫管態轉換為持久態

saveOrUpdate(obj)
  如果obj是瞬態物件,儲存
  如果obj是脫管物件,更新

delete(obj)
  將持久態刪除

Session API :

Session介面是Hibernate嚮應用程式提供的操縱資料庫的最主要的介面,它提供了基本的儲存,更新,刪除和查詢的方法。

save(): 把一個臨時物件加入到快取中,使它變成持久化物件

-->選用對映檔案指定的主鍵生成器為持久化物件分配唯一的OID

-->計劃一條insert語句,把引數物件當前的屬性值組裝到insert語句中,但是save()方法並不立即執行SQL insert語句,只有當Session清理快取時候才會執行。

-->如果在save()方法之後,又修改了持久化物件的屬性,會使得Session在清理快取的時候額外執行SQL update語句。

注意:save()方法是用來持久化一個臨時物件的!

如果將一個持久化物件傳給save()方法將不會執行任何操作,多餘的步驟

如果將一個遊離態物件傳給save()方法,session會將它當作臨時物件來處理,再次向資料庫中插入一條記錄,不符合業務需求!

update():把Customer物件重新加入到Session快取中,使之變為持久化物件。

--->計劃一條update語句,只有在清理快取的時候才會執行,並且在執行的時候才會把引數物件中的屬性值組裝到update語句中。

注意:update()是將一個遊離物件轉變為持久化物件的。

  只要通過update()方法使遊離物件被一個session關聯,即使沒有修改引數物件的任何屬性,Session在清理快取的時候也會執行由update方法計劃的Update語句。

saveOrUpdate():同時包含了save()與update()方法的功能,如果傳入的引數是臨時物件,呼叫save方法,如果參入引數是遊離物件,呼叫update()方法,如果傳入的是持久化物件,直接返回。

load()/get(): 都會根據給定的OID從資料庫中載入一個持久化物件,區別在於,當資料庫中不存在與OID對應的記錄時,load()方法會丟擲ObjectNotFoundException異常,而get()方法返回null.

delete():用於從資料庫中刪除與引數物件對應的記錄,如果傳入的引數是持久化物件,Session就計劃執行一個delete語句,如果傳入的引數是遊離物件,先使遊離物件被Session關聯,使它變為持久化物件,然後計劃一個delete語句,在清理快取的時候執行。

evict():從快取中清除引數指定的持久化物件。

  適用場合:不希望Session繼續按照該物件的狀態改變來同步更新資料庫。
  在批量更新或批量刪除的場合,當更新或者刪除一個物件後,及時釋放該物件佔用的記憶體。當然批量操作優先考慮JDBC.

clear():清空快取中所有持久化物件。

get VS load

1.get方法,hibernate會確認一下該id對應的資料是否存在,首先在session快取中查詢,然後在二級快取中查詢,還沒有就查資料庫。預設延遲載入,查詢當前物件的時候暫時先不查詢與之關聯的物件。

load方法,為延遲載入即先將查詢語句放入快取中,待到用時再將快取中的查詢語句傳送到資料庫進行查詢。(lazy屬性對它不影響)

2.get方法檢索不到的話會返回null

load方式檢索不到的話會丟擲org.hibernate.ObjectNotFoundException異常

注意:

Java程式碼

Users user = (Users)session.load(Users.class, userId);  
System.out.println(user.getId());  
  • 1
  • 2

上面這2句程式碼,不會去執行資料庫操作。因為load後會在hibernate的一級快取裡存放一個map物件,該map的key就是userId的值,但是當你getId()時,它會去一級快取裡拿map的key值,而不去執行資料庫查詢。所以不會報任何錯。不會執行任何資料庫操作。

補充點:

cascade

    級聯操作,一般誰來維護關係,就誰來級聯。如果主表來級聯,而從表來維護關係,這時儲存的時候就會報錯。

    儲存或更新(save-update)
            customer
            <set name="orders" cascade="save-update"/>
        這表示customer級聯order
            維護關係
                customer 維護 order
                customer.getOrders().add(o1);
                customer.getOrders().add(o2);
                customer.getOrders().add(o3);

            session.save(customer);
                insert into tbl_customer values();
                if(級聯){
                    os = customer.getOrders();
                    for(Order o : os){
                        save(o);
                    }
                }
    刪除(delete)
    刪除儲存更新(all)
    解除關係即刪除(delete-orphan):必須雙方接觸關係,刪除的是從表的資料
        Customer c = (Customer) session.get(Customer.class, 4L);
        Order order = (Order) session.get(Order.class, 8L);
        //接觸關係
        c.getOrders().remove(order);
        order.setCustomer(null);
  • 1
  • 2
  • 3
  • 4
  • 5
    刪除儲存更新,解除關係即刪除(all-delete-orphan)

lazy

預設true  延遲載入,查詢當前物件的時候暫時先不查詢與之關聯的物件

false   立即載入,查詢當前物件的時候查詢所有與之關聯的物件。

inverse(維護關係的權利的反轉)

一般誰來維護關係誰就來產生外來鍵,不能權利顛倒。如果主表維護從表,而主表的維護關係的權利反轉了(即在主表中 inverse="true"),則插入資料的時候不會產生外來鍵即從表中的外來鍵為null。一般1:n關係由多的一方來維護即可。

一般寫在set中

預設false 當前方可以維護關係(產生外來鍵)

true        當前方不維護關係(不產生外來鍵)