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 當前方不維護關係(不產生外來鍵)