hibernate官方入門教程 (轉載)

 關鍵字: hibernate
hibernate官方入門教程
第一部分 - 第一個Hibernate程序
首先我們將創建一個簡單的控制檯(console-based)Hibernate程序。我們使用內置數據庫(in-memory database) (HSQL DB),所以我們不必安裝任何數據庫服務器。

讓我們假設我們希望有一個小程序可以保存我們希望關注的事件(Event)和這些事件的信息。 (譯者注:在本教程的後面部分,我們將直接使用Event而不是它的中文翻譯“事件”,以免混淆。)

我們做的第一件事是建立我們的開發目錄,並把所有需要用到的Java庫文件放進去。 從Hibernate網站的下載頁面下載Hibernate分發版本。 解壓縮包並把/lib下面的所有庫文件放到我們新的開發目錄下面的/lib目錄下面。 看起來就像這樣:

.+lib antlr.jar cglib-full.jar asm.jar asm-attrs.jars commons-collections.jar commons-logging.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar
This is the minimum set of required libraries (note that we also copied hibernate3.jar, the main archive) for Hibernate. See the README.txt file in the lib/ directory of the Hibernate distribution for more information about required and optional third-party libraries. (Actually, Log4j is not required but preferred by many developers.) 這個是Hibernate運行所需要的最小庫文件集合(注意我們也拷貝了Hibernate3.jar,這個是最重要的庫)。 可以在Hibernate分發版本的lib/目錄下查看README.txt,以獲取更多關於所需和可選的第三方庫文件信息 (事實上,Log4j並不是必須的庫文件但是許多開發者都喜歡用它)。

接下來我們創建一個類,用來代表那些我們希望儲存在數據庫裏面的event.

2.2.1. 第一個class
我們的第一個持久化類是 一個簡單的JavaBean class,帶有一些簡單的屬性(property)。 讓我們來看一下代碼:

import java.util.Date;public class Event { private Long id; private String title; private Date date; Event() {} public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; }}
你可以看到這個class對屬性(property)的存取方法(getter and setter method) 使用標準的JavaBean命名約定,同時把內部字段(field)隱藏起來(private visibility)。 這個是個受推薦的設計方式,但並不是必須這樣做。 Hibernate也可以直接訪問這些字段(field),而使用訪問方法(accessor method)的好處是提供了程序重構的時候健壯性(robustness)。

id 屬性(property) 爲一個Event實例提供標識屬性(identifier property)的值- 如果我們希望使用Hibernate的所有特性,那麼我們所有的持久性實體類(persistent entity class)(這裏也包括一些次要依賴類) 都需要一個標識屬性(identifier property)。而事實上,大多數應用程序(特別是web應用程序)都需要識別特定的對象,所以你應該 考慮使用標識屬性而不是把它當作一種限制。然而,我們通常不會直接操作一個對象的標識符(identifier), 因此標識符的setter方法應該被聲明爲私有的(private)。這樣當一個對象被保存的時候,只有Hibernate可以爲它分配標識符。 你會發現Hibernate可以直接訪問被聲明爲public,private和protected等不同級別訪問控制的方法(accessor method)和字段(field)。 所以選擇哪種方式來訪問屬性是完全取決於你,你可以使你的選擇與你的程序設計相吻合。

所有的持久類(persistent classes)都要求有無參的構造器(no-argument constructor); 因爲Hibernate必須要使用Java反射機制(Reflection)來實例化對象。構造器(constructor)的訪問控制可以是私有的(private), 然而當生成運行時代理(runtime proxy)的時候將要求使用至少是package級別的訪問控制,這樣在沒有字節碼編入 (bytecode instrumentation)的情況下,從持久化類裏獲取數據會更有效率一些。

我們把這個Java源代碼文件放到我們的開發目錄下面一個叫做src的目錄裏。 這個目錄現在應該看起來像這樣:

.+lib <Hibernate and third-party libraries>+src Event.java
在下一步裏,我們將把這個持久類(persisten class)的信息通知Hibernate

2.2.2. 映射文件
Hibernate需要知道怎樣去加載(load)和存儲(store)我們的持久化類的對象。這裏正是Hibernate映射文件(mapping file)發揮作用的地方。 映射文件告訴Hibernate它應該訪問數據庫裏面的哪個表(table)和應該使用表裏面的哪些字段(column)。

一個映射文件的基本結構看起來像這樣:

<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping>[...]</hibernate-mapping>
注意Hibernate的DTD是非常複雜的。 你可以在你的編輯器或者IDE裏面使用它來自動提示並完成(auto-completion)那些用來映射的XML元素(element)和屬性(attribute)。 你也可以用你的文本編輯器打開DTD-這是最簡單的方式來瀏覽所有元素和參數,查看它們的缺省值以及它們的註釋,以得到一個整體的概觀。 同時也要注意Hibernate不會從web上面獲取DTD文件,雖然XML裏面的URL也許會建議它這樣做,但是Hibernate會首先查看你的程序的classpath。 DTD文件被包括在hibernate3.jar,同時也在Hibernate分發版的src/路徑下。

在以後的例子裏面,我們將通過省略DTD的聲明來縮短代碼長度。但是顯然,在實際的程序中,DTD聲明是必須的。

在兩個hibernate-mapping標籤(tag)中間, 我們包含了一個 class元素(element)。所有的持久性實體類(persistent entity classes)(再次聲明, 這裏也包括那些依賴類,就是那些次要的實體)都需要一個這樣的映射,來映射到我們的SQL database。

<hibernate-mapping> <class name="Event" table="EVENTS"> </class></hibernate-mapping>
我們到現在爲止做的一切是告訴Hibernate怎樣從數據庫表(table)EVENTS裏持久化和 加載Event類的對象,每個實例對應數據庫裏面的一行。現在我們將繼續討論有關唯一標識屬性(unique identifier property)的映射。 另外,我們不希望去考慮怎樣產生這個標識屬性,我們將配置Hibernate的標識符生成策略(identifier generation strategy)來產生代用主鍵。

<hibernate-mapping> <class name="Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="increment"/> </id> </class></hibernate-mapping>
id元素是標識屬性(identifer property)的聲明, name="id" 聲明瞭Java屬性(property)的名字 - Hibernate將使用getId()和setId()來訪問它。 字段參數(column attribute)則告訴Hibernate我們使用EVENTS表的哪個字段作爲主鍵。 嵌套的generator元素指定了標識符的生成策略 - 在這裏我們使用increment,這個是非常簡單的在內存中直接生成數字的方法,多數用於測試(或教程)中。 Hibernate同時也支持使用數據庫生成(database generated),全局唯一性(globally unique)和應用程序指定(application assigned) (或者你自己爲任何已有策略所寫的擴展) 這些方式來生成標識符。

最後我們還必須在映射文件裏面包括需要持久化屬性的聲明。缺省的情況下,類裏面的屬性都被視爲非持久化的:

<hibernate-mapping> <class name="Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="increment"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> </class></hibernate-mapping>
和id元素類似,property元素的name參數 告訴Hibernate使用哪個getter和setter方法。

爲什麼date屬性的映射包括column參數,但是title卻沒有? 當沒有設定column參數的時候,Hibernate缺省使用屬性名作爲字段(column)名。對於title,這樣工作得很好。 然而,date在多數的數據庫裏,是一個保留關鍵字,所以我們最好把它映射成另外一個名字。

下一件有趣的事情是title屬性缺少一個type參數。 我們聲明並使用在映射文件裏面的type,並不像我們假想的那樣,是Java data type, 同時也不是SQL database type。這些類型被稱作Hibernate mapping types, 它們把數據類型從Java轉換到SQL data types。如果映射的參數沒有設置的話,Hibernate也將嘗試去確定正確的類型轉換和它的映射類型。 在某些情況下這個自動檢測(在Java class上使用反射機制)不會產生你所期待或者 需要的缺省值。這裏有個例子是關於date屬性。Hibernate無法知道這個屬性應該被映射成下面這些類型中的哪一個: SQL date,timestamp,time。 我們通過聲明屬性映射timestamp來表示我們希望保存所有的關於日期和時間的信息。

這個映射文件(mapping file)應該被保存爲Event.hbm.xml,和我們的EventJava 源文件放在同一個目錄下。映射文件的名字可以是任意的,然而hbm.xml已經成爲Hibernate開發者社區的習慣性約定。 現在目錄應該看起來像這樣:

.+lib <Hibernate and third-party libraries>+src Event.java Event.hbm.xml
我們繼續進行Hibernate的主要配置。

2.2.3. Hibernate配置
我們現在已經有了一個持久化類和它的映射文件,是時候配置Hibernate了。在我們做這個之前,我們需要一個數據庫。 HSQL DB,一個java-based內嵌式SQL數據庫(in-memory SQL Database),可以從HSQL DB的網站上下載。 實際上,你僅僅需要下載/lib/目錄中的hsqldb.jar。把這個文件放在開發文件夾的lib/目錄裏面。

在開發目錄下面創建一個叫做data的目錄 - 這個是HSQL DB存儲它的數據文件的地方。

Hibernate是你的程序裏連接數據庫的那個應用層,所以它需要連接用的信息。連接(connection)是通過一個也由我們配置的JDBC連接池(connection pool)。 Hibernate的分發版裏面包括了一些open source的連接池,但是我們已經決定在這個教程裏面使用內嵌式連接池。 如果你希望使用一個產品級的第三方連接池軟件,你必須拷貝所需的庫文件去你的classpath並使用不同的連接池設置。

爲了配置Hibernate,我們可以使用一個簡單的hibernate.properties文件, 或者一個稍微複雜的hibernate.cfg.xml,甚至可以完全使用程序來配置Hibernate。 多數用戶喜歡使用XML配置文件:

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:data/tutorial</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <mapping resource="Event.hbm.xml"/> </session-factory></hibernate-configuration>
注意這個XML配置使用了一個不同的DTD。我們配置Hibernate的SessionFactory- 一個關聯於特定數據庫全局性的工廠(factory)。如果你要使用多個數據庫,通常應該在多個配置文件中使用多個<session-factory> 進行配置(在更早的啓動步驟中進行)。

最開始的4個property元素包含必要的JDBC連接信息。dialectproperty 表明Hibernate應該產生針對特定數據庫語法的SQL語句。hbm2ddl.auto選項將自動生成數據庫表定義(schema)- 直接插入數據庫中。當然這個選項也可以被關閉(通過去除這個選項)或者通過Ant任務SchemaExport來把數據庫表定義導入一個文件中進行優化。 最後,爲持久化類加入映射文件。

把這個文件拷貝到源代碼目錄下面,這樣它就位於classpath的root路徑上。Hibernate在啓動時會自動 在它的根目錄開始尋找名爲hibernate.cfg.xml的配置文件。

2.2.4. 用Ant編譯
在這個教程裏面,我們將用Ant來編譯程序。你必須先安裝Ant-可以從Ant download page 下載它。怎樣安裝Ant不是這個教程的內容,請參考Ant manual。 當你安裝完了Ant,我們就可以開始創建編譯腳本,它的文件名是build.xml,把它直接放在開發目錄下面。

完善Ant
注意Ant的分發版通常功能都是不完整的(就像Ant FAQ裏面說得那樣),所以你常常不得不需要自己動手來完善Ant。 例如:如果你希望在你的build文件裏面使用JUnit功能。爲了讓JUnit任務被激活(這個教程裏面我們並不需要這個任務), 你必須拷貝junit.jar到ANT_HOME/lib目錄下或者刪除ANT_HOME/lib/ant-junit.jar這個插件。

一個基本的build文件看起來像這樣

<project name="hibernate-tutorial" default="compile"> <property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/> <path id="libraries"> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> </path> <target name="clean"> <delete dir="${targetdir}"/> <mkdir dir="${targetdir}"/> </target> <target name="compile" depends="clean, copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" classpathref="libraries"/> </target> <target name="copy-resources"> <copy todir="${targetdir}"> <fileset dir="${sourcedir}"> <exclude name="**/*.java"/> </fileset> </copy> </target></project>
這個將告訴Ant把所有在lib目錄下以.jar結尾的文件加入classpath中用來進行編譯。 它也將把所有的非Java源代碼文件,例如配置和Hibernate映射文件,拷貝到目標目錄下。如果你現在運行Ant, 你將得到以下輸出:

C:/hibernateTutorial/>antBuildfile: build.xmlcopy-resources: [copy] Copying 2 files to C:/hibernateTutorial/bincompile: [javac] Compiling 1 source file to C:/hibernateTutorial/binBUILD SUCCESSFULTotal time: 1 second
2.2.5. 安裝和幫助
是時候來加載和儲存一些Event對象了,但是首先我們不得不完成一些基礎的代碼。 我們必須啓動Hibernate。這個啓動過程包括創建一個全局性的SessoinFactory並把它儲存在一個應用程序容易訪問的地方。 SessionFactory可以創建並打開新的Session。 一個Session代表一個單線程的單元操作,SessionFactory則是一個線程安全的全局對象,只需要創建一次。

我們將創建一個HibernateUtil幫助類(helper class)來負責啓動Hibernate並使 操作Session變得容易。這個幫助類將使用被稱爲ThreadLocal Session 的模式來保證當前的單元操作和當前線程相關聯。讓我們來看一眼它的實現:

import org.hibernate.*;import org.hibernate.cfg.*;public class HibernateUtil { public static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static final ThreadLocal session = new ThreadLocal(); public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // Open a new Session, if this thread has none yet if (s == null) { s = sessionFactory.openSession(); // Store it in the ThreadLocal variable session.set(s); } return s; } public static void closeSession() throws HibernateException { Session s = (Session) session.get(); if (s != null) s.close(); session.set(null); }}
這個類不僅僅在它的靜態初始化過程(僅當加載這個類的時候被JVM執行一次)中產生全局SessionFactory, 同時也有一個ThreadLocal變量來爲當前線程保存Session。不論你何時 調用HibernateUtil.currentSession(),它總是返回同一個線程中的同一個Hibernate單元操作。 而一個HibernateUtil.closeSession()調用將終止當前線程相聯繫的那個單元操作。

在你使用這個幫助類之前,確定你明白Java關於本地線程變量(thread-local variable)的概念。一個功能更加強大的 HibernateUtil幫助類可以在CaveatEmptorhttp://caveatemptor.hibernate.org/找到 -它同時也出現在書:《Hibernate in Action》中。注意當你把Hibernate部署在一個J2EE應用服務器上的時候,這個類不是必須的: 一個Session會自動綁定到當前的JTA事物上,你可以通過JNDI來查找SessionFactory。 如果你使用JBoss AS,Hibernate可以被部署成一個受管理的系統服務(system service)並自動綁定SessionFactory到JNDI上。

把HibernateUtil.java放在開發目錄的源代碼路徑下面,與 Event.java放在一起:

.+lib <Hibernate and third-party libraries>+src Event.java Event.hbm.xml HibernateUtil.java hibernate.cfg.xml+databuild.xml
再次編譯這個程序不應該有問題。最後我們需要配置一個日誌系統 - Hibernate使用通用日誌接口,這允許你在Log4j和 JDK 1.4 logging之間進行選擇。多數開發者喜歡Log4j:從Hibernate的分發版(它在etc/目錄下)拷貝 log4j.properties到你的src目錄,與hibernate.cfg.xml.放在一起。 看一眼配置示例,你可以修改配置如果你希望看到更多的輸出信息。缺省情況下,只有Hibernate的啓動信息會顯示在標準輸出上。

教程的基本框架完成了 - 現在我們可以用Hibernate來做些真正的工作。

2.2.6. 加載並存儲對象
終於,我們可以使用Hibernate來加載和存儲對象了。我們編寫一個帶有main()方法 的EventManager類:

import org.hibernate.Transaction;import org.hibernate.Session;import java.util.Date;public class EventManager { public static void main(String[] args) { EventManager mgr = new EventManager(); if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } HibernateUtil.sessionFactory.close(); }}
我們從命令行讀入一些參數,如果第一個參數是"store",我們創建並儲存一個新的Event:

private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); tx.commit(); HibernateUtil.closeSession();}
我們創建一個新的Event對象並把它傳遞給Hibernate。Hibernate現在負責創建SQL並把 INSERT命令傳給數據庫。在運行它之前,讓我們花一點時間在Session和Transaction的處理代碼上。

每個Session是一個獨立的單元操作。你會對我們有另外一個API:Transaction而感到驚奇。 這暗示一個單元操作可以擁有比一個單獨的數據庫事務更長的生命週期 - 想像在web應用程序中,一個單元操作跨越多個Http request/response循環 (例如一個創建對話框)。根據“應用程序用戶眼中的單元操作”來切割事務是Hibernate的基本設計思想之一。我們調用 一個長生命期的單元操作Application Transaction時,通常包裝幾個更生命期較短的數據庫事務。 爲了簡化問題,在這個教程裏我們使用Session和Transaction之間是1對1關係的粒度(one-to-one granularity)。

Transaction.begin()和commit()都做些什麼?rollback()在哪些情況下會產生錯誤? Hibernate的Transaction API 實際上是可選的, 但是我們通常會爲了便利性和可移植性而使用它。 如果你寧可自己處理數據庫事務(例如,調用session.connection.commit()),通過直接和無管理的JDBC,這樣將把代碼綁定到一個特定的部署環境中去。 通過在Hibernate配置中設置Transaction工廠,你可以把你的持久化層部署在任何地方。 查看第 12 章 事務和併發瞭解更多關於事務處理和劃分的信息。在這個例子中我們也忽略任何異常處理和事務回滾。

爲了第一次運行我們的應用程序,我們必須增加一個可以調用的target到Ant的build文件中。

<target name="run" depends="compile"> <java fork="true" classname="EventManager" classpathref="libraries"> <classpath path="${targetdir}"/> <arg value="${action}"/> </java></target>
action參數的值是在通過命令行調用這個target的時候設置的:

C:/hibernateTutorial/>ant run -Daction=store
你應該會看到,編譯結束以後,Hibernate根據你的配置啓動,併產生一大堆的輸出日誌。在日誌最後你會看到下面這行:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
這是Hibernate執行的INSERT命令,問號代表JDBC的待綁定參數。如果想要看到綁定參數的值或者減少日誌的長度, 檢查你在log4j.properties文件裏的設置。

現在我們想要列出所有已經被存儲的event,所以我們增加一個條件分支選項到main方法中去。

if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date());}else if (args[0].equals("list")) { List events = mgr.listEvents(); for (int i = 0; i < events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()); }}
我們也增加一個新的listEvents()方法:

private List listEvents() { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); List result = session.createQuery("from Event").list(); tx.commit(); session.close(); return result;}
我們在這裏是用一個HQL(Hibernate Query Language-Hibernate查詢語言)查詢語句來從數據庫中 加載所有存在的Event。Hibernate會生成正確的SQL,發送到數據庫並使用查詢到的數據來生成Event對象。 當然你也可以使用HQL來創建更加複雜的查詢。

如果你現在使用命令行參數-Daction=list來運行Ant,你會看到那些至今爲止我們儲存的Event。 如果你是一直一步步的跟隨這個教程進行的,你也許會喫驚這個並不能工作 - 結果永遠爲空。原因是hbm2ddl.auto 打開了一個Hibernate的配置選項:這使得Hibernate會在每次運行的時候重新創建數據庫。通過從配置裏刪除這個選項來禁止它。 運行了幾次store之後,再運行list,你會看到結果出現在列表裏。 另外,自動生成數據庫表並導出在單元測試中是非常有用的。

2.3. 第二部分 - 關聯映射
我們已經映射了一個持久化實體類到一個表上。讓我們在這個基礎上增加一些類之間的關聯性。 首先我們往我們程序裏面增加人(people)的概念,並存儲他們所參與的一個Event列表。 (譯者注:與Event一樣,我們在後面的教程中將直接使用person來表示“人”而不是它的中文翻譯)

2.3.1. 映射Person類
最初的Person類是簡單的:

public class Person { private Long id; private int age; private String firstname; private String lastname; Person() {} // Accessor methods for all properties, private setter for 'id'}
Create a new mapping file called Person.hbm.xml:

<hibernate-mapping> <class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="increment"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> </class></hibernate-mapping>
Finally, add the new mapping to Hibernate's configuration:

<mapping resource="Event.hbm.xml"/> <mapping resource="Person.hbm.xml"/>
我們現在將在這兩個實體類之間創建一個關聯。顯然,person可以參與一系列Event,而Event也有不同的參加者(person)。 設計上面我們需要考慮的問題是關聯的方向(directionality),階數(multiplicity)和集合(collection)的行爲。

2.3.2. 一個單向的Set-based關聯
我們將向Person類增加一組Event。這樣我們可以輕鬆的通過調用aPerson.getEvents() 得到一個Person所參與的Event列表,而不必執行一個顯式的查詢。我們使用一個Java的集合類:一個Set,因爲Set 不允許包括重複的元素而且排序和我們無關。

目前爲止我們設計了一個單向的,在一端有許多值與之對應的關聯,通過Set來實現。 讓我們爲這個在Java類裏編碼並映射這個關聯:

public class Person { private Set events = new HashSet(); public Set getEvents() { return events; } public void setEvents(Set events) { this.events = events; }}
在我們映射這個關聯之前,先考慮這個關聯另外一端。很顯然的,我們可以保持這個關聯是單向的。如果我們希望這個關聯是雙向的, 我們可以在Event裏創建另外一個集合,例如:anEvent.getParticipants()。 這是留給你的一個設計選項,但是從這個討論中我們可以很清楚的瞭解什麼是關聯的階數(multiplicity):在這個關聯的兩端都是“多”。 我們叫這個爲:多對多(many-to-many)關聯。因此,我們使用Hibernate的many-to-many映射:

<class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="increment"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> <set name="events" table="PERSON_EVENT"> <key column="PERSON_ID"/> <many-to-many column="EVENT_ID" class="Event"/> </set></class>
Hibernate支持所有種類的集合映射,<set>是最普遍被使用的。對於多對多(many-to-many)關聯(或者叫n:m實體關係), 需要一個用來儲存關聯的表(association table)。表裏面的每一行代表從一個person到一個event的一個關聯。 表名是由set元素的table屬性值配置的。關聯裏面的標識字段名,person的一端,是 由<key>元素定義,event一端的字段名是由<many-to-many>元素的 column屬性定義的。你也必須告訴Hibernate集合中對象的類(也就是位於這個集合所代表的關聯另外一端的類)。

這個映射的數據庫表定義如下:

_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |__________________| | PERSON | | | | | |_____________| | *EVENT_ID | <--> | *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | |_____________| | FIRSTNAME | | LASTNAME | |_____________|
2.3.3. 使關聯工作
讓我們把一些people和event放到EventManager的一個新方法中:

private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); aPerson.getEvents().add(anEvent); tx.commit(); HibernateUtil.closeSession();}
在加載一個Person和一個Event之後,簡單的使用普通的方法修改集合。 如你所見,沒有顯式的update()或者save(), Hibernate自動檢測到集合已經被修改 並需要保存。這個叫做automatic dirty checking,你也可以嘗試修改任何對象的name或者date的參數。 只要他們處於persistent狀態,也就是被綁定在某個Hibernate Session上(例如:他們 剛剛在一個單元操作從被加載或者保存),Hibernate監視任何改變並在後臺隱式執行SQL。同步內存狀態和數據庫的過程,通常只在 一個單元操作結束的時候發生,這個過程被叫做flushing。

你當然也可以在不同的單元操作裏面加載person和event。或者在一個Session以外修改一個 不是處在持久化(persistent)狀態下的對象(如果該對象以前曾經被持久化,我們稱這個狀態爲脫管(detached))。 在程序裏,看起來像下面這樣:

private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); tx.commit(); HibernateUtil.closeSession(); aPerson.getEvents().add(anEvent); // aPerson is detached Session session2 = HibernateUtil.currentSession(); Transaction tx2 = session.beginTransaction(); session2.update(aPerson); // Reattachment of aPerson tx2.commit(); HibernateUtil.closeSession(); }
對update的調用使一個脫管對象(detached object)重新持久化,你可以說它被綁定到 一個新的單元操作上,所以任何你對它在脫管(detached)狀態下所做的修改都會被保存到數據庫裏。

這個對我們當前的情形不是很有用,但是它是非常重要的概念,你可以把它設計進你自己的程序中。現在,加進一個新的 選項到EventManager的main方法中,並從命令行運行它來完成這個練習。如果你需要一個person和 一個event的標識符 - save()返回它。*******這最後一句看不明白

上面是一個關於兩個同等地位的類間關聯的例子,這是在兩個實體之間。像前面所提到的那樣,也存在其它的特別的類和類型,這些類和類型通常是“次要的”。 其中一些你已經看到過,好像int或者String。我們稱呼這些類爲值類型(value type), 它們的實例依賴(depend)在某個特定的實體上。這些類型的實例沒有自己的身份(identity),也不能在實體間共享 (比如兩個person不能引用同一個firstname對象,即使他們有相同的名字)。當然,value types並不僅僅在JDK中存在 (事實上,在一個Hibernate程序中,所有的JDK類都被視爲值類型),你也可以寫你自己的依賴類,例如Address, MonetaryAmount。

你也可以設計一個值類型的集合(collection of value types),這個在概念上與實體的集合有很大的不同,但是在Java裏面看起來幾乎是一樣的。

2.3.4. 值類型的集合
我們把一個值類型對象的集合加入Person。我們希望保存email地址,所以我們使用String, 而這次的集合類型又是Set:

private Set emailAddresses = new HashSet();public Set getEmailAddresses() { return emailAddresses;}public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses;}
Set的映射

<set name="emailAddresses" table="PERSON_EMAIL_ADDR"> <key column="PERSON_ID"/> <element type="string" column="EMAIL_ADDR"/></set>
比較這次和較早先的映射,差別主要在element部分這次並沒有包括對其它實體類型的引用,而是使用一個元素類型是 String的集合(這裏使用小寫的名字是向你表明它是一個Hibernate的映射類型或者類型轉換器)。 和以前一樣,set的table參數決定用於集合的數據庫表名。key元素 定義了在集合表中使用的外鍵。element元素的column參數定義實際保存String值 的字段名。

看一下修改後的數據庫表定義。

_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | ___________________ |_____________| |__________________| | PERSON | | | | | | | |_____________| | PERSON_EMAIL_ADDR | | *EVENT_ID | <--> | *EVENT_ID | | | |___________________| | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | | *EMAIL_ADDR | |_____________| | FIRSTNAME | |___________________| | LASTNAME | |_____________|
你可以看到集合表(collection table)的主鍵實際上是個複合主鍵,同時使用了2個字段。這也暗示了對於同一個 person不能有重複的email地址,這正是Java裏面使用Set時候所需要的語義(Set裏元素不能重複)。

你現在可以試着把元素加入這個集合,就像我們在之前關聯person和event的那樣。Java裏面的代碼是相同的。

2.3.5. 雙向關聯
下面我們將映射一個雙向關聯(bi-directional association)- 在Java裏面讓person和event可以從關聯的 任何一端訪問另一端。當然,數據庫表定義沒有改變,我們仍然需要多對多(many-to-many)的階數(multiplicity)。一個關係型數據庫要比網絡編程語言 更加靈活,所以它並不需要任何像導航方向(navigation direction)的東西 - 數據可以用任何可能的方式進行查看和獲取。

首先,把一個參與者(person)的集合加入Event類中:

private Set participants = new HashSet();public Set getParticipants() { return participants;}public void setParticipants(Set participants) { this.participants = participants;}
在Event.hbm.xml裏面也映射這個關聯。

<set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="Person"/></set>
如你所見,2個映射文件裏都有通常的set映射。注意key和many-to-many 裏面的字段名在兩個映射文件中是交換的。這裏最重要的不同是Event映射文件裏set元素的 inverse="true"參數。

這個表示Hibernate需要在兩個實體間查找關聯信息的時候,應該使用關聯的另外一端 - Person類。 這將會極大的幫助你理解雙向關聯是如何在我們的兩個實體間創建的。

2.3.6. 使雙向關聯工作
首先,請牢記在心,Hibernate並不影響通常的Java語義。 在單向關聯中,我們是怎樣在一個Person和一個Event之間創建聯繫的? 我們把一個Event的實例加到一個Person類內的Event集合裏。所以,顯然如果我們要讓這個關聯可以雙向工作, 我們需要在另外一端做同樣的事情 - 把Person加到一個Event類內的Person集合中。 這“在關聯的兩端設置聯繫”是絕對必要的而且你永遠不應該忘記做它。

許多開發者通過創建管理關聯的方法來保證正確的設置了關聯的兩端,比如在Person裏:

protected Set getEvents() { return events;}protected void setEvents(Set events) { this.events = events;}public void addToEvent(Event event) { this.getEvents().add(event); event.getParticipants().add(this);}public void removeFromEvent(Event event) { this.getEvents().remove(event); event.getParticipants().remove(this);}
注意現在對於集合的get和set方法的訪問控制級別是protected - 這允許在位於同一個包(package)中的類以及繼承自這個類的子類 可以訪問這些方法,但是禁止其它的直接外部訪問,避免了集合的內容出現混亂。你應該儘可能的在集合所對應的另外一端也這樣做。

inverse映射參數究竟表示什麼呢?對於你和對於Java來說,一個雙向關聯僅僅是在兩端簡單的設置引用。然而僅僅這樣 Hibernate並沒有足夠的信息去正確的產生INSERT和UPDATE語句(以避免違反數據庫約束), 所以Hibernate需要一些幫助來正確的處理雙向關聯。把關聯的一端設置爲inverse將告訴Hibernate忽略關聯的 這一端,把這端看成是另外一端的一個鏡子(mirror)。這就是Hibernate所需的信息,Hibernate用它來處理如何把把 一個數據導航模型映射到關係數據庫表定義。 你僅僅需要記住下面這個直觀的規則:所有的雙向關聯需要有一端被設置爲inverse。在一個一對多(one-to-many)關聯中 它必須是代表多(many)的那端。而在多對多(many-to-many)關聯中,你可以任意選取一端,兩端之間並沒有差別。

2.4. 總結
這個教程覆蓋了關於開發一個簡單的Hibernate應用程序的幾個基礎方面。

如果你已經對Hibernate感到自信,繼續瀏覽開發指南里你感興趣的內容-那些會被問到的問題大多是事務處理 (第 12 章 事務和併發), 抓取(fetch)的效率 (第 20 章 提升性能 ),或者API的使用 (第 11 章 與對象共事)和查詢的特性(第 11.4 節 “查詢”)。

不要忘記去Hibernate的網站查看更多(有針對性的)教程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章