Hibernate實踐

http://35java.com/zhibo/forum.php?mod=viewthread&tid=358&extra=page%3D4

一.




在實際項目中使用Hibernate有兩年多了,在兩年多的實踐過程中既體驗到了Hibernate帶來的N多好處,同時也碰到不少的問題,特寫此篇文章做個總結,記錄自己在Hibernate實踐中的一些經驗,希望對於新使用Hibernate的朋友能有個幫助,避免走過多的彎路。



閱讀本文前建議至少擁有Hibernate的一些基本知識,因爲本文不會去詳細介紹相關的基本知識,最好就是先用Hibernate開發了一個HelloWorld,^_^。



根據自己所經歷的項目中使用Hibernate所涉及的範圍,本文從開發環境、開發、設計、性能、測試以及推薦的相關書籍方面進行講述,本篇文檔不會講的非常細緻,只是根據自己在實踐時的經驗提出一些建議,關於細緻以及具體的部分請參閱《Hibernate Reference》或推薦的相關書籍章節。



此文檔的PDF版本請到此下載:



http://www.blogjava.net/Files/BlueDavy/Hibernate 實踐.rar


本文允許轉載,但轉載時請註明作者以及來源。



作者:BlueDavy


來源:www.blogjava.net/BlueDavy


二.
開發環境



Hibernate 開發環境的搭建非常的簡單,不過爲了提高基於Hibernate開發的效率,通常都需要使用一些輔助工具,如xdoclet、middlegen等。



儘管Hibernate已經封裝提供了很簡單的進行持久的方法,但在實際項目的使用中基本還是要提供一些通用的代碼,以便在進行持久的相關操作的時候能夠更加的方便。



2.1. lib


2.1.1.
Hibernate lib


Hibernate 相關的 lib 自然是開發環境中首要的問題,這部分可以從 Hibernate 的官方網站進行下載,在其官方網站中同時提供了對於 Hibernate 所必須依賴的 lib 以及其他可選 lib 的介紹。



2.2. xdoclet


Hibernate 作爲ORM工具,從名字上就能看出它需要一個從O à R 的Mapping的描述,而這個描述就是在使用Hibernate時常見的hbm.xml,在沒有工具支持的情況下,需要在編寫持久層對象的同時手寫這個文件,甚爲不便。



在jdk 5.0未推出之前,xdoclet支持的在javadoc中編寫註釋生成相關配置文件的方式大受歡迎,減少了編寫hibernate映射文件的複雜性,手寫一個完整的hibernate映射文件出錯機率比較的高,再加上手寫容易造成編寫出來的風格相差很大,因此,基於xdoclet來生成hbm.xml的方式被大量的採用,基於xdoclet來編寫能夠基於我們在持久層對象上編寫的javadoc來生成hbm.xml文件,非常的方便。



2.2.1.
Hibernate template


如果沒記錯的話,大概在 04 年的時候 javaeye 上有位同仁整理了一個這樣的 template 文件, ^_^ ,非常感謝,我一直都在用着,呵呵。



這個文件的方便就是把它導入 eclipse 後,在 javadoc 中我們可以直接寫 hibid ,然後按 eclipse 的代碼輔助鍵 (alt+/) 來生成整個 hibernate.id 的相關的格式,呵呵,免得在寫 hibernate.id 這些東西的時候過於麻煩, ^_^ ,這個 template 文件我稍微做了修改,可在這裏下載:



http://www.blogjava.net/Files/BlueDavy/templates-eclipse-tags.rar


當然,你也可以選擇直接用 xdoclet 提供的 template 文件,不過 xdoclet 官方網站上好像只提供了可直接導入 idea 的模板文件。



關於註釋上的 hibernate.id 這些東西具體請參見 xdoclet 官方網站的說明。



如果你的項目採用的是 jdk 5 ,那麼就可以直接使用 hibernate annotation 了,那就更爲方便。



2.2.2.
Ant task build


Eclipse 裏沒有集成 xdoclet 的插件,你也可以去安裝一個 jboss ide 的插件,裏面有 xdoclet 的插件,反正我是覺得太麻煩了。



在項目中我仍然採用 ant task 的方式來生成 hbm.xml , target 如下所示:



<path id="app.classpath">


<pathelement path="${java.class.path}"/>


<fileset dir="${xdoclib.dir}">


<include name="*.jar"/>


</fileset>


</path>


<target name="hbm" description=" 生成映射文件 ">


<tstamp>


<format property="TODAY" pattern="yy-MM-dd"/>


</tstamp>


<taskdef name="hibernatedoclet" classname="xdoclet.modules.hibernate.HibernateDocletTask" classpathref="app.classpath"/>


<hibernatedoclet destdir="src/java" force="true" verbose="true" excludedtags="@version,@author,@todo">


<fileset dir="src/java">



<include name="**/po/**/*.java"/>


</fileset>


<hibernate version ="3.0"/>


</hibernatedoclet>


</target>


這個文件請根據項目情況以及環境稍做修改, ^_^ ,其中需要通過 properties 文件指明 xdocletlib.dir ,類似 xdocletlib.dir=c:\xdocletlib ,裏面放置 xdoclet 的相關 jar 文件。



在搭建好了這樣的環境後,就可以在直接在 eclipse 中運行 ant 文件中的這個 target 來生成 hbm.xml 。



2.3. Hibernate3 Tools


如果採用Hibernate 3,則可以直接下載Hibernate 3 Tools的Eclipse Plugin,那就可以類似在PL/SQL裏執行sql一樣在eclipse裏執行hql,^_^


2.4. HibernateUtil


爲了方便項目中Hibernate的使用,一般來說都會提供HibernateUtil這樣的類,這個類的作用主要是創建sessionFactory和管理session,在Hibernate 3以前採用的是在這裏建立ThreadLocal來存放session,在Hibernate 3以後則可以直接使用SessionFactory.getCurrentSession來獲取session,而session的獲取方式則可通過在hibernate.cfg.xml中執行current_session_context_class的屬性來決定是採用thread或jta或自定義的方式來產生session。



2.5. CommonDao


在持久層部分目前採用的較多的仍然是dao模式,Hibernate作爲ORM工具已經提供了CRUD的封裝,類如可以使用session.save();session.persist()這樣簡單的方式來完成CRUD的操作,但在實際的項目中還是需要提供一個通用的Dao,來簡化對於事務、異常處理以及session的操作,同時提供一些項目中需要的相關操作。



三.
開發



在完成了Hibernate的開發環境的搭建後,就可以基於Hibernate進行持久層的開發了,對於持久層開發來說,會涉及到實體的編寫、實體的維護以及實體的查詢三個部分。



3.1. 實體的編寫



Hibernate 的一個明顯的優點就是在於可透明化的對對象進行持久,這也就意味着持久對象根本就不需要依賴任何的東西,可以採用POJO的方式來編寫,在Hibernate 3以上版本還提供了對於Map、XML的方式的持久的支持,就更方便了,在項目中,更多采用的仍然是POJO的方式。



在實體的編寫上應該說不會有什麼問題,只要仔細查看xdoclet關於hibernatedoclet部分的說明即可完成。



這塊需要學習的主要是普通的值類型註釋的編寫、id字段註釋的編寫、關聯註釋的編寫,這些部分xdoclet均提供了較詳細的說明。



3.2. 實體的維護



3.2.1.
新增 / 編輯 / 刪除



新增 / 編輯 / 刪除是持久操作中最常使用的維護性操作,基於 Hibernate 做這樣的維護就比採用 sql 的方式簡單多了,通過上面 CommonDao ,就可以直接完成 dao.save 、 dao.update 、 dao.delete 的操作,而且在 Hibernate 3 也支持了批量的 insert 、 update 和 delete 。



這個部分中需要注意的是 Hibernate 對於對象的三種狀態的定義:



u
Transient


很容易理解,就是從未與 session 發生過關係的對象, ^_^ ,例如在代碼中直接 User user=new User() ;這樣形成的 user 對象,就稱爲 Transient 對象了。



u
Detached


同樣很容易理解,就是與 session 發生過關係的對象,但 session 已經關閉了的情況下存在的對象,例如:



User user=new User();


user.setName(“bluedavy”);


session.save(user);


session.close();


在 session.close() 後這個時候的 user 對象就處於 Detached 狀態之中了,如果想將這個對象變爲 Persistent 狀態,可以通過 session.merge 或 session.saveOrUpdate() 等方式來實現。



Detached 狀態的對象在實際的應用中最常採用,從概念上我們可以這麼理解,處於 Detached 狀態的對象可以看做是一個 DTO ,而不是 PO ,這從很大程度上就方便了 PO 在實際項目中的使用了。



u
Persistent


Persistent 狀態就是指和 Session 發生了關係的對象,並且此時 session 未關閉,舉例如下:



User user=new User();


user.setName(“bluedavy”);


session.save(user);


user.getName();


在 session.save 後 user 就處於 Persistent 狀態,此時如果通過 session 根據 user 的 id 去獲取 user 對象,則可發現獲取的對象和之前的 user 是同一個對象,這是 session 一級緩存所起的作用了,當然,也可以強制的刷新 session 的一級緩存,讓 session 從數據庫中重新獲取,只需要在獲取前執行 session.evict(user) 或 session.clear() 。



3.2.2.
關聯維護



關聯維護在 Hibernate 中表現出來可能會讓熟悉使用 sql 的人有些的不熟,但其實以對象的觀點去看是會覺得很正常的。



在 Hibernate 的關聯維護中,最重要的是 inverse 和 cascade 兩個概念。



u
inverse


inverse 從詞義上看過去可能不是那麼容易理解,其實它的意思就是由誰來控制關聯關係的自動維護,當 inverse=true 就意味着當前對象是不能自動維護關聯關係,當 inverse=false 就意味着當前對象可自動維護關聯關係,還是舉例來說:



假設 Org 和 User 一對多關聯,



當 org 中 getUsers 的 inverse=false 的情況:



org.getUsers().add(user);


dao.save(org);


這樣執行後將會看到數據庫中 user 這條記錄中的 orgId 已經被設置上去了。



當 inverse=true 的情況下,執行上面的代碼,會發現在數據庫中 user 這條記錄中的 orgId 沒有被設置上去。



^_^ , inverse 的作用這樣可能看的不是很明顯,在下面的一對多中會加以描述。



u
cascade


cascade 的概念和數據庫的 cascade 概念是基本一致的, cascade 的意思形象的來說就是噹噹前對象執行某操作的情況下,其關聯的對象也執行 cascade 設置的同樣的操作。



例如當 org.getUsers 的 cascade 設置爲 delete 時,當刪除 org 時,相應的 users 也同樣被刪除了,但這個時候要注意, org.getUsers 這個集合是被刪除的 user 的集合,也就是說如果這個時候數據庫中新增加了一個 user 給 org ,那麼這個 user 是不會被刪除的。



cascade 的屬性值詳細見《 Hibernate reference 》。



3.2.2.1.
一對一



一對一的關聯維護在實際項目中使用不多,一對一在Hibernate中可採用兩種方式來構成,一種是主鍵關聯,一種是外鍵關聯。



一對一的使用推薦使用主鍵關聯,具體配置方法請參見《Hibernate Reference》。



3.2.2.2.
一對多/多對一



一對多/多對一的關聯維護在實際項目中使用是比較多的,在Hibernate中可採用多種方式來配置一對多的關聯,如採用Set、List、Bag、Map等,具體在《Hibernate Reference》中都有詳細說明。



在這裏我想說的一點就是關於inverse的設置,在一對多的情況下建議將一端的inverse設爲true,而由多端去自動維護關聯關係,爲什麼這樣做其實挺容易理解的,假設org和user爲一對多的關聯,org.getUsers的inverse設置爲false,org.getUsers().add(user);dao.update(org);當update的時候org所關聯的所有user的orgId都會更新一次,可想而知這個效率,而如果改爲在多端維護(多端設置爲inverse=false),則是這樣:user.setOrg(org);dao.update(user);當update的時候就僅僅是更新user這一條記錄而已。



另外一點就是合理的設置cascade,這個要根據需求來實際決定。



3.2.2.3.
多對多



多對多的關聯維護在實際項目中其實也是比較多的,儘管在《Hibernate Reference》中認爲多對多的情況其實很多時候都是設計造成的。



多對多的關聯也同樣可以採用Set、List等多種方式來配置,具體在《Hibernate Reference》中也有詳細的說明。



多對多的關聯維護上沒有什麼需要多說的,在實踐過程中來看這塊不會出什麼太多問題,唯一需要注意的是合理設置cascade,這個要根據項目的實際情況而定。



3.3. 實體的查詢



Hibernate 提供了多種方式來支持實體的查詢,如對於原有熟悉sql的人可以繼續使用sql,符合對象語言的對象查詢語句(HQL)以及條件查詢API(Criteria)。



在熟練使用hql或criteria的情況下,我相信你會覺得Hibernate的查詢方式會比採用sql的方式更加簡便。



3.3.1.
符合對象語言的查詢語句



Hibernate 提供了一種符合對象語言的查詢語句,稱爲 HQL ,這種語句的好處是能夠避免使用 sql 的情況下依賴數據庫特徵的情況出現,同時它帶來的最大的好處就是我們能夠根據 OO 的習慣去進行實體的查詢。



對於 HQL 沒有什麼多講的,如果熟悉 sql 的人應該也是能夠很快就學會 HQL ,而如果不熟悉 sql 的人那也沒關係, HQL 的上手是非常容易的,具體請參考《 Hibernate Reference 》。



3.3.2.
佔位符式的查詢



佔位符式的查詢 ( 就是採用 ? 替換查詢語句中的變量 ) 是在採用 sql 的情況下經常使用的一種查詢方式,也是查詢時推薦使用的一種方式。



Hibernate 中的查詢參數主要有兩種類型:值類型和實體類型,值類型就是指一個切實的值 ( 如 String 、 int 、 List 這些 ) ,實體類型就是一個具體的實體,如編寫的 User 、 Organization 等,值類型的查詢和普通 sql 幾乎一樣,而實體類型的查詢就體現了 Hibernate 的強項, ^_^ ,可以起到簡化 sql 的作用,並且使得查詢語句更加容易理解。



3.3.2.1.
值類型



3.3.2.1.1.
簡單值



舉例如下:



from User u where u.name=:username and u.yearold=:yearold


這就是一個常見的簡單值的佔位符式的查詢,通過這樣的方式就可以把值注入到參數中:



query.setParameter(“username”,”bluedavy”);


query.setParameter(“yearold”,25);


同樣, hibernate 也支持和 sql 完全相同的 ? 的方式,那麼上面的語句以及注入參數的方式就變爲了:



from User u where u.name=? and u.yearold=?


query.setParameter(0,”bluedavy”);


query.setParameter(1,25);


推薦使用第一種,那樣參數的意義更容易被理解。



3.3.2.1.2.
in 查詢



in 查詢也是經常被使用到的一種查詢,在 Hibernate 中表現出來會稍有不同,不過如果按照對象觀點去看就很容易理解了,例如下面這句:



from User u where u.name in (:usernameList)


在 Hibernate 中通過這樣的方式將值注入到這個參數中:



List list=new ArrayList();


list.add(“jerry”);


list.add(“bluedavy”);


query.setParameterList(“usernameList”,list);


在 sql 中通常是組裝一個由 , 連接的值來構成 in 中的參數值,而在 Hibernate 中則依照對象轉化爲採用 list 了, ^_^ ,是不是更方便些。



3.3.2.2.
實體類型



在Hibernate中關聯採用的都是對象形式,表現對外就是隱藏了數據庫的外鍵的部分,這也就對習慣使用sql查詢的人帶來一個問題,因爲無法再操作外鍵字段,那麼在涉及到關聯的實體的查詢時應該怎麼做呢,我把它分爲單實體和實體集合兩種情況來說說。



3.3.2.2.1.
單實體



單實體的查詢對應到 sql 情況通常是在一對多的情況下通過多端查詢同時結合一端的一些過濾條件,在 sql 中通常採用 join 的方式來實現這個,而在 Hibernate 中要實現這點就更容易了,舉例如下:



User 和 Organization 是一對多,現在要查詢屬於組織機構名稱爲 ”Blogjava” 以及用戶年齡大於 20 的用戶:



from User u where u.org.name=rgname and u.yearold>:yearold


query.setParameter(“orgname”,”Blogjava”);


query.setParameter(“yearold”,20);


可以看到這樣的查詢語句比 sql 更簡單多了,同時也更容易理解多了。



3.3.2.2.2.
實體集合



實體集合過濾形式的查詢在實際的項目中也經常會碰到,仍然用上面的例子,但改爲通過 Organization 去查詢:



from Organization org where org.name=rgname and org.users.yearold>:yearold


是不是比 sql 簡單多了,而且更容易理解呢, ^_^


這個時候對象化查詢語句的優勢就體現出來了,而不用陷入 sql 的那種關係型的通過外鍵進行查詢的方式。



3.3.3.
NamedQuery


NamedQuery 的意思就是指在 PO 的映射文件中定義關於 PO 的查詢語句,而在應用中指需要直接調用此查詢語句的別名即可,這個好處非常明顯,使得所有的查詢語句可以統一的進行管理,同樣,我們可以在 PO 中通過 javadoc 的方式進行定義,這就更方便了, ^_^


操作 NamedQuery 的方法和普通 hql 的方法基本一樣:



session.getNamedQuery(queryname);


其中的 queryname 就是我們定義的查詢語句的別名,一個 namedQuery 的語句的示例如下:



< query name = "validate" ><![CDATA[



from User u where u.loginname=:loginname and u.password=:password



]]></ query >


3.3.4.
Criteria


條件查詢的 API 使得我們可以採用完全對象化的方式進行實體的查詢,而不是通過 hql 的方式,在實際項目中,使用 hql 的方式更爲居多,畢竟寫起來更方便。



關於 Criteria 的具體介紹請參閱《 Hibernate Reference 》。



3.3.5.
原生 SQL


原生 SQL 不推薦使用,但在某些確實需要用 sql 的情況下那麼 Hibernate 還是支持的,具體見《 Hibernate Reference 》。



四.
設計



獨立的編寫這個章節的原因是希望在採用Hibernate的情況下充分的去發揮Hibernate的優勢,改變我們以關係形式去做持久層的設計的慣性思維,形成以OO的思想去設計持久層,所以我非常推薦通過寫PO去生成數據表的方式,而不是設計表反向導成PO的形式,當然,對於原有的系統那就沒辦法了。



OO 思想中的核心三要素:封裝、繼承和多態,在Hibernate的支持下同樣可以充分發揮OO的三要素來優化持久層的設計。



4.1. 封裝



4.1.1.
Component


Hibernate 中有一個 Component 的概念,這就允許在進行持久層設計的時候採用細粒度級的領域模型進行設計,例如在 User 對象中需要記錄 User 的 firstname 、 lastname 這些信息,而在其他的表中也有這種需求,那麼在 Hibernate 中我們就可以把 firstname 、 lastname 組裝爲一個 UserName 對象,作爲 Component 放入 User 中,在 user 中就可以變爲採用 user.getUserName.getFristName 的方式來獲取。



Component 對於我們採用對象的封裝概念進行持久層設計提供了很好的支持,同時在 Hibernate 中還有 Elements 、 Properties 這些元素,具體請參見《 Hibernate Reference 》。



4.2. 繼承



繼承使得我們可以對持久層中的對象進行抽象,類如我們可以形成Person這個對象,而User、Employee都繼承自這個對象。



繼承在數據庫形式的設計中固然也可以實現,但通常不能以對象的觀點去發揮的淋漓盡致,當然不是說以對象的方式去設計一定是最好的。



在Hibernate中對於繼承映射到數據表有幾種不同的策略,各有適用的不同場合,具體的解釋和說明見《Hibernate Reference》



4.2.1.
單表策略



單表策略很容易理解,就是將類、子類中所有的屬性都放至一張表中,這對於子類屬性不多的情況非常有效。



在 Hibernate 中通常將子類定義爲 @hibernate.subclass 的方式來實現這個策略。



4.2.2.
每個子類一張表



每個子類一張表在 Hibernate 中有幾種實現方式, @hibernate.join-subclass 、 @hibernate.join-subclass-key 的組合方式以及 @hibernate.join-subclass 、 @hibernate.discriminator 的組合方式是較常用的兩種方式,第一種方式採用的是主鍵關聯方式,第二種方式採用的是 discriminator 字段的關聯方式,個人比較推崇第一種方式。



這種策略適合在子類屬性和父類有較大不同的情況下采用。



4.2.3.
每個具體類一張表



這種策略適合在類層次結構上有一定數量的抽象類的情況下使用,同樣有兩種方式,一種是採用顯式多態的方式,另一種是採用隱式多態的方式,顯式多態採用的爲 @hibernate.union-subclass 的方式,隱式多態則採用每個具體類的 PO 獨立建表的策略,在它的映射文件中將看不出任何的和接口、抽象類的關係,同時對於抽象類,需要指明其 abstract=”true” 。



4.3. 多態



4.3.1.
查詢



在查詢中很容易體現 Hibernate 對於多態的支持,如系統有 Person 對象、 User 和 Employee 分別繼承自 Person ,同時 Person 和 Organization 對象關聯,這個時候我們通過 Organization 獲取其關聯的 Person 時得到的既有可能是 User ,也有可能是 Employee , ^_^…


五.
性能



Hibernate 作爲ORM工具,從性能上來講帶給了很多人憂慮,但我覺得Hibernate在性能上也許會帶來少許的降低,但如果對於不能合理設計數據庫和使用SQL的人來說,我覺得Hibernate反倒能提高性能,除非是在一些特殊的場合,如報表式的那種查詢推薦繼續採用JDBC的方式。



Hibernate 在性能提升上其實有很多種做法,在《Hibernate Reference》中也有專門的提升性能的章節,在這裏我提幾點在項目中通常採用的方法。
發佈了30 篇原創文章 · 獲贊 0 · 訪問量 653
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章