hibernate官方新手教程 (转载)
第一部分 - 第一個Hibernate程序
首先我們將創建一個簡單的控制臺(console-based)Hibernate程序。我們使用內置數據庫(in-memory database) (HSQL DB),所以我們不必安裝不論什么數據庫server。
讓我們如果我們希望有一個小程序能夠保存我們希望關注的事件(Event)和這些事件的信息。 (譯者注:在本教程的后面部分,我們將直接使用Event而不是它的中文翻譯“事件”,以免混淆。)
我們做的第一件事是建立我們的開發文件夾,并把全部須要用到的Java庫文件放進去。 從Hibernate站點的下載頁面下載Hibernate分發版本號。 解壓縮包并把/lib以下的全部庫文件放到我們新的開發文件夾以下的/lib文件夾以下。 看起來就像這樣:
. +libantlr.jarcglib-full.jarasm.jarasm-attrs.jarscommons-collections.jarcommons-logging.jarehcache.jarhibernate3.jarjta.jardom4j.jarlog4j.jarThis 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> +srcEvent.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,這個是很easy的在內存中直接生成數字的方法,多數用于測試(或教程)中。 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在多數的數據庫里,是一個保留keyword,所以我們最好把它映射成另外一個名字。
下一件有趣的事情是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> +srcEvent.javaEvent.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/>ant Buildfile: build.xmlcopy-resources:[copy] Copying 2 files to C:/hibernateTutorial/bincompile:[javac] Compiling 1 source file to C:/hibernateTutorial/binBUILD SUCCESSFUL Total time: 1 second2.2.5. 安裝和幫助
是時候來載入和儲存一些Event對象了,可是首先我們不得不完畢一些基礎的代碼。 我們必須啟動Hibernate。這個啟動過程包含創建一個全局性的SessoinFactory并把它儲存在一個應用程序easy訪問的地方。 SessionFactory能夠創建并打開新的Session。 一個Session代表一個單線程的單元操作,SessionFactory則是一個線程安全的全局對象,僅僅須要創建一次。
我們將創建一個HibernateUtil幫助類(helper class)來負責啟動Hibernate并使 操作Session變得easy。這個幫助類將使用被稱為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.xmlsessionFactory = new Configuration().configure().buildSessionFactory();} catch (Throwable ex) {// Make sure you log the exception, as it might be swallowedSystem.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 yetif (s == null) {s = sessionFactory.openSession();// Store it in the ThreadLocal variablesession.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應用server上的時候,這個類不是必須的: 一個Session會自己主動綁定到當前的JTA事物上,你能夠通過JNDI來查找SessionFactory。 假設你使用JBoss AS,Hibernate能夠被部署成一個受管理的系統服務(system service)并自己主動綁定SessionFactory到JNDI上。
把HibernateUtil.java放在開發文件夾的源碼路徑以下,與 Event.java放在一起:
. +lib<Hibernate and third-party libraries> +srcEvent.javaEvent.hbm.xmlHibernateUtil.javahibernate.cfg.xml +data build.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 detachedSession session2 = HibernateUtil.currentSession();Transaction tx2 = session.beginTransaction();session2.update(aPerson); // Reattachment of aPersontx2.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的站點查看很多其它(有針對性的)教程
?轉載于:https://www.cnblogs.com/zfyouxi/p/4074668.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的hibernate官方新手教程 (转载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hdu 5092 线裁剪(纵向连线最小和
- 下一篇: 共享内存生产者消费者模型