hibernate 回顧一
開發流程:
1. 由Domain object -> mapping -> db(官方推薦)
2. 由DB開始,用工具生成mapping和Domain object。(使用較多)。
3. 由映射文件開始。
Domain Object限制
1. 默認的構造方法(必須的)
2. 有無意義的標識符id(主鍵)(可選)
3. 非final的,對懶加載有影響(可選)
persist與save的區別:
當開啓事務時,二者效果是一樣的。
當不開啓事務時,save會發出增加語句,只是未提交(即回滾)
當不開啓事務時,persist方法不會發出增加語句插入記錄。
load()與get()的區別:
load返回的是代理,不會立即訪問數據庫,在用到屬性時才發出查詢語句。
get直接發出查詢語句
對象的狀態:
瞬時(transient):數據庫中沒有數據與之對應,超過作用域會被JVM垃圾回收器回收,一般是new出來且與session沒有關聯的對象。
持久(persistent):數據庫中有數據與之對應,當前與session有關聯,並且相關聯的session並沒有關閉,事務沒有提交:持久對象狀態發生改變,在事務提交時會影響到數據庫(hibernate能檢測到)。
脫管(detached):數據庫中有數據與之想對應,但當前沒有session與之關聯,託管對象狀態發生改變,hibernate不能檢測到。
saveOrUpdate,merge(根據ID和version的值來確定是save或是update,ID是0或者是null時,是瞬時的),調用merge你的對象還是託管的。
處於persistent的對象會在事務提交時,進行同步。
HQL(Hibernate Query Language)
面向對象的查詢語言,與SQL不同,HQL的對象名是區分大小寫的(除了JAVA類和屬性其他部分不區分大小寫);HQL中查的是對象而不是表,並且支持多態:HQL主要通過Query接口來操作,Query的創建方式:
Query q = session.createQuery(hql);
from Person;
from User user where user.name=:name
from User user where user.name=:name and user.birthday <:birthday.
Criteria是一種比HQL更面向對象的查詢方式,Criteria的創建方式:
Creteria crit = session.createCriteria(DomainClass.class);
crit.add(Restrictions.eq(“name”, name));
crit.add(Restrictions.);
hibernate中類與屬性與數據庫中關鍵字衝突的解決辦法:
1. 修改表名或字段名
2. 把表名或字段名加反引號
hibernate實現分頁:
query.setFirstResult();設置查詢開始的記錄數,從0開始
query.setMaxResults();設置查詢出的記錄數
hibernate屬性文件和xml文件可以同時存在,但xml文件中的屬性會覆蓋屬性文件中的屬性。也可以通過編碼方式實現。
hibernate默認不會提交數據
關聯關係映射通常情況下是最難配置正確的。包括單向關係映射,和雙向關係映射。在傳統的數據建模中,允許爲Null的值的外鍵被認爲是一種不好的實踐。因此Hibernate建議外鍵設置爲不允許爲Null。
多對一關聯映射:
<many-to-one name="user" class="User" not-null="false" lazy="false" >
<column name="user_id" ></column>
</many-to-one>
<set name="pens" lazy="false">
<key column="user_id" >
</key>
<one-to-many class="Pen"/>
</set>
一對一外鍵關聯映射:
<many-to-one name="user" class="User" not-null="false" lazy="false" unique=”true”>
<column name="user_id" ></column>
</many-to-one>
<one-to-one name="pen" class="Pen" property-ref="user" ></one-to-one>
基於主鍵的one-to-one(person的映射文件)
<id name="id">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="name" column="pen_name" not-null="true"/>
<one-to-one name="user" constrained="true" lazy="false" />
<one-to-one name="pen" lazy="false"/>
注意保存從對象時,一定要先保存主對象。
注意此時,當查詢主對象時,無論是否懶加載,用連接表查詢,
當查詢從對象時,先查詢從對象,當用到主對象時,然後連接表查詢
多對多關聯:
多對多在操作和性能方面都不太理想,所以多對多的映射使用較少,實際使用中最好轉換成一對多的對象模型;hibernate會爲我們創建中間關聯表,轉換稱兩個一對多。
<set name="pens" table="users_pens">
<key column="user_id" />
<many-to-many class="Pen" column="pen_id"></many-to-many>
</set>
<set name="users" table="users_pens">
<key column="pen_id" />
<many-to-many class="User" column="user_id"></many-to-many>
</set>
多對多中,應該只有一方維護關係,如果雙方都維護關係,會出現在中間表中重複插入數據的現象。
注意:實際設計中,我們一般並不會在少的一端建立存放多的集合,因爲如果多的一端如果很多的話,查詢全部效率是很低的,而且存儲集合需要佔用大量的內存空間,一般我們不會建立,而只是單端關聯,手動分頁查詢多的一段的數據。
組合鍵:
<component name=”name”>
<property name=”” value=””>
</component>
注:當兩個實體有關聯,如果要在一張表裏表示所有的屬性,則用組合鍵,如果在兩張表裏表示可用關聯。
hibernate一般情況是對於關聯的查詢,是通過兩條select語句實現的,默認情況下也都是懶加載的,是通過cglib生成代理對象,而對於通過主鍵實現的一對一關聯,如果是查詢主對象是個例外,它只是發一條左連接查詢。
hibernate 映射list集合
<list name=”emps”>
<key column=”depart_id”/>
<list-index column=”order_col” />
<one-to-many class=”Employee” />
</list>
</class>
hibernate用下面這列來記錄插入的順序,這樣在數據庫表中就會多加一列
<list-index column=”order_col” />
<bag name=”emps”>
<key column=”depart_id”/>
<one-to-many class=”Employee” />
</bag>
</class>
這是依舊映射java中的list,但是它不保存順序,即數據庫表中不會有記錄插入順序的一列。
此外hibernate還可以映射list, array, map
一般情況下,選擇set就可以了,list(bag), array, map實際上用的較少
注意,持久化中的集合類不應該是具體的集合類,應該是接口。
級聯和關係維護
cascade和inverse
Cascade用來說明當對主對象進行某種操作時,是否對其關聯的從對象也作類似的操作,常用的cascade:
none, all, save-update, delete, lock, refresh, evict, repliace, persisit, merge, delete-orphan。一般對many-to-one, many-to-many 不設置級聯,在<one-to-one>和<one-to-many>中設置級聯。
inverse表“是否放棄維護關聯關係”(在java裏兩個對象的產生關聯時,對數據庫表的影響),在one-to-many和many-to-many的集合定義中使用,inverse=”true”表示該對象不維護關聯關係;該屬性的值一般在使用有序集合時設置稱false(注意hibernate的缺省值默認呢爲false)
one-to-many維護關聯關係就是更新外鍵,many-to-many維護關聯關係就是在中間表增加記錄。
注:配置稱one-to-one的對象不維護關聯關係。
Session刷出(flush)
每間隔一段時間,Session會執行一些必需的SQL語句來把內存中的對象的狀態同步到JDBC連接中。這個過程被稱爲刷出(flush),默認會在下面的時間點執行:
在某些查詢執行之前
在調用org.hibernate.Transaction.commit()的時候
在調用Session.flush()的時候
涉及到的SQL語句會按照下面的順序發出執行。
所有對實體進行插入的語句,其順序按照對象執行Session.save()的時間順序
所有對實體進行更新的語句
所有進行集合刪除的語句
所有對集合元素進行刪除,更新或者插入的語句
所有進行集合插入的語句
所有對實體進行刪除的語句,其順序按照對象執行Session.delete()的時間順序
(有一個例外是,如果對象使用native方式生成ID(持久化標識))的話,它們一執行save就會被插入。
除非你明確地發出了flush()指令,關於Session何時會執行這些JDBC調用是完全無法保證的,只能保證它們的執行的前後順序,當然,Hibernate保證,Query.list(..)絕對不會返回已經失效的數據,也不會返回錯誤數據。
Hibernate傳播性持久化(transitive persistence)
對每一個對象都要執行保存,刪除或重關聯操作讓人感覺有點麻煩,尤其是在處理許多彼此關聯的對象的時候。一個常見的例子是父子關係。
異常處理: 不應該強迫應用程序開發人員,在底層捕獲無法恢復的異常。在大多數軟件系統中,非檢查期異常和致命異常都是在相應方法調用的堆棧的頂層被處理的。(也就是說,在軟件上面的邏輯層),並且提供一個錯誤信息給應用軟件的用戶(或者採取其他某些相應的操作)。
懶加載: load的使用相對較少。Hibernate.initialze(Object object);初始化懶加載。
一對一懶加載(系統影響不大):
查詢主對象不會懶加載,因爲查詢主對象時,不能確定從對象是否存在,從對象纔會懶加載而且抓取方式必須是feach=’select’,contrained=true。
lazy標識是否使用代理,feach採用何種抓去策略,二者可能相互牽扯。
一對多懶加載(影響較大) ,如果查詢發費的代價較大,不如直接賦予代理,節省效率。lazy=true fetch=select
多對一賴加載(影響不大)與一對一大致相同
能夠懶加載的對象都是被Hibernate該寫過的代理。
解決方案:可以在調用時,傳入是否懶加載(屬性)
或者使用openSessionInvew
數據類型:
<property name=”name” type=”java.lang.String” />
type可以是hibernate,java類型,或者你自己的類型(需要實現hibernate的一個接口)
基本類型一般不需要在映射文件(hbm.xml)中說明,只有在一個JAVA類型和多個數據庫數據類型相對應時,並且你想要的和hibernate缺省映射不一致時,需要在映射文件中指明類型(如:java.util.Date,數據庫DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省會把java.util.Date映射成爲DATATIME型),而如果你想映射成TIME,則你必須在映射文件中指定類型。
Session是非線程安全的,生命週期較短,代表一個和數據庫的連接,在B/S系統中,一般不會超過一個請求;內部維護一級緩存和數據庫連接,如果session長時間打開,會長時間佔用內存和數據庫連接。
緩存的作用主要用來提高性能,可以簡單的理解成一個Map;使用緩存涉及到三個操作:把數據放入緩存,從緩存中獲取數據,刪除緩存中的無效數據。
一級緩存,Session級共享
save, update, saveOrUpdate,load,get list,iterator,lock這些方法都會將對象放在一級緩存中,一級緩存不能控制緩存的數量,所以要注意大批量操作數據時可能造成內存溢出;可以用evict,clear方法清除緩存中的內容。但是能從緩衝中拿數據的不多,get, load, iterator.
二級緩存,SessionFactory級共享,實現爲可插拔,通過修改cache.provider_class參數來改變;
hibernate內置了對EhCache,OSCache,TreeCache,SwarmCache的支持,可以通過實現CacheProvider和Cache接口來加入對Hibernate不支持的緩存的實現。
分佈式緩存和中央緩存。
使用緩存的條件:
讀取大於修改
數據量不能超過內存容量。
對數據要有獨享的控制
可以容忍出現無效數據
JTATransaction
可以簡單的理解成垮數據庫的事務,由應用JTA容器實現;使用JTATransaction需要配置hibernate.transaction.factory_class參數,該參數缺省值是org.hibernate.transaction.JDBCTransactionFactory,當使用JTATransaction時,需要將該參數改成org.hibernate.transaxtion.JTATransactionFactory,並配置jta.UserTransaction參數JNDI名(Hibernate在啓動JTATransaction時要用該值到JNDI的上下文Context中去找javax.transaction.UserTransaction).
javax.transaction.UserTransaction tx = context.lookup(“jndiName”);
session context和事務邊界
用current_session_context_class屬性來定義context(用sessionFactory.getCurrentSession()來獲得session),其值爲:
1. thread:ThreadLocal用來管理Session實現多個操作共享一個Session,避免反覆獲取Session,並控制事務邊界,此時session不能調用close當commit或rollback的時候session會自動關閉。
2. jta:有jta事務管理器技術來管理事務。
悲觀鎖和樂觀鎖
悲觀鎖有數據庫來實現,樂觀鎖由hibernate用version和timestam來實現。
session.fllush()是同步session一級緩存和數據庫。
大批量處理
大量操作數據時可能造成內存溢出,解決辦法如下:
1. 清除session中的數據
for (int i=0;i<10000000;i++) session.save(obj);
for(int i=0;i<10000000;i++) {
session.save(obj);
if (i%50 == 0) {session.flush(); session.clear();}
}
2. 用StaelessSession接口:它不和一級緩存、二級緩存交互,也不觸發任何事件,監聽器,攔截器,通過該接口的操作會立刻發給數據庫,與JDBC的功能一樣。
StatelessSession ss = sessionFactory.openStatelessSession();該接口的方法與Session類似。
Query.executeUpdate()執行批量更新,會清除相關聯的類二級緩存(sessionFactory.evict(class)),也可能會造成級聯,和樂觀鎖定出現的問題(不能更新樂觀縮版本號)。
Query q = s.createQuery(from User);
List<User> users = q.list();
for(User user : users) {
u.setBirthday(new Date());
}
批量更新的方法(3.0之後提供的方法):
Query q1 = s.createQuery(update u set birthday=:bd from user as u);
q1.executeUpdate():
Hibernate不適合的場景:
不適合OLAP(On-Line Analytical Processing聯機分析處理),以查詢分析數據爲主的系統;適合OLTP(On-line transaction proceesing聯機事務處理)。
對於些關係模式設計不合理的老系統,也不能發揮hibernate優勢,數據量巨大,性能要求苛刻的系統,hibernate也很難達到要求,批量操作數據的效率也不高。
攔截器與事件都是hibernate的擴展機制,Interceptor接口是老的實現機制,現在改成事件監聽器機制;他們都是hibernate回調接口,hiberante在save,delete,update…等會回調這些類。
事件系統(Event system):
如果需要響應持久層的某些特殊事件,你也可以使用Hibernate3的事件框架。該事件系統可以用來替代攔截器,也可以作爲攔截器的補充來使用。
基本上,Session接口的每個方法都有相對應的事件。比如LoadEvent,FlushEvent,等等。當某個方法被調用時,Hibernate Session會生成一個相對應的事件並激活所有配置好的事件監聽器。