1.為什么要使用Hibernate開發(fā)你的項目呢?Hibernate的開發(fā)流程是怎么樣的?
①.對JDBC訪問數(shù)據(jù)庫的代碼做了封裝,大大簡化了數(shù)據(jù)訪問層繁瑣的重復(fù)性代碼。
②.Hibernate 是一個基于JDBC的主流持久化框架,是一個優(yōu)秀的ORM 實現(xiàn)。他很大程度的簡化DAO層的編碼工作
③.hibernate 的性能非常好,因為它是個輕量級框架。映射的靈活性很出色。它支持各種關(guān)系數(shù)據(jù)庫,從一對一到多對多的各種復(fù)雜關(guān)系。
開發(fā)流程
2.什么是延遲加載?
延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要數(shù)據(jù)的時候,才真正執(zhí)行數(shù)據(jù)加載操作。在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。
3.說一下hibernate的緩存機制
(1)hibernate支持兩個級別的緩存,默認只支持一級緩存;
(2)每個Session內(nèi)部自帶一個一級緩存;
(3)某個Session被關(guān)閉時,其對應(yīng)的一級緩存自動清除;
(1) 二級緩存獨立于session,默認不開啟;
4.Hibernate的查詢方式有哪些?
5.如何優(yōu)化Hibernate?
1.使用雙向一對多關(guān)聯(lián),不使用單向一對多
2.靈活使用單向一對多關(guān)聯(lián)
3.不用一對一,用多對一取代
4.配置對象緩存,不使用集合緩存
5.一對多集合使用Bag,多對多集合使用Set
6. 繼承類使用顯式多態(tài)
7. 表字段要少,表關(guān)聯(lián)不要怕多,有二級緩存撐腰
6.Hibernate中GET和LOAD的區(qū)別?
請注意如果沒有匹配的數(shù)據(jù)庫記錄,load()方法可能拋出無法恢復(fù)的異常(unrecoverable exception)。 如果類的映射使用了代理(proxy),load()方法會返回一個未初始化的代理,直到你調(diào)用該代理的某方法時才會去訪問數(shù)據(jù)庫。若你希望在某對象中創(chuàng)建一個指向另一個對象的關(guān)聯(lián),又不想在從數(shù)據(jù)庫中裝載該對象時同時裝載相關(guān)聯(lián)的那個對象,那么這種操作方式就用得上的了。 如果為相應(yīng)類映射關(guān)系設(shè)置了batch-size, 那么使用這種操作方式允許多個對象被一批裝載(因為返回的是代理,無需從數(shù)據(jù)庫中抓取所有對象的數(shù)據(jù))。 如果你不確定是否有匹配的行存在,應(yīng)該使用 get()方法,它會立刻訪問數(shù)據(jù)庫,如果沒有對應(yīng)的行,會返回null。
session.get 方法, 查詢立即執(zhí)行 , 返回Customer類對象
session.load 方法,默認采用延遲加載數(shù)據(jù)方式,不會立即查詢,返回 Customer類子類對象 (動態(tài)生成代理對象)
* 如果 PO類使用final修飾,load無法創(chuàng)建代理對象,返回目標對象本身 (load效果和 get效果 相同 )
7.說說在 hibernate中使用Integer做映射和使用int做映射之間有什么差別?
Integer code和int code的區(qū)別:
Integer是對象. code=null; 對象可以為空.
根據(jù)你的數(shù)據(jù)庫code是可以空的,故應(yīng)該映射成Integer. 你沒理由hbm.xml里寫 Integer,類里卻寫int
8.SQL和HQL有什么區(qū)別?
sql 面向數(shù)據(jù)庫表查詢 hql 面向?qū)ο蟛樵? hql:from 后面跟的 類名+類對象 where 后 用 對象的屬性做條件 sql:from 后面跟的是表名 where 后 用表中字段做條件 查詢 在Hibernate中使用查詢時,一般使用Hql查詢語句。 HQL(Hibernate Query Language),即Hibernate的查詢語言跟SQL非常相像。不過HQL與SQL的最根本的區(qū)別,就是它是面向?qū)ο蟮摹? 使用HQL時需要注意以下幾點: 1.大小寫敏感
因為HQL是面向?qū)ο蟮?,而對象類的名稱和屬性都是大小寫敏感的,所以HQL是大小寫敏感的。
HQL語句:from Cat as cat where cat.id > 1;與from Cat as cat where cat.ID > 1;是不一樣的,這點與SQL不同。
from Cat,該句返回Cat對象實例,開發(fā)人員也可以給其加上別名,eg. from Cat as cat,對于多表查詢的情況,可參考如下:
from Cat as cat, Dog as dog
9.Hibernate的分頁查詢
例如:從數(shù)據(jù)庫中的第20000條數(shù)據(jù)開始查后面100條記錄
Query q = session.createQuery("from Cat as c");; q.setMaxResults(100);; List l = q.list();; q.setFirstResult(20000);;
10.Hibernate中Java對象的狀態(tài)以及對應(yīng)的特征有哪些?
持久化對象的三種狀態(tài) 持久化對象PO和OID PO=POJO + hbm映射配置 編寫規(guī)則
①必須提供無參數(shù)public構(gòu)造器
②所有屬性private,提供public的getter和setter方法
③必須提供標識屬性,與數(shù)據(jù)表中主鍵對應(yīng),例如Customer類 id屬性
④PO類屬性應(yīng)盡量使用基本數(shù)據(jù)類型的包裝類型(區(qū)分空值) 例如int---Integer long---Long
⑤不要用final修飾(將無法生成代理對象進行優(yōu)化)
OID 指與數(shù)據(jù)表中主鍵對應(yīng) PO類中屬性,例如 Customer類 id屬性
Hibernate框架使用OID來區(qū)分不同PO對象
* 例如內(nèi)存中有兩個PO對象,只要具有相同 OID, Hibernate認為同一個對象
* Hibernate 不允許緩存同樣OID的兩個不同對象
①瞬時態(tài)(臨時態(tài)、自由態(tài)):不存在持久化標識OID,尚未與Hibernate Session關(guān)聯(lián)對象,被認為處于瞬時態(tài),失去引用將被JVM回收
②持久態(tài):存在持久化標識OID,與當前session有關(guān)聯(lián),并且相關(guān)聯(lián)的session沒有關(guān)閉 ,并且事務(wù)未提交
③脫管態(tài)(離線態(tài)、游離態(tài)):存在持久化標識OID,但沒有與當前session關(guān)聯(lián),脫管狀態(tài)改變hibernate不能檢測到
區(qū)分三種狀態(tài):判斷對象是否有OID,判斷對象是否與session關(guān)聯(lián)(被一級緩存引用)
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
Book book =newBook();// 瞬時態(tài)(沒有OID,未與Session關(guān)聯(lián))
book.setName("hibernate精通");
book.setPrice(56d);
session.save(book);// 持久態(tài)(具有OID,與Session關(guān)聯(lián))
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
System.out.println(book.getId());// 脫管態(tài)(具有 OID,與Session斷開關(guān)聯(lián))
持久化對象狀態(tài)轉(zhuǎn)換
①瞬時態(tài)對象:通過new獲得
瞬時----->持久 save、saveOrUpdate(都是session)
瞬時----->脫管 book.setId(1) 為瞬時對象設(shè)置OID
②持久態(tài)對象:通過get/load 、Query查詢獲得
持久----->瞬時 delete (被刪除持久化對象 不建議再次使用 )
持久----->脫管 evict(清除一級緩存中某一個對象)、close(關(guān)閉Session,清除一級緩存)、clear(清除一級緩存所有對象 )
③脫管態(tài)對象 無法直接獲得
脫管----->瞬時 book.setId(null); 刪除對象OID
脫管----->持久 update、saveOrUpdate、 lock(過時)
11.Hibernate中怎樣處理事務(wù)?
Hibernate是對JDBC的輕量級對象封裝,Hibernate本身是不具備Transaction 處理功能的,Hibernate的Transaction實際上是底層的JDBC Transaction的封裝,或者是JTA Transaction的封裝,下面我們詳細的分析:
Hibernate可以配置為JDBCTransaction或者是JTATransaction,這取決于你在hibernate.properties中的配置:
#hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class net.sf.hibernate.transaction.JDBCTransactionFactory
如果你什么都不配置,默認情況下使用JDBCTransaction,如果你配置為:
hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory
將使用JTATransaction,不管你準備讓Hibernate使用JDBCTransaction,還是JTATransaction,我的忠告就是什么都不配,將讓它保持默認狀態(tài),如下:
#hibernate.transaction.factory_class net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class net.sf.hibernate.transaction.JDBCTransactionFactory
在下面的分析中我會給出原因。
一、JDBC Transaction
看看使用JDBC Transaction的時候我們的代碼例子:
Session session = sf.openSession();
Transaction tx = session.beginTransactioin();
...
session.flush();
tx.commit();
session.close();
這是默認的情況,當你在代碼中使用Hibernate的Transaction的時候?qū)嶋H上就是JDBCTransaction。那么JDBCTransaction究竟是什么東西呢?來看看源代碼就清楚了:
Hibernate2.0.3源代碼中的類
net.sf.hibernate.transaction.JDBCTransaction:
public void begin() throws HibernateException {
...
if (toggleAutoCommit) session.connection().setAutoCommit(false);
...
}
這是啟動Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是不是很熟悉?
再來看
public void commit() throws HibernateException {
...
try {
if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush();
try {
session.connection().commit();
committed = true;
}
...
toggleAutoCommit();
}
這是提交方法,看到connection().commit() 了嗎?下面就不用我多說了,這個類代碼非常簡單易懂,通過閱讀使我們明白Hibernate的Transaction都在干了些什么?我現(xiàn)在把用 Hibernate寫的例子翻譯成JDBC,大家就一目了然了:
Connection conn = ...; <--- session = sf.openSession();
conn.setAutoCommit(false); <--- tx = session.beginTransactioin();
... <--- ...
conn.commit(); <--- tx.commit(); (對應(yīng)左邊的兩句)
conn.setAutoCommit(true);
conn.close(); <--- session.close();
看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神秘可言,只不過在Hibernate中, Session打開的時候,就會自動conn.setAutoCommit(false),不像一般的JDBC,默認都是true,所以你最后不寫 commit也沒有關(guān)系,由于Hibernate已經(jīng)把AutoCommit給關(guān)掉了,所以用Hibernate的時候,你在程序中不寫 Transaction的話,數(shù)據(jù)庫根本就沒有反應(yīng)。
12.簡單的介紹一下Hibernate的核心API?
1.Configuration
用于加載hibernate配置
①加載核心屬性配置hibernate.properties和hibernate.cfg.xml
//方式一: 去src 讀取 hibernate.properties 屬性配置文件
Configuration cfg =newConfiguration();
//方式二:去src讀取 hibernate.cfg.xml
Configuration cfg =newConfiguration().configure();
Configuration cfg =newConfiguration().configure("自定義xml文件");去src 加載指定文件
②手動加載hbm映射配置,持久化類與數(shù)據(jù)表的映射關(guān)系(*.hbm.xml 文件)
如果沒有對PO類進行hbm映射,會報錯 :
org.hibernate.MappingException:Unknown entity: cn.itcast.domain.Customer
那么我們可以手動加載其映射文件:
//方式一:
configuration.addResource("cn/itcast/domain/Customer.hbm.xml");加載hbm文件
//方式二:
configuration.addClass(Customer.class);加載Class,自動搜索hbm映射文件
* 如果使用 hibernate.cfg.xml配置,將映射配置xml中 <mapping resource="cn/itcast/domain/Customer.hbm.xml"/>
2.SessionFactory
①保存了當前的數(shù)據(jù)庫配置信息和所有映射關(guān)系以及預(yù)定義的SQL語句這個對象是線程安全的
//預(yù)定義SQL語句
<sql-queryname="login">
<![CDATA[select * from user where username= ? and password =?]]>
</sql-query>
3.Session
代表hibernate操作會話對象,相當于Connection
session是一個單線程對象,線程不安全(在方法內(nèi)部定義和使用Session,不會出現(xiàn)線程問題)
* 每個線程方法調(diào)用棧,是線程私有的
session 進行PO(Persistent Object)對象常見持久化操作, 存在一級緩存
常用API
save 完成插入
update 完成修改
delete完成刪除
get/load 根據(jù)主鍵字段查詢
createQuery、 createSQLQuery 創(chuàng)建查詢對象Query接收HQL,SQLQuery接收SQL
createCriteria() 面向?qū)ο髼l件查詢
4.Transaction 事務(wù)操作
commit 提交
rollback 回滾
如果沒有開啟事務(wù),那么每個Session的操作,都相當于一個獨立的事務(wù)
* 事務(wù)是否提交
//默認false
<property name="hibernate.connection.autocommit">false</property> 事務(wù)不提交
<propertyname="hibernate.connection.autocommit">true</property> 事務(wù)提交
5.Query
session.createQuery()獲得
面向?qū)ο蟛樵?,操作類,操作屬?/span>
接收參數(shù) HQL語句
開發(fā)代碼步驟
獲得HibernateSession對象
編寫HQL語句
調(diào)用session.createQuery 創(chuàng)建查詢對象
如果HQL語句包含參數(shù),則調(diào)用Query的setXXX設(shè)置參數(shù)
調(diào)用Query對象的list()或uniqueResult()方法執(zhí)行查詢
6.Criteria 接口(QBC查詢 Query By Criteria )
主要為了解決多條件查詢問題,以面向?qū)ο蟮姆绞教砑訔l件,無需拼接HQL語句
13.update與saveOrUpdate有什么區(qū)別?
save() 方法很顯然是執(zhí)行保存操作的,如果是對一個新的剛new出來的對象進行保存,自然要使用這個方法了,數(shù)據(jù)庫中沒有這個對象。 update() 如果是對一個已經(jīng)存在的托管對象進行更新那么肯定是要使用update()方法了,數(shù)據(jù)中有這個對象。
saveOrUpdate() 這個方法是更新或者插入,有主鍵就執(zhí)行更新,如果沒有主鍵就執(zhí)行插入?!?span style="background-color: #ffad5b;">此方法慎用】
在Hibernate中saveOrUpdate()方法在執(zhí)行的時候,先會去session中去找存不存在指定的字段,如果存在直接update,否則save,這個時候問題就發(fā)生了。
有兩張表,表A和表B,這兩張表的主鍵都是一樣的,例如都是MASTER_ID,同時對應(yīng)的BO里面屬性都是masterID,現(xiàn)在要執(zhí)行的操作是,以 MASTER_ID為條件將表A中的數(shù)據(jù)查詢出來,然后將部分值插入到表B中,然后再更新表B,在查詢表A后,session中已經(jīng)存在masterID 了,這個時候再去對表B進行savaOrUpdate的時候,Hibernate會發(fā)現(xiàn)session中已經(jīng)存在masterID了,所以執(zhí)行的就是 update,但是實際上表B中根本不存在masterID這個值,當你執(zhí)行完查詢數(shù)據(jù)庫的時候會發(fā)現(xiàn)沒有插入數(shù)據(jù),像這種情況,就得先用 masterID對表B進行查詢,當返回的BO為NULL時,new一個新BO然后再進行插入,這個時候用到的就是createbo了。
14.Hibernate的inverse屬性的作用?
1.明確inverse和cascade的作用
inverse 決定是否把對對象中集合的改動反映到數(shù)據(jù)庫中,所以inverse只對集合起作用,也就是只對one-to-many或many-to-many有效(因為只有這兩種關(guān)聯(lián)關(guān)系包含集合,而one-to-one和many-to-one只含有關(guān)系對方的一個引用)。
cascade決定是否把對對象的改動反映到數(shù)據(jù)庫中,所以cascade對所有的關(guān)聯(lián)關(guān)系都起作用(因為關(guān)聯(lián)關(guān)系就是指對象之間的關(guān)聯(lián)關(guān)系)。
2.inverse屬性 :inverse所描述的是對象之間關(guān)聯(lián)關(guān)系的維護方式。
inverse只存在于集合標記的元素中 。Hibernate提供的集合元素包括<set/> <map/> <list/> <array /> <bag />
Inverse屬性的作用是:是否將對集合對象的修改反映到數(shù)據(jù)庫中。 inverse屬性的默認值為false,表示對集合對象的修改會被反映到數(shù)據(jù)庫中;inverse=false 的為主動方,由主動方負責維護關(guān)聯(lián)關(guān)系。 inverse=”true” 表示對集合對象的修改不會被反映到數(shù)據(jù)庫中。為了維持兩個實體類(表)的關(guān)系,而添加的一些屬性,該屬性可能在兩個實體類(表)或者在一個獨立的表里面,這個要看這雙方直接的對應(yīng)關(guān)系了: 這里的維護指的是當主控放進行增刪改查操作時,會同時對關(guān)聯(lián)關(guān)系進行對應(yīng)的更新。
一對多: 該屬性在多的一方。應(yīng)該在一方的設(shè)置 inverse=true ,多的一方設(shè)置 inverse=false(多的一方也可以不設(shè)置inverse屬性,因為默認值是false),這說明關(guān)聯(lián)關(guān)系由多的一方來維護。如果要一方維護關(guān) 系,就會使在插入或是刪除"一"方時去update"多"方的每一個與這個"一"的對象有關(guān)系的對象。而如果讓"多"方面維護關(guān)系時就不會有update 操作,因為關(guān)系就是在多方的對象中的,直指插入或是刪除多方對象就行了。顯然這樣做的話,會減少很多操作,提高了效率。
注:單向one-to-many關(guān)聯(lián)關(guān)系中,不可以設(shè)置inverse="true",因為被控方的映射文件中沒有主控方的信息。
多對多: 屬性在獨立表中。inverse屬性的默認值為false。在多對多關(guān)聯(lián)關(guān)系中,關(guān)系的兩端 inverse不能都設(shè)為false,即默認的情況是不對的,如果都設(shè)為false,在做插入操作時會導(dǎo)致在關(guān)系表中插入兩次關(guān)系。也不能都設(shè)為 true,如果都設(shè)為true,任何操作都不會觸發(fā)對關(guān)系表的操作。因此在任意一方設(shè)置inverse=true,另一方inverse=false。
一對一: 其實是一對多的一個特例,inverse 的設(shè)置也是一樣的,主要還是看關(guān)聯(lián)關(guān)系的屬性在哪一方,這一方的inverse=false。
多對一: 也就是一對多的反過來,沒什么區(qū)別。
3.cascade屬性
級聯(lián)操作:指當主控方執(zhí)行某項操作時,是否要對被關(guān)聯(lián)方也執(zhí)行相同的操作。
cascade屬性的作用是描述關(guān)聯(lián)對象進行操作時的級聯(lián)特性。因此,只有涉及到關(guān)系的元素才有cascade屬性。具有cascade屬性的標記包括<many-to-one /> <one-to-one /> <any /> <set /><bag /> <idbag /> <list /> <array />
注意:<one-to-many />和 <many-to-many />是用在集合標記內(nèi)部的,所以是不需要cascade屬性的。
4.inverse和cascade的區(qū)別
作用的范圍不同:
Inverse是設(shè)置在集合元素中的。
Cascade對于所有涉及到關(guān)聯(lián)的元素都有效。
<many-to-one/><ont-to-many/>沒有inverse屬性,但有cascade屬性
執(zhí)行的策略不同
Inverse 會首先判斷集合的變化情況,然后針對變化執(zhí)行相應(yīng)的處理。
Cascade 是直接對集合中每個元素執(zhí)行相應(yīng)的處理
執(zhí)行的時機不同
Inverse是在執(zhí)行SQL語句之前判斷是否要執(zhí)行該SQL語句
Cascade則在主控方發(fā)生操作時用來判斷是否要進行級聯(lián)操作
執(zhí)行的目標不同
Inverse對于<ont-to-many>和<many-to-many>處理方式不相同。
對于<ont-to-many>,inverse所處理的是對被關(guān)聯(lián)表進行修改操作。
對于<many-to-many>,inverse所處理的則是中間關(guān)聯(lián)表
Cascade不會區(qū)分這兩種關(guān)系的差別,所做的操作都是針對被關(guān)聯(lián)的對象。
總結(jié):
<one-to-many>
<one-to-many>中,建議inverse=”true”,由“many”方來進行關(guān)聯(lián)關(guān)系的維護
<many-to-many>中,只設(shè)置其中一方inverse=”false”,或雙方都不設(shè)置
Cascade,通常情況下都不會使用。特別是刪除,一定要慎重。
操作建議:
一般對many-to-one和many-to-many不設(shè)置級聯(lián),這要看業(yè)務(wù)邏輯的需要;對one-to-one和one-to-many設(shè)置級聯(lián)。
many-to-many關(guān)聯(lián)關(guān)系中,一端設(shè)置inverse=”false”,另一端設(shè)置為inverse=”true”。在one-to-many關(guān)聯(lián)關(guān)系中,設(shè)置inverse=”true”,由多端來維護關(guān)系表
Hibernate一級緩存相關(guān)問題
1.Session中的一級緩存
Hibernate框架共有兩級緩存, 一級緩存(Session級別緩存)、二級緩存(SessionFactory級別緩存)
在Session接口的實現(xiàn)中包含一系列的 Java 集合, 這些 Java 集合構(gòu)成了 Session 緩存. 持久化對象保存Session一級緩存中(一級緩存引用持久化對象地址),只要 Session 實例沒有結(jié)束生命周期, 存放在它緩存中的對象也不會結(jié)束生命周期
Hibernate Session接口的實現(xiàn)類SessionImpl類(查看源碼,右擊session,選擇Open Type Hierarchy) ,里面有2個重要的字段:
*private transient ActionQueue actionQueue; ----行動隊列(標記數(shù)據(jù)活動)
*private transient StatefulPersistenceContext persistenceContext;----持久化上下文
當session的save()方法持久化一個對象時,該對象被載入緩存,以后即使程序中不再引用該對象,只要緩存不清空,該對象仍然處于生命周期中。當試圖get()、 load()對象時,會判斷緩存中是否存在該對象,有則返回,此時不查詢數(shù)據(jù)庫。沒有再查詢數(shù)據(jù)庫
Session 能夠在某些時間點, 按照緩存中對象的變化來執(zhí)行相關(guān)的 SQL 語句, 來同步更新數(shù)據(jù)庫, 這一過程被稱為刷出緩存(flush)
* Transaction的commit()
* 應(yīng)用程序執(zhí)行一些查詢操作時
* 調(diào)用Session的flush()方法
①驗證一級緩存的存在
Book book =(Book) session.get(Book.class,1);// 第一次查詢,緩存中沒有
System.out.println(book);
Book book2 =(Book) session.get(Book.class,1);// 因為第一次查詢,對象已經(jīng)被放入1級緩存,不會查詢數(shù)據(jù)
System.out.println(book2);
*生成一條SQL語句,返回同一個對象,第一次查詢生成SQL,查詢對象,將對象放入一級緩存,第二次查詢,直接從一級緩存獲得
②測試Hibernate快照 (深入理解一級緩存內(nèi)存結(jié)構(gòu)原理)
hibernate 向一級緩存放入數(shù)據(jù)時,同時保存快照數(shù)據(jù)(數(shù)據(jù)庫備份),當修改一級緩存數(shù)據(jù),在flush操作時,對比緩存和快照,如果不一致,自動更新(將緩存的內(nèi)容同步到數(shù)據(jù)庫,更新快照)
* 快照區(qū)使用,在Session 保存一份與數(shù)據(jù)庫相同的數(shù)據(jù),在session的flush時, 通過快照區(qū)比較得知一級緩存數(shù)據(jù)是否改變,如果改變執(zhí)行對應(yīng)操作(update)
/**
* 測試快照區(qū)的使用
*/
@Test
publicvoid demo3(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
// 查詢id 為1 的圖書對象
Book book =(Book) session.get(Book.class,1);// 第一次查詢,將對象加入一級緩存
System.out.println(book);
book.setName("深入淺出Hibernate技術(shù)");// 修改書名(一級緩存被修改,自動update)
// 沒有手動執(zhí)行update,因為快照區(qū)原因,自動更新
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
}
使用Debug模式進行截圖說明:
我們重點關(guān)注session中的2個屬性actionQueue和persistenceContext
大白話解析:
**當執(zhí)行g(shù)et后,緩存里面有數(shù)據(jù)了,活動隊列沒有發(fā)生變化,說明沒有需要提交到數(shù)據(jù)的內(nèi)容,而PersistenceContext里面有內(nèi)容了。
我們說,持久化上下文里面存放的是一個Map,它的鍵為一級緩存對象,值為快照(它是一級緩存對象的一個副本)。
**當執(zhí)行setName后,一級緩存里面的數(shù)據(jù)發(fā)生了改變,在緩存flush時,會對比緩存和快照,如果不一致,那么會將緩存中的內(nèi)容同步到數(shù)據(jù)庫,并更新快照!
* Hibernate中 持久態(tài) 對象具有自動更新數(shù)據(jù)庫能力 (持久態(tài)對象 才保存在 Session中,才有快照 )
2.一級緩存常見操作
所有操作需要使用斷點調(diào)試才能看得比較清楚!
1)flush : 修改一級緩存數(shù)據(jù)針對內(nèi)存操作,需要在session執(zhí)行flush操作時,將緩存變化同步到數(shù)據(jù)庫
* 只有在緩存數(shù)據(jù)與快照區(qū)不同時,生成update語句
2)clear : 清除所有對象 一級緩存
3)evict : 清除一級緩存指定對象
4)refresh :重新查詢數(shù)據(jù)庫,更新快照和一級緩存
@Test
// Session 對于 一級緩存操作
publicvoid demo4(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
// 查詢id 為1 的圖書對象
Book book =(Book) session.get(Book.class,1);// 第一次查詢,將對象加入一級緩存
System.out.println(book);
// book.setPrice(80d); // 修改一級緩存數(shù)據(jù)
// 將緩存內(nèi)容同步到數(shù)據(jù)庫
// session.flush();
// 清除一級緩存所有數(shù)據(jù)
// session.clear();
// 清除一級緩存中 指定對象
// session.evict(book);
book.setPrice(30d);// 一級緩存改變
session.refresh(book);// 用數(shù)據(jù)庫內(nèi)容 覆蓋快照區(qū)和一級緩存
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
}
3.一級緩存刷出時間點設(shè)置 (FlushMode)
ALWAYS :在每次查詢時,session都會flush (不要求 )
AUTO : 在有些查詢時,session會flush (默認) ---------- 查詢、commit 、session.flush
COMMIT : 在事務(wù)提交時,session會flush ------- commit 、session.flush
MANUAL :只有手動調(diào)用 session.flush 才會刷出 ---- session.flush
刷出條件(時間點嚴格程度 )
MANUAL > COMMIT> AUTO> ALWAYS
@Test
// 理解 FlushMode作用
publicvoid demo5(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 設(shè)置 flushMode
session.setFlushMode(FlushMode.MANUAL);
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
// 查詢id 為1 的圖書對象
Book book =(Book) session.get(Book.class,1);// 第一次查詢,將對象加入一級緩存
System.out.println(book);
book.setPrice(1000d);// 修改價格
session.createQuery("from Book").list();// 查詢所有圖書 (AUTO 級別 flush)
// 提交事務(wù),關(guān)閉Session
transaction.commit();// (COMMIT 級別 flush)
// session.flush(); // MANUAL 級別 flush
session.close();
}
4.session持久化對象操作方法
1) save 將數(shù)據(jù)保存到數(shù)據(jù)庫 , 將瞬時對象轉(zhuǎn)換持久對象
持久化對象,不允許隨便修改 OID
2) update 更新數(shù)據(jù) ,主要用于脫管對象的更新(持久對象,可以根據(jù)快照自動更新 ), 將脫管對象轉(zhuǎn)換持久對象
@Test
// 脫管對象更新
publicvoid demo6(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
Book book =newBook();// 瞬時
book.setId(1);// 脫管
book.setName("java入門");
book.setPrice(40d);
session.update(book);// 持久
session.flush();
// book.setPrice(50d);
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
}
問題一: 調(diào)用update,默認直接生成update語句,如果數(shù)據(jù)沒有改變,不希望生成update
在hbm文件 <class>元素 添加 select-before-update="true"
<classname="cn.itcast.domain.firstcache.Book"table="book"catalog="hibernate3day2"select-before-update="true">
問題二: 當update,脫管對象變?yōu)槌志脤ο螅?一級緩存不允許出現(xiàn)相同OID 兩個持久對象
@Test
// 一級緩存 存在兩個相同OID 持久態(tài)對象 報錯
publicvoid demo7(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
// 查詢
// Book b = (Book) session.get(Book.class, 1); // 持久
Book book =newBook();// 瞬時
book.setId(1);// 脫管
book.setName("java入門");
book.setPrice(50d);
session.update(book);// 持久
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
}
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [cn.itcast.domain.firstcache.Book#1]
問題三: 脫管對象 OID 在數(shù)據(jù)表中不存在,update時,發(fā)生異常
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [cn.itcast.domain.firstcache.Book#20]
3) saveOrUpdate , 如果參數(shù)是一個瞬時對象執(zhí)行save, 如果參數(shù)是一個脫管對象執(zhí)行update, 如果參數(shù)是持久對象直接返回
判斷對象是瞬時對象 : OID為null , 在hbm文件中為 <id>元素指定 unsaved-value屬性,如果PO對象OID為 unsaved-value 也是瞬時對象
<id name="id" unsaved-value="-1"> 如果對象 OID為-1 也是瞬時對象,此時執(zhí)行的是save操作
@Test
// PO對象,OID為 hbm文件 配置 unsaved-value 也是瞬時對象, saveOrUpdate 執(zhí)行 save操作
publicvoid demo8(){
// 獲得Session
Session session =HibernateUtils.openSession();
// 開啟事務(wù)
Transaction transaction = session.beginTransaction();
Book book =newBook();// 瞬時
book.setId(-1);// 存在OID , -1是unsaved-value 也是瞬時
book.setName("xxx");
book.setPrice(100d);
session.saveOrUpdate(book);
// 提交事務(wù),關(guān)閉Session
transaction.commit();
session.close();
}
4) get/load
如果查詢OID不存在, get方法返回 null , load 方法返回代理對象 (代理對象初始化時拋出 ObjectNotFoundException )
5) delete 方法既可以刪除一個脫管對象, 也可以刪除一個持久化對象
**如果刪除脫管,先將脫管對象 與 Session 關(guān)聯(lián),然后再刪除
**執(zhí)行delete,先刪除一級緩存數(shù)據(jù),在session.flush 操作時,刪除數(shù)據(jù)表中數(shù)據(jù)
Hibernate二級緩存相關(guān)問題
1.二級緩存的相關(guān)介紹
緩存好處: 將數(shù)據(jù)庫或者硬盤數(shù)據(jù),保存在內(nèi)存中,減少數(shù)據(jù)庫查詢次數(shù),減少硬盤交互,提高檢索效率
hibernate 共有兩個級別的緩存
* 一級緩存,保存Session中, 事務(wù)范圍的緩存
* 二級緩存,保存SessionFactory ,進程范圍的緩存
SessionFacoty 兩部分緩存
內(nèi)置 :Hibernate 自帶的, 不可卸載. 通常在 Hibernate 的初始化階段, Hibernate 會把映射元數(shù)據(jù)和預(yù)定義的 SQL 語句放到 SessionFactory 的緩存中, 映射元數(shù)據(jù)是映射文件中數(shù)據(jù)的復(fù)制, 而預(yù)定義 SQL 語句時 Hibernate 根據(jù)映射元數(shù)據(jù)推到出來的. 該內(nèi)置緩存是只讀的.
外置 :一個可配置的緩存插件. 在默認情況下, SessionFactory 不會啟用這個緩存插件. 外置緩存中的數(shù)據(jù)是數(shù)據(jù)庫數(shù)據(jù)的復(fù)制, 外置緩存的物理介質(zhì)可以是內(nèi)存或硬盤,必須引入第三方緩存插件才能使用。
2.二級緩存的內(nèi)部結(jié)構(gòu)以及存儲特點
Hibernate二級緩存分為:
* 類緩存區(qū)域
* 集合緩存區(qū)域
* 更新時間戳區(qū)域
* 查詢緩存區(qū)域
類緩存區(qū)數(shù)據(jù)存儲特點
* 從二級緩存區(qū)返回數(shù)據(jù)每次地址都是不同的(散裝數(shù)據(jù) )。每次查詢二級緩存,都是將散裝數(shù)據(jù)構(gòu)造為一個新的對象
集合緩存區(qū)
如果注釋掉 Order類緩存,orders 集合無法緩存
* 集合緩存區(qū)數(shù)據(jù)緩存依賴類緩存區(qū)數(shù)據(jù)緩存
** 一級緩存的操作會同步到二級緩存
更新時間戳區(qū)域
作用:記錄數(shù)據(jù)最后更新時間,確保緩存數(shù)據(jù)是有效的
Hibernate 提供了和查詢相關(guān)的緩存區(qū)域:
**時間戳緩存區(qū)域: org.hibernate.cahce.UpdateTimestampCache
時間戳緩存區(qū)域存放了對于查詢結(jié)果相關(guān)的表進行插入, 更新或刪除操作的時間戳. Hibernate 通過時間戳緩存區(qū)域來判斷被緩存的查詢結(jié)果是否過期, 其運行過程如下:
T1 時刻執(zhí)行查詢操作, 把查詢結(jié)果存放在 QueryCache 區(qū)域, 記錄該區(qū)域的時間戳為 T1
T2 時刻對查詢結(jié)果相關(guān)的表進行更新操作, Hibernate 把 T2 時刻存放在 UpdateTimestampCache 區(qū)域.
T3 時刻執(zhí)行查詢結(jié)果前, 先比較 QueryCache 區(qū)域的時間戳和 UpdateTimestampCache 區(qū)域的時間戳, 若 T2 >T1, 那么就丟棄原先存放在 QueryCache 區(qū)域的查詢結(jié)果, 重新到數(shù)據(jù)庫中查詢數(shù)據(jù), 再把結(jié)果存放到 QueryCache 區(qū)域; 若 T2 < T1, 直接從 QueryCache 中獲得查詢結(jié)果。
**更新時間戳區(qū)域,記錄數(shù)據(jù)最后更新時間,在使用二級緩存時,比較緩存時間t1 與更新時間 t2 , 如果 t2 > t1 丟棄原來緩存數(shù)據(jù),重新查詢緩存
查詢緩存
有人稱查詢緩存 為hibernate 第三級緩存
* 二級緩存緩存數(shù)據(jù)都是類對象數(shù)據(jù),數(shù)據(jù)都是緩存在 "類緩存區(qū)域" ,二級緩存緩存PO類對象,條件(key)是id
查詢緩存適用場合:
**應(yīng)用程序運行時經(jīng)常使用查詢語句
**很少對與查詢語句檢索到的數(shù)據(jù)進行插入, 刪除和更新操作
如果查詢條件不是id查詢, 緩存數(shù)據(jù)不是PO類完整對象 =====> 不適合使用二級緩存
查詢緩存: 緩存的是查詢數(shù)據(jù)結(jié)果, key是查詢生成SQL語句 , 查詢緩存比二級緩存功能更加強大
適用查詢緩存的步驟
1)配置二級緩存(查詢緩存依賴二級緩存)
2)啟用查詢緩存 hibernate.cfg.xml
<property name="hibernate.cache.use_query_cache">true</property>
3)必須在程序中指定使用查詢緩存
query.setCacheable(true);
3.二級緩存的并發(fā)策略
transactional : 提供Repeatable Read事務(wù)隔離級別,緩存支持事務(wù),發(fā)生異常的時候,緩存也能夠回滾
read-write : 提供Read Committed事務(wù)隔離級別,更新緩存的時候會鎖定緩存中的數(shù)據(jù)
nonstrict-read-write :導(dǎo)致臟讀, 很少使用
read-only : 數(shù)據(jù)不允許修改,只能查詢
* 很少被修改,不是很重要,允許偶爾的并發(fā)問題, 適合放入二級緩存??紤]因素(二級緩存的監(jiān)控【后面學(xué)習(xí)】,它是是否采用二級緩存主要參考指標)
4.Hibernate支持哪些二級緩存技術(shù)?
* EHCache (主要學(xué)習(xí),支持本地緩存,支持分布式緩存)
可作為進程范圍內(nèi)的緩存, 存放數(shù)據(jù)的物理介質(zhì)可以是內(nèi)存或硬盤, 對 Hibernate 的查詢緩存提供了支持。
* OSCache
可作為進程范圍內(nèi)的緩存, 存放數(shù)據(jù)的物理介質(zhì)可以是內(nèi)存或硬盤, 提供了豐富的緩存數(shù)據(jù)過期策略, 對 Hibernate 的查詢緩存提供了支持
* SwarmCache
可作為集群范圍內(nèi)的緩存, 但不支持 Hibernate 的查詢緩存
* JBossCache
可作為集群范圍內(nèi)的緩存, 支持 Hibernate 的查詢緩存
|