第 1 章 Hibernate入門(mén)
1.1. 前言
本章是面向Hibernate初學(xué)者的一個(gè)入門(mén)教程。我們從一個(gè)使用駐留內存式(in-memory)數據庫的簡(jiǎn)單命令行應用程序開(kāi)始, 用易于理解的方式逐步開(kāi)發(fā)。
本章面向Hibernate初學(xué)者,但需要Java和SQL知識。它是在Michael Goegl所寫(xiě)的指南的基礎上完成的。在這里,我們稱(chēng)第三方庫文件是指JDK 1.4和5.0。若使用JDK1.3,你可能需要其它的庫文件。
本章的源代碼已包含在發(fā)布包中,位于doc/reference/tutorial/目錄下。
1.2. 第一部分 - 第一個(gè)Hibernate應用程序
首先我們將創(chuàng )建一個(gè)簡(jiǎn)單的基于控制臺的(console-based)Hibernate應用程序。由于我們使用Java數據庫(HSQL DB),所以不必安裝任何數據庫服務(wù)器。
假設我們希望有一個(gè)小應用程序可以保存我們希望參加的活動(dòng)(events)和這些活動(dòng)主辦方的相關(guān)信息。 (譯者注:在本教程的后面部分,我們將直接使用event而不是它的中文翻譯?活動(dòng)?,以免混淆。)
我們所做的第一件事就是創(chuàng )建我們的開(kāi)發(fā)目錄,并且把所有需要用到的Java庫文件放進(jìn)去。解壓縮從Hibernate網(wǎng)站下載的Hibernate發(fā)布包,并把/lib目錄下所有需要的庫文件拷到我們新建開(kāi)發(fā)目錄下的/lib目錄下??雌饋?lái)就像這樣:
.
+lib
antlr.jar
cglib.jar
asm.jar
asm-attrs.jars
commons-collections.jar
commons-logging.jar
ehcache.jar
hibernate3.jar
jta.jar
dom4j.jar
log4j.jar
到編寫(xiě)本文時(shí)為止,這些是Hibernate運行所需要的最小庫文件集合(注意我們也拷貝了 Hibernate3.jar,這個(gè)是最主要的文件)。你正使用的Hibernate版本可能需要比這更多或少一些的庫文件。請參見(jiàn)發(fā)布包中的lib/目錄下的README.txt,以獲取更多關(guān)于所需和可選的第三方庫文件信息(事實(shí)上,Log4j并不是必須的庫文件,但被許多開(kāi)發(fā)者所喜歡)。
接下來(lái)我們創(chuàng )建一個(gè)類(lèi),用來(lái)代表那些我們希望儲存在數據庫里的event。
1.2.1. 第一個(gè)class
我們的第一個(gè)持久化類(lèi)是一個(gè)帶有一些屬性(property)的簡(jiǎn)單JavaBean類(lèi):
package events;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public 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) { <hibernate-mapping> <class name="events.Event" table="EVENTS"> </class> </hibernate-mapping> <class name="events.Event" table="EVENTS"> </hibernate-mapping> <class name="events.Event" table="EVENTS"> </hibernate-mapping> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <!-- JDBC connection pool (use the built-in) --> <!-- SQL dialect --> <!-- Enable Hibernate‘s automatic session context management --> <!-- Disable the second-level cache --> <!-- Echo all executed SQL to stdout --> <!-- Drop and re-create the database schema on startup --> <mapping resource="events/Event.hbm.xml"/> </session-factory> </hibernate-configuration> <property name="sourcedir" value="${basedir}/src"/> <path id="libraries"> <target name="clean"> <target name="compile" depends="clean, copy-resources"> <target name="copy-resources"> </project> copy-resources: compile: BUILD SUCCESSFUL import org.hibernate.*; public class HibernateUtil { private static final SessionFactory sessionFactory; static { public static SessionFactory getSessionFactory() { } import java.util.Date; import util.HibernateUtil; public class EventManager { public static void main(String[] args) { if (args[0].equals("store")) { HibernateUtil.getSessionFactory().close(); private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Event theEvent = new Event(); session.save(theEvent); session.getTransaction().commit(); } Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List result = session.createQuery("from Event").list(); session.getTransaction().commit(); return result; public class Person { private Long id; public Person() {} // Accessor methods for all properties, private setter for ‘id‘ } <class name="events.Person" table="PERSON"> </hibernate-mapping> private Set events = new HashSet(); public Set getEvents() { public void setEvents(Set events) { <set name="events" table="PERSON_EVENT"> </class> Session session = HibernateUtil.getSessionFactory().getCurrentSession(); Person aPerson = (Person) session.load(Person.class, personId); aPerson.getEvents().add(anEvent); session.getTransaction().commit(); Session session = HibernateUtil.getSessionFactory().getCurrentSession(); Person aPerson = (Person) session Event anEvent = (Event) session.load(Event.class, eventId); session.getTransaction().commit(); // End of first unit of work aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached // Begin second unit of work Session session2 = HibernateUtil.getSessionFactory().getCurrentSession(); session2.update(aPerson); // Reattachment of aPerson session2.getTransaction().commit(); public Set getEmailAddresses() { public void setEmailAddresses(Set emailAddresses) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); Person aPerson = (Person) session.load(Person.class, personId); // The getEmailAddresses() might trigger a lazy load of the collection session.getTransaction().commit(); public Set getParticipants() { public void setParticipants(Set participants) { protected void setEvents(Set events) { public void addToEvent(Event event) { public void removeFromEvent(Event event) { // Imports public class EventManagerServlet extends HttpServlet { private final SimpleDateFormat dateFormatter = // Servlet code try { // Process request and render page... // End unit of work } catch (Exception ex) { } // Handle actions String eventTitle = request.getParameter("eventTitle"); if ( "".equals(eventTitle) || "".equals(eventDate) ) { // Print page // Write HTML footer HibernateUtil.getSessionFactory() <classes dir="${targetdir}"/> <servlet> <servlet-mapping> <hibernate-configuration> <!-- 以/jndi/name綁定到JNDI的SessionFactory實(shí)例 --> <!-- 屬性 --> <!-- 映射定義文件 --> <!-- 緩存設置 --> </session-factory> </hibernate-configuration> <mbean code="org.hibernate.jmx.HibernateService" <!-- 必須的服務(wù) --> <!-- 將Hibernate服務(wù)綁定到JNDI --> <!-- 數據源設置 --> <!-- 事務(wù)集成 --> <!-- 抓取選項 --> <!-- 二級緩存 --> <!-- 日志 --> <!-- 映射定義文件 --> </mbean> </server> public class Cat { private Date birthdate; private Cat mother; private void setId(Long id) { void setBirthdate(Date date) { void setWeight(float weight) { public Color getColor() { void setSex(char sex) { void setLitterId(int id) { void setMother(Cat mother) { public class DomesticCat extends Cat { public String getName() { ... final Cat cat = (Cat) other; if ( !cat.getLitterId().equals( getLitterId() ) ) return false; return true; public int hashCode() { } <class entity-name="Customer"> <id name="id" <property name="name" <property name="address" <many-to-one name="organization" <bag name="orders" </class> // Create a customer // Create an organization // Link both // Save both tx.commit(); // Create a customer <id name="id" type="long" column="ID"> <!-- other properties --> private static final class CustomMapInstantiator <hibernate-mapping package="eg"> <class name="Cat" <discriminator column="subclass" <property name="weight"/> <property name="birthdate" <property name="color" <property name="sex" <property name="litterId" <many-to-one name="mother" <set name="kittens" <subclass name="DomesticCat" <property name="name" </subclass> </class> <class name="Dog"> </hibernate-mapping> (18) check (可選): 這是一個(gè)SQL表達式, 用于為自動(dòng)生成的schema添加多行(multi-row)約束檢查。 <generator class="generatorClass"/> <key-property name="propertyName" type="typename" column="column_name"/> 注意,<timestamp> 和<version type="timestamp">是等價(jià)的。并且<timestamp source="db">和<version type="dbtimestamp">是等價(jià)的。 index="index_name" embed-xml="true|false" embed-xml="true|false" <property .... /> <key .... > <property .... /> <hibernate-mapping package="eg"> <class name="Cat" table="CATS"> <class name="eg.Dog"> </hibernate-mapping> <property .... /> <id name="id" column="PERSON_ID">...</id> <join table="ADDRESS"> <class name="Contract" table="ContractHistory" /** /* /** /** /** /** @Id; String firstName; @Transient @Embedded @OneToMany(cascade=CascadeType.ALL) // Getter/setter and business methods <key .... /> <map name="holidays" sort="my.custom.HolidayComparator"> <map name="holidays" order-by="hol_date, hol_name"> <class name="Item"> <!-- inverse end --> session.persist(item); // The relationship won‘‘t be saved! <class name="Child"> <class name="Child"> <class name="Child"> public class Parent { public long getId() { return id; } private Set getChildren() { return children; } .... <class name="Parent"> <class name="Child"> </hibernate-mapping> <class name="Parent"> <class name="Child"> </hibernate-mapping> <class name="Parent"> <class name="Child"> </hibernate-mapping> <class name="Parent"> <class name="Child"> </hibernate-mapping> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="Address"> <class name="CashPayment" table="CASH_PAYMENT"> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> List mothers = session.createQuery( List kittens = session.createQuery( Cat mother = (Cat) session.createQuery( Query mothersWithKittens = (Cat) session.createQuery( while ( kittensAndMothers.hasNext() ) { // find the first name on each page of an alphabetical list of cats by name // Now get the first page of cats } // in a higher layer of the application // later, in a new session // in a higher tier of the application // later, in a new session //reconcile with a second database Cat izi = (Cat) sess.load(Cat.class, id); // might return stale data // change to izi is not flushed! Object[] propertyValues = catMeta.getPropertyValues(fritz); // get a Map of all properties which are not collections or associations // do some work tx.commit(); // do some work factory.getCurrentSession().getTransaction().commit(); // do some work tx.commit(); tx.begin(); // Do some work on Session bound to transaction tx.commit(); // do some work 在CMT/EJB中甚至會(huì )自動(dòng)rollback,因為假若有未捕獲的RuntimeException從session bean方法中拋出,這就會(huì )通知容器把全局事務(wù)回滾。這就意味著(zhù),在BMT或者CMT中,你根本就不需要使用Hibernate Transaction API ,你自動(dòng)得到了綁定到事務(wù)的?當前?Session。 // do some work sess.getTransaction().commit() int oldVersion = foo.getVersion(); t.commit(); foo.setProperty("bar"); session.flush(); // Only for last transaction in conversation import java.io.Serializable; import org.hibernate.EmptyInterceptor; public class AuditInterceptor extends EmptyInterceptor { private int updates; public void onDelete(Object entity, public boolean onFlushDirty(Object entity, if ( entity instanceof Auditable ) { public boolean onLoad(Object entity, public boolean onSave(Object entity, if ( entity instanceof Auditable ) { public void afterTransactionCompletion(Transaction tx) { } String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; String hqlDelete = "delete Customer c where c.name = :oldName"; String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; select count(distinct cat.name), count(cat) from Cat cat from Cat as cat where cat.mate.id = 69 <sql-query name="personsWith" resultset-ref="personAddress"> update PERSON return SQL%ROWCOUNT; END updatePerson; <filter-param name="myFilterParam" type="string"/> </filter-def> ... <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> </class> <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> </set> <filter-param name="asOfDate" type="date"/> </filter-def> <class name="Employee" ...> ... <many-to-one name="department" column="dept_id" class="Department"/> <property name="effectiveStartDate" type="date" column="eff_start_dt"/> <property name="effectiveEndDate" type="date" column="eff_end_dt"/> ... <!-- Note that this assumes non-terminal records have an eff_end_dt set to a max db date for simplicity-sake 注意,為了簡(jiǎn)單起見(jiàn),此處假設雇用關(guān)系生效期尚未結束的記錄的eff_end_dt字段的值等于數據庫最大的日期 --> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </class> <class name="Department" ...> ... <set name="employees" lazy="true"> <key column="dept_id"/> <one-to-many class="Employee"/> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </set> </class> session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date()); List results = session.createQuery("from Employee as e where e.salary > :targetSalary") .setLong("targetSalary", new Long(1000000)) .list(); 在上面的HQL中,雖然我們僅僅顯式的使用了一個(gè)薪水條件,但因為啟用了過(guò)濾器,查詢(xún)將僅返回那些目前雇用 關(guān)系處于生效期的,并且薪水高于一百萬(wàn)美刀的雇員的數據。 List results = dom4jSession tx.commit(); Element cust = (Element) dom4jSession.get("Customer", customerId); tx.commit(); tx.commit(); Integer accessLevel = (Integer) permissions.get("accounts"); // Error! <instrument verbose="true"> StatisticsService stats = new StatisticsService(); // MBean implementation StatisticsService stats = new StatisticsService(); // MBean implementation double queryCacheHitCount = stats.getQueryCacheHitCount(); log.info("Query Hit ratio:" + queryCacheHitRatio); EntityStatistics entityStats = import java.util.List; public class Blog { public Long getId() { import java.text.DateFormat; public class BlogItem { public Blog getBlog() {
this.title = title;
}
}
你可以看到這個(gè)類(lèi)對屬性的存取方法(getter and setter method)使用了標準JavaBean命名約定,同時(shí)把類(lèi)屬性(field)的訪(fǎng)問(wèn)級別設成私有的(private)。這是推薦的設計,但并不是必須的。Hibernate也可以直接訪(fǎng)問(wèn)這些field,而使用訪(fǎng)問(wèn)方法(accessor method)的好處是提供了重構時(shí)的健壯性(robustness)。為了通過(guò)反射機制(Reflection)來(lái)實(shí)例化這個(gè)類(lèi)的對象,我們需要提供一個(gè)無(wú)參的構造器(no-argument constructor)。
對一特定的event, id 屬性持有唯一的標識符(identifier)的值。如果我們希望使用Hibernate提供的所有特性,那么所有的持久化實(shí)體(persistent entity)類(lèi)(這里也包括一些次要依賴(lài)類(lèi))都需要一個(gè)這樣的標識符屬性。而事實(shí)上,大多數應用程序(特別是web應用程序)都需要通過(guò)標識符來(lái)區別對象,所以你應該考慮使用標識符屬性而不是把它當作一種限制。然而,我們通常不會(huì )操作對象的標識(identity),因此它的setter方法的訪(fǎng)問(wèn)級別應該聲明private。這樣當對象被保存的時(shí)候,只有Hibernate可以為它分配標識符值。你可看到Hibernate可以直接訪(fǎng)問(wèn)public,private和protected的訪(fǎng)問(wèn)方法和field。所以選擇哪種方式完全取決于你,你可以使你的選擇與你的應用程序設計相吻合。
所有的持久化類(lèi)(persistent classes)都要求有無(wú)參的構造器,因為Hibernate必須使用Java反射機制來(lái)為你創(chuàng )建對象。構造器(constructor)的訪(fǎng)問(wèn)級別可以是private,然而當生成運行時(shí)代理(runtime proxy)的時(shí)候則要求使用至少是package 級別的訪(fǎng)問(wèn)控制,這樣在沒(méi)有字節碼指令(bytecode instrumentation)的情況下,從持久化類(lèi)里獲取數據會(huì )更有效率。
把這個(gè)Java源代碼文件放到開(kāi)發(fā)目錄下的src目錄里,注意包位置要正確。 現在這個(gè)目錄看起來(lái)應該像這樣:
.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
下一步,我們把這個(gè)持久化類(lèi)的信息告訴Hibernate。
1.2.2. 映射文件
Hibernate需要知道怎樣去加載(load)和存儲(store)持久化類(lèi)的對象。這正是Hibernate映射文件發(fā)揮作用的地方。映射文件告訴Hibernate它,應該訪(fǎng)問(wèn)數據庫(database)里面的哪個(gè)表(table)及應該使用表里面的哪些字段(column)。
一個(gè)映射文件的基本結構看起來(lái)像這樣:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"
[...]
</hibernate-mapping>
注意Hibernate的DTD是非常復雜的。你的編輯器或者IDE里使用它來(lái)自動(dòng)完成那些用來(lái)映射的XML元素(element)和屬性(attribute)。你也可以在文本編輯器里打開(kāi)DTD-這是最簡(jiǎn)單的方式來(lái)概覽所有的元素和attribute,并查看它們的缺省值以及注釋。注意Hibernate不會(huì )從web加載DTD文件,但它會(huì )首先在應用程序的classpath中查找。DTD文件已包括在hibernate3.jar里,同時(shí)也在Hibernate發(fā)布包的src/目錄下。
為縮短代碼長(cháng)度,在以后的例子里我們會(huì )省略DTD的聲明。當然,在實(shí)際的應用程序中,DTD聲明是必須的。
在hibernate-mapping標簽(tag)之間, 含有一個(gè)class元素。所有的持久化實(shí)體類(lèi)(再次聲明,或許接下來(lái)會(huì )有依賴(lài)類(lèi),就是那些次要的實(shí)體)都需要一個(gè)這樣的映射,來(lái)把類(lèi)對象映射到SQL數據庫里的表。
<hibernate-mapping>
到目前為止,我們告訴了Hibernate怎樣把Events類(lèi)的對象持久化到數據庫的EVENTS表里,以及怎樣從EVENTS表加載到Events類(lèi)的對象。每個(gè)實(shí)例對應著(zhù)數據庫表中的一行?,F在我們將繼續討論有關(guān)唯一標識符屬性到數據庫表的映射。另外,由于我們不關(guān)心怎樣處理這個(gè)標識符,我們就配置由Hibernate的標識符生成策略來(lái)產(chǎn)生代理主鍵字段。
<hibernate-mapping>
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
</class>
id元素是標識符屬性的聲明,name="id" 聲明了Java屬性的名字 - Hibernate會(huì )使用getId()和setId()來(lái)訪(fǎng)問(wèn)它。 column屬性則告訴Hibernate, 我們使用EVENTS表的哪個(gè)字段作為主鍵。嵌套的generator元素指定了標識符生成策略,在這里我們指定native,它根據已配置的數據庫(方言)自動(dòng)選擇最佳的標識符生成策略。Hibernate支持由數據庫生成,全局唯一性(globally unique)和應用程序指定(或者你自己為任何已有策略所寫(xiě)的擴展)這些策略來(lái)生成標識符。
最后我們在映射文件里面包含需要持久化屬性的聲明。默認情況下,類(lèi)里面的屬性都被視為非持久化的:
<hibernate-mapping>
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
和id元素一樣,property元素的name屬性告訴Hibernate使用哪個(gè)getter和setter方法。在此例中,Hibernate會(huì )尋找getDate()/setDate(), 以及getTitle()/setTitle()。
為什么date屬性的映射含有column attribute,而title卻沒(méi)有?當沒(méi)有設定column attribute 的時(shí)候,Hibernate缺省地使用JavaBean的屬性名作為字段名。對于title,這樣工作得很好。然而,date在多數的數據庫里,是一個(gè)保留關(guān)鍵字,所以我們最好把它映射成一個(gè)不同的名字。
另一有趣的事情是title屬性缺少一個(gè)type attribute。我們在映射文件里聲明并使用的類(lèi)型,卻不是我們期望的那樣,是Java數據類(lèi)型,同時(shí)也不是SQL數據庫的數據類(lèi)型。這些類(lèi)型就是所謂的Hibernate 映射類(lèi)型(mapping types),它們能把Java數據類(lèi)型轉換到SQL數據類(lèi)型,反之亦然。再次重申,如果在映射文件中沒(méi)有設置type屬性的話(huà),Hibernate會(huì )自己試著(zhù)去確定正確的轉換類(lèi)型和它的映射類(lèi)型。在某些情況下這個(gè)自動(dòng)檢測機制(在Java 類(lèi)上使用反射機制)不會(huì )產(chǎn)生你所期待或需要的缺省值。date屬性就是個(gè)很好的例子,Hibernate無(wú)法知道這個(gè)屬性(java.util.Date類(lèi)型的)應該被映射成:SQL date,或timestamp,還是time 字段。在此例中,把這個(gè)屬性映射成timestamp 轉換器,這樣我們預留了日期和時(shí)間的全部信息。
應該把這個(gè)映射文件保存為Event.hbm.xml,且就在EventJava類(lèi)的源文件目錄下。映射文件可隨意地命名,但hbm.xml的后綴已成為Hibernate開(kāi)發(fā)者社區的約定?,F在目錄結構看起來(lái)應該像這樣:
.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
Event.hbm.xml
我們繼續進(jìn)行Hibernate的主要配置。
1.2.3. Hibernate配置
現在我們已經(jīng)有了一個(gè)持久化類(lèi)和它的映射文件,該是配置Hibernate的時(shí)候了。在此之前,我們需要一個(gè)數據庫。 HSQL DB是種基于Java 的SQL數據庫管理系統(DBMS),可以從HSQL DB的網(wǎng)站上下載。實(shí)際上,你只需下載的包中的hsqldb.jar文件,并把這個(gè)文件放在開(kāi)發(fā)文件夾的lib/目錄下即可。
在開(kāi)發(fā)的根目錄下創(chuàng )建一個(gè)data目錄 - 這是HSQL DB存儲數據文件的地方。此時(shí)在data目錄中運行java -classpath lib/hsqldb.jar org.hsqldb.Server就可啟動(dòng)數據庫。你可以在log中看到它的啟動(dòng),及綁定到TCP/IP套結字,這正是我們的應用程序稍后會(huì )連接的地方。如果你希望在本例中運行一個(gè)全新的數據庫,就在窗口中按下CTRL + C來(lái)關(guān)閉HSQL數據庫,并刪除data/目錄下的所有文件,再重新啟動(dòng)HSQL數據庫。
Hibernate是你的應用程序里連接數據庫的那層,所以它需要連接用的信息。連接(connection)是通過(guò)一個(gè)也由我們配置的JDBC連接池(connection pool)來(lái)完成的。Hibernate的發(fā)布包里包含了許多開(kāi)源的(open source)連接池,但在我們例子中使用Hibernate內置的連接池。注意,如果你希望使用一個(gè)產(chǎn)品級(production-quality)的第三方連接池軟件,你必須拷貝所需的庫文件到你的classpath下,并使用不同的連接池設置。
為了保存Hibernate的配置,我們可以使用一個(gè)簡(jiǎn)單的hibernate.properties文件,或者一個(gè)稍微復雜的hibernate.cfg.xml,甚至可以完全使用程序來(lái)配置Hibernate。多數用戶(hù)更喜歡使用XML配置文件:
<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
注意這個(gè)XML配置使用了一個(gè)不同的DTD。在這里,我們配置了Hibernate的SessionFactory-一個(gè)關(guān)聯(lián)于特定數據庫全局的工廠(chǎng)(factory)。如果你要使用多個(gè)數據庫,就要用多個(gè)的<session-factory>,通常把它們放在多個(gè)配置文件中(為了更容易啟動(dòng))。
最開(kāi)始的4個(gè)property元素包含必要的JDBC連接信息。方言(dialect)的property元素指明Hibernate 生成的特定SQL變量。你很快會(huì )看到,Hibernate對持久化上下文的自動(dòng)session管理就會(huì )派上用場(chǎng)。 打開(kāi)hbm2ddl.auto選項將自動(dòng)生成數據庫模式(schema)- 直接加入數據庫中。當然這個(gè)選項也可以被關(guān)閉(通過(guò)去除這個(gè)配置選項)或者通過(guò)Ant任務(wù)SchemaExport的幫助來(lái)把數據庫schema重定向到文件中。最后,在配置中為持久化類(lèi)加入映射文件。
把這個(gè)文件拷貝到源代碼目錄下面,這樣它就位于classpath的根目錄的最后。Hibernate在啟動(dòng)時(shí)會(huì )自動(dòng)在classpath的根目錄查找名為hibernate.cfg.xml的配置文件。
1.2.4. 用Ant構建
現在我們用Ant來(lái)構建應用程序。你必須先安裝Ant-可以從Ant 下載頁(yè)面得到它。怎樣安裝Ant就不在這里介紹了,請參考Ant 用戶(hù)手冊。當你安裝完了Ant,就可以開(kāi)始創(chuàng )建build.xml文件,把它直接放在開(kāi)發(fā)目錄下面。
一個(gè)簡(jiǎn)單的build文件看起來(lái)像這樣:
<project name="hibernate-tutorial" default="compile">
<property name="targetdir" value="${basedir}/bin"/>
<property name="librarydir" value="${basedir}/lib"/>
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>
</path>
<delete dir="${targetdir}"/>
<mkdir dir="${targetdir}"/>
</target>
<javac srcdir="${sourcedir}"
destdir="${targetdir}"
classpathref="libraries"/>
</target>
<copy todir="${targetdir}">
<fileset dir="${sourcedir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
這將告訴Ant把所有在lib目錄下以.jar結尾的文件拷貝到classpath中以供編譯之用。它也把所有的非Java源代碼文件,例如配置和Hibernate映射文件,拷貝到目標目錄。如果你現在運行Ant,會(huì )得到以下輸出:
C:\hibernateTutorial\>ant
Buildfile: build.xml
[copy] Copying 2 files to C:\hibernateTutorial\bin
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
Total time: 1 second
1.2.5. 啟動(dòng)和輔助類(lèi)
是時(shí)候來(lái)加載和儲存一些Event對象了,但首先我們得編寫(xiě)一些基礎的代碼以完成設置。我們必須啟動(dòng)Hibernate,此過(guò)程包括創(chuàng )建一個(gè)全局的SessoinFactory,并把它儲存在應用程序代碼容易訪(fǎng)問(wèn)的地方。SessionFactory可以創(chuàng )建并打開(kāi)新的Session。一個(gè)Session代表一個(gè)單線(xiàn)程的單元操作,SessionFactory則是個(gè)線(xiàn)程安全的全局對象,只需要被實(shí)例化一次。
我們將創(chuàng )建一個(gè)HibernateUtil輔助類(lèi)(helper class)來(lái)負責啟動(dòng)Hibernate和更方便地操作SessionFactory。讓我們來(lái)看一下它的實(shí)現:
package util;
import org.hibernate.cfg.*;
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);
}
}
return sessionFactory;
}
這個(gè)類(lèi)不但在它的靜態(tài)初始化過(guò)程(僅當加載這個(gè)類(lèi)的時(shí)候被JVM執行一次)中產(chǎn)生全局的SessionFactory,而且隱藏了它使用了靜態(tài)singleton的事實(shí)。它也可能在應用程序服務(wù)器中的JNDI查找SessionFactory。
如果你在配置文件中給SessionFactory一個(gè)名字,在SessionFactory創(chuàng )建后,Hibernate會(huì )試著(zhù)把它綁定到JNDI。要完全避免這樣的代碼,你也可以使用JMX部署,讓具有JMX能力的容器來(lái)實(shí)例化HibernateService并把它綁定到JNDI。這些高級可選項在后面的章節中會(huì )討論到。
把HibernateUtil.java放在開(kāi)發(fā)目錄的源代碼路徑下,與放events的包并列:
.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
Event.hbm.xml
+util
HibernateUtil.java
hibernate.cfg.xml
+data
build.xml
再次編譯這個(gè)應用程序應該不會(huì )有問(wèn)題。最后我們需要配置一個(gè)日志(logging)系統 - Hibernate使用通用日志接口,允許你在Log4j和JDK 1.4 日志之間進(jìn)行選擇。多數開(kāi)發(fā)者更喜歡Log4j:從Hibernate的發(fā)布包中(它在etc/目錄下)拷貝log4j.properties到你的src目錄,與hibernate.cfg.xml.放在一起??匆幌屡渲檬纠?,如果你希望看到更加詳細的輸出信息,你可以修改配置。默認情況下,只有Hibernate的啟動(dòng)信息才會(huì )顯示在標準輸出上。
示例的基本框架完成了 - 現在我們可以用Hibernate來(lái)做些真正的工作。
1.2.6. 加載并存儲對象
我們終于可以使用Hibernate來(lái)加載和存儲對象了,編寫(xiě)一個(gè)帶有main()方法的EventManager類(lèi):
package events;
import org.hibernate.Session;
EventManager mgr = new EventManager();
mgr.createAndStoreEvent("My Event", new Date());
}
}
theEvent.setTitle(title);
theEvent.setDate(theDate);
}
我們創(chuàng )建了個(gè)新的Event對象并把它傳遞給Hibernate?,F在Hibernate負責與SQL打交道,并把INSERT命令傳給數據庫。在運行之前,讓我們看一下處理Session和Transaction的代碼。
一個(gè)Session就是個(gè)單一的工作單元。我們暫時(shí)讓事情簡(jiǎn)單一些,并假設HibernateSession和數據庫事務(wù)是一一對應的。為了讓我們的代碼從底層的事務(wù)系統中脫離出來(lái)(此例中是JDBC,但也可能是JTA),我們使用Hibernate Session中的Transaction API。
sessionFactory.getCurrentSession()是干什么的呢?首先,只要你持有SessionFactory(幸虧我們有HibernateUtil,可以隨時(shí)獲得),大可在任何時(shí)候、任何地點(diǎn)調用這個(gè)方法。getCurrentSession()方法總會(huì )返回?當前的?工作單元。記得我們在hibernate.cfg.xml中把這一配置選項調整為"thread"了嗎?因此,當前工作單元的范圍就是當前執行我們應用程序的Java線(xiàn)程。但是,這并非總是正確的。 Session在第一次被使用的時(shí)候,或者第一次調用getCurrentSession()的時(shí)候,其生命周期就開(kāi)始。然后它被Hibernate綁定到當前線(xiàn)程。當事務(wù)結束的時(shí)候,不管是提交還是回滾,Hibernate也會(huì )把Session從當前線(xiàn)程剝離,并且關(guān)閉它。假若你再次調用getCurrentSession(),你會(huì )得到一個(gè)新的Session,并且開(kāi)始一個(gè)新的工作單元。這種線(xiàn)程綁定(thread-bound)的編程模型(model)是使用Hibernate的最廣泛的方式。
關(guān)于事務(wù)處理及事務(wù)邊界界定的詳細信息,請參看第 11 章 事務(wù)和并發(fā)。在上面的例子中,我們也忽略了所有的錯誤與回滾的處理。
為第一次運行我們的程序,我們得在A(yíng)nt的build文件中增加一個(gè)可以調用得到的target。
<target name="run" depends="compile">
<java fork="true" classname="events.EventManager" classpathref="libraries">
<classpath path="${targetdir}"/>
<arg value="${action}"/>
</java>
</target>
action參數(argument)的值是通過(guò)命令行調用這個(gè)target的時(shí)候設置的:
C:\hibernateTutorial\>ant run -Daction=store
你應該會(huì )看到,編譯以后,Hibernate根據你的配置啟動(dòng),并產(chǎn)生一大堆的輸出日志。在日志最后你會(huì )看到下面這行:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
這是Hibernate執行的INSERT命令,問(wèn)號代表JDBC的綁定參數。如果想要看到綁定參數的值或者減少日志的長(cháng)度,就要調整你在log4j.properties文件里的設置。
我們想要列出所有已經(jīng)被存儲的events,就要增加一個(gè)條件分支選項到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());
}
}
我們也增加一個(gè)新的listEvents()方法:
private List listEvents() {
}
我們在這里是用一個(gè)HQL(Hibernate Query Language-Hibernate查詢(xún)語(yǔ)言)查詢(xún)語(yǔ)句來(lái)從數據庫中加載所有存在的Event對象。Hibernate會(huì )生成適當的SQL,把它發(fā)送到數據庫,并操作從查詢(xún)得到數據的Event對象。當然,你可以使用HQL來(lái)創(chuàng )建更加復雜的查詢(xún)。
現在,根據以下步驟來(lái)執行并測試以上各項:
? 運行ant run -Daction=store來(lái)保存一些內容到數據庫。當然,先得用hbm2ddl來(lái)生成數據庫schema。
? 現在把hibernate.cfg.xml文件中hbm2ddl屬性注釋掉,這樣我們就取消了在啟動(dòng)時(shí)用hbm2ddl來(lái)生成數據庫schema。通常只有在不斷重復進(jìn)行單元測試的時(shí)候才需要打開(kāi)它,但再次運行hbm2ddl會(huì )把你保存的一切都刪掉(drop)——create配置的真實(shí)含義是:?在創(chuàng )建SessionFactory的時(shí)候,從schema 中drop 掉所有的表,再重新創(chuàng )建它們?。
如果你現在使用命令行參數-Daction=list運行Ant,你會(huì )看到那些至今為止我們所儲存的events。當然,你也可以多調用幾次store以保存更多的envents。
注意,很多Hibernate新手在這一步會(huì )失敗,我們不時(shí)看到關(guān)于Table not found錯誤信息的提問(wèn)。但是,只要你根據上面描述的步驟來(lái)執行,就不會(huì )有這個(gè)問(wèn)題,因為hbm2ddl會(huì )在第一次運行的時(shí)候創(chuàng )建數據庫schema,后繼的應用程序重起后還能繼續使用這個(gè)schema。假若你修改了映射,或者修改了數據庫schema,你必須把hbm2ddl重新打開(kāi)一次。
1.3. 第二部分 - 關(guān)聯(lián)映射
我們已經(jīng)映射了一個(gè)持久化實(shí)體類(lèi)到表上。讓我們在這個(gè)基礎上增加一些類(lèi)之間的關(guān)聯(lián)。首先我們往應用程序里增加人(people)的概念,并存儲他們所參與的一個(gè)Event列表。(譯者注:與Event一樣,我們在后面將直接使用person來(lái)表示?人?而不是它的中文翻譯)
1.3.1. 映射Person類(lèi)
最初簡(jiǎn)單的Person類(lèi):
package events;
private int age;
private String firstname;
private String lastname;
創(chuàng )建一個(gè)名為Person.hbm.xml的新映射文件(別忘了最上面的DTD引用):
<hibernate-mapping>
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
最后,把新的映射加入到Hibernate的配置中:
<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>
現在我們在這兩個(gè)實(shí)體之間創(chuàng )建一個(gè)關(guān)聯(lián)。顯然,persons可以參與一系列events,而events也有不同的參加者(persons)。我們需要處理的設計問(wèn)題是關(guān)聯(lián)方向(directionality),階數(multiplicity)和集合(collection)的行為。
1.3.2. 單向Set-based的關(guān)聯(lián)
我們將向Person類(lèi)增加一連串的events。那樣,通過(guò)調用aPerson.getEvents(),就可以輕松地導航到特定person所參與的events,而不用去執行一個(gè)顯式的查詢(xún)。我們使用Java的集合類(lèi)(collection):Set,因為set 不包含重復的元素及與我們無(wú)關(guān)的排序。
我們需要用set 實(shí)現一個(gè)單向多值關(guān)聯(lián)。讓我們在Java類(lèi)里為這個(gè)關(guān)聯(lián)編碼,接著(zhù)映射它:
public class Person {
return events;
}
this.events = events;
}
}
在映射這個(gè)關(guān)聯(lián)之前,先考慮一下此關(guān)聯(lián)的另外一端。很顯然,我們可以保持這個(gè)關(guān)聯(lián)是單向的?;蛘?,我們可以在Event里創(chuàng )建另外一個(gè)集合,如果希望能夠雙向地導航,如:anEvent.getParticipants()。從功能的角度來(lái)說(shuō),這并不是必須的。因為你總可以顯式地執行一個(gè)查詢(xún),以獲得某個(gè)特定event的所有參與者。這是個(gè)在設計時(shí)需要做出的選擇,完全由你來(lái)決定,但此討論中關(guān)于關(guān)聯(lián)的階數是清楚的:即兩端都是?多?值的,我們把它叫做多對多(many-to-many)關(guān)聯(lián)。因而,我們使用Hibernate的多對多映射:
<class name="events.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="events.Event"/>
</set>
Hibernate支持各種各樣的集合映射,<set>使用的最為普遍。對于多對多關(guān)聯(lián)(或叫n:m實(shí)體關(guān)系), 需要一個(gè)關(guān)聯(lián)表(association table)。表里面的每一行代表從person到event的一個(gè)關(guān)聯(lián)。表名是由set元素的table屬性配置的。關(guān)聯(lián)里面的標識符字段名,對于person的一端,是由<key>元素定義,而event一端的字段名是由<many-to-many>元素的column屬性定義。你也必須告訴Hibernate集合中對象的類(lèi)(也就是位于這個(gè)集合所代表的關(guān)聯(lián)另外一端的類(lèi))。
因而這個(gè)映射的數據庫schema是:
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
1.3.3. 使關(guān)聯(lián)工作
我們把一些people和events 一起放到EventManager的新方法中:
private void addPersonToEvent(Long personId, Long eventId) {
session.beginTransaction();
Event anEvent = (Event) session.load(Event.class, eventId);
}
在加載一Person和Event后,使用普通的集合方法就可容易地修改我們定義的集合。如你所見(jiàn),沒(méi)有顯式的update()或save(),Hibernate會(huì )自動(dòng)檢測到集合已經(jīng)被修改并需要更新回數據庫。這叫做自動(dòng)臟檢查(automatic dirty checking),你也可以嘗試修改任何對象的name或者date屬性,只要他們處于持久化狀態(tài),也就是被綁定到某個(gè)Hibernate 的Session上(如:他們剛剛在一個(gè)單元操作被加載或者保存),Hibernate監視任何改變并在后臺隱式寫(xiě)的方式執行SQL。同步內存狀態(tài)和數據庫的過(guò)程,通常只在單元操作結束的時(shí)候發(fā)生,稱(chēng)此過(guò)程為清理緩存(flushing)。在我們的代碼中,工作單元由數據庫事務(wù)的提交(或者回滾)來(lái)結束——這是由CurrentSessionContext類(lèi)的thread配置選項定義的。
當然,你也可以在不同的單元操作里面加載person和event?;蛟赟ession以外修改不是處在持久化(persistent)狀態(tài)下的對象(如果該對象以前曾經(jīng)被持久化,那么我們稱(chēng)這個(gè)狀態(tài)為脫管(detached))。你甚至可以在一個(gè)集合被脫管時(shí)修改它:
private void addPersonToEvent(Long personId, Long eventId) {
session.beginTransaction();
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
session2.beginTransaction();
}
對update的調用使一個(gè)脫管對象重新持久化,你可以說(shuō)它被綁定到一個(gè)新的單元操作上,所以在脫管狀態(tài)下對它所做的任何修改都會(huì )被保存到數據庫里。這也包括你對這個(gè)實(shí)體對象的集合所作的任何改動(dòng)(增加/刪除)。
這對我們當前的情形不是很有用,但它是非常重要的概念,你可以把它融入到你自己的應用程序設計中。在EventManager的main方法中添加一個(gè)新的動(dòng)作,并從命令行運行它來(lái)完成我們所做的練習。如果你需要person及event的標識符 — 那就用save()方法返回它(你可能需要修改前面的一些方法來(lái)返回那個(gè)標識符):
else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);
上面是個(gè)關(guān)于兩個(gè)同等重要的實(shí)體類(lèi)間關(guān)聯(lián)的例子。像前面所提到的那樣,在特定的模型中也存在其它的類(lèi)和類(lèi)型,這些類(lèi)和類(lèi)型通常是?次要的?。你已看到過(guò)其中的一些,像int或String。我們稱(chēng)這些類(lèi)為值類(lèi)型(value type),它們的實(shí)例依賴(lài)(depend)在某個(gè)特定的實(shí)體上。這些類(lèi)型的實(shí)例沒(méi)有它們自己的標識(identity),也不能在實(shí)體間被共享(比如,兩個(gè)person不能引用同一個(gè)firstname對象,即使他們有相同的first name)。當然,值類(lèi)型并不僅僅在JDK中存在(事實(shí)上,在一個(gè)Hibernate應用程序中,所有的JDK類(lèi)都被視為值類(lèi)型),而且你也可以編寫(xiě)你自己的依賴(lài)類(lèi),例如Address,MonetaryAmount。
你也可以設計一個(gè)值類(lèi)型的集合,這在概念上與引用其它實(shí)體的集合有很大的不同,但是在Java里面看起來(lái)幾乎是一樣的。
1.3.4. 值類(lèi)型的集合
我們把一個(gè)值類(lèi)型對象的集合加入Person實(shí)體中。我們希望保存email地址,所以使用String類(lèi)型,而且這次的集合類(lèi)型又是Set:
private Set emailAddresses = new HashSet();
return emailAddresses;
}
this.emailAddresses = emailAddresses;
}
這個(gè)Set的映射
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set>
比較這次和此前映射的差別,主要在于element部分,這次并沒(méi)有包含對其它實(shí)體引用的集合,而是元素類(lèi)型為String的集合(在映射中使用小寫(xiě)的名字?string?是向你表明它是一個(gè)Hibernate的映射類(lèi)型或者類(lèi)型轉換器)。和之前一樣,set元素的table屬性決定了用于集合的表名。key元素定義了在集合表中外鍵的字段名。element元素的column屬性定義用于實(shí)際保存String值的字段名。
看一下修改后的數據庫schema。
_____________ __________________
| | | | _____________
| 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 |
|_____________|
你可以看到集合表的主鍵實(shí)際上是個(gè)復合主鍵,同時(shí)使用了2個(gè)字段。這也暗示了對于同一個(gè)person不能有重復的email地址,這正是Java里面使用Set時(shí)候所需要的語(yǔ)義(Set里元素不能重復)。
你現在可以試著(zhù)把元素加入到這個(gè)集合,就像我們在之前關(guān)聯(lián)person和event的那樣。其實(shí)現的Java代碼是相同的:
private void addEmailToPerson(Long personId, String emailAddress) {
session.beginTransaction();
aPerson.getEmailAddresses().add(emailAddress);
}
這次我們沒(méi)有使用fetch查詢(xún)來(lái)初始化集合。因此,調用其getter方法會(huì )觸發(fā)另一附加的select來(lái)初始化集合,這樣我們才能把元素添加進(jìn)去。檢查SQL log,試著(zhù)通過(guò)預先抓取來(lái)優(yōu)化它。
1.3.5. 雙向關(guān)聯(lián)
接下來(lái)我們將映射雙向關(guān)聯(lián)(bi-directional association)- 在Java里讓person和event可以從關(guān)聯(lián)的任何一端訪(fǎng)問(wèn)另一端。當然,數據庫schema沒(méi)有改變,我們仍然需要多對多的階數。一個(gè)關(guān)系型數據庫要比網(wǎng)絡(luò )編程語(yǔ)言 更加靈活,所以它并不需要任何像導航方向(navigation direction)的東西 - 數據可以用任何可能的方式進(jìn)行查看和獲取。
首先,把一個(gè)參與者(person)的集合加入Event類(lèi)中:
private Set participants = new HashSet();
return participants;
}
this.participants = participants;
}
在Event.hbm.xml里面也映射這個(gè)關(guān)聯(lián)。
<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="events.Person"/>
</set>
如你所見(jiàn),兩個(gè)映射文件里都有普通的set映射。注意在兩個(gè)映射文件中,互換了key和many-to-many的字段名。這里最重要的是Event映射文件里增加了set元素的inverse="true"屬性。
這意味著(zhù)在需要的時(shí)候,Hibernate能在關(guān)聯(lián)的另一端 - Person類(lèi)得到兩個(gè)實(shí)體間關(guān)聯(lián)的信息。這將會(huì )極大地幫助你理解雙向關(guān)聯(lián)是如何在兩個(gè)實(shí)體間被創(chuàng )建的。
1.3.6. 使雙向連起來(lái)
首先請記住,Hibernate并不影響通常的Java語(yǔ)義。 在單向關(guān)聯(lián)的例子中,我們是怎樣在Person和Event之間創(chuàng )建聯(lián)系的?我們把Event實(shí)例添加到Person實(shí)例內的event引用集合里。因此很顯然,如果我們要讓這個(gè)關(guān)聯(lián)可以雙向地工作,我們需要在另外一端做同樣的事情 - 把Person實(shí)例加入Event類(lèi)內的Person引用集合。這?在關(guān)聯(lián)的兩端設置聯(lián)系?是完全必要的而且你都得這么做。
許多開(kāi)發(fā)人員防御式地編程,創(chuàng )建管理關(guān)聯(lián)的方法來(lái)保證正確的設置了關(guān)聯(lián)的兩端,比如在Person里:
protected Set getEvents() {
return events;
}
this.events = events;
}
this.getEvents().add(event);
event.getParticipants().add(this);
}
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
注意現在對于集合的get和set方法的訪(fǎng)問(wèn)級別是protected - 這允許在位于同一個(gè)包(package)中的類(lèi)以及繼承自這個(gè)類(lèi)的子類(lèi)可以訪(fǎng)問(wèn)這些方法,但禁止其他任何人的直接訪(fǎng)問(wèn),避免了集合內容的混亂。你應盡可能地在另一端也把集合的訪(fǎng)問(wèn)級別設成protected。
inverse映射屬性究竟表示什么呢?對于你和Java來(lái)說(shuō),一個(gè)雙向關(guān)聯(lián)僅僅是在兩端簡(jiǎn)單地正確設置引用。然而,Hibernate并沒(méi)有足夠的信息去正確地執行INSERT和UPDATE語(yǔ)句(以避免違反數據庫約束),所以它需要一些幫助來(lái)正確的處理雙向關(guān)聯(lián)。把關(guān)聯(lián)的一端設置為inverse將告訴Hibernate忽略關(guān)聯(lián)的這一端,把這端看成是另外一端的一個(gè)鏡象(mirror)。這就是所需的全部信息,Hibernate利用這些信息來(lái)處理把一個(gè)有向導航模型轉移到數據庫schema時(shí)的所有問(wèn)題。你只需要記住這個(gè)直觀(guān)的規則:所有的雙向關(guān)聯(lián)需要有一端被設置為inverse。在一對多關(guān)聯(lián)中它必須是代表多(many)的那端。而在多對多(many-to-many)關(guān)聯(lián)中,你可以任意選取一端,因為兩端之間并沒(méi)有差別。
讓我們把進(jìn)入一個(gè)小型的web應用程序。
1.4. 第三部分 - EventManager web應用程序
Hibernate web應用程序使用Session 和Transaction的方式幾乎和獨立應用程序是一樣的。但是,有一些常見(jiàn)的模式(pattern)非常有用?,F在我們編寫(xiě)一個(gè)EventManagerServlet。這個(gè)servlet可以列出數據庫中保存的所有的events,還提供一個(gè)HTML表單來(lái)增加新的events。
1.4.1. 編寫(xiě)基本的servlet
在你的源代碼目錄的events包中創(chuàng )建一個(gè)新的類(lèi):
package events;
new SimpleDateFormat("dd.MM.yyyy");
}
我們后面會(huì )用到dateFormatter 的工具, 它把Date對象轉換為字符串。只要一個(gè)formatter作為servlet的成員就可以了。
這個(gè)servlet只處理 HTTP GET 請求,因此,我們要實(shí)現的是doGet()方法:
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Begin unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().rollback();
throw new ServletException(ex);
}
我們稱(chēng)這里應用的模式為每次請求一個(gè)session(session-per-request)。當有請求到達這個(gè)servlet的時(shí)候,通過(guò)對SessionFactory的第一次調用,打開(kāi)一個(gè)新的Hibernate Session。然后啟動(dòng)一個(gè)數據庫事務(wù)—所有的數據訪(fǎng)問(wèn)都是在事務(wù)中進(jìn)行,不管是讀還是寫(xiě)(我們在應用程序中不使用auto-commit模式)。
下一步,對請求的可能動(dòng)作進(jìn)行處理,渲染出反饋的HTML。我們很快就會(huì )涉及到那部分。
最后,當處理與渲染都結束的時(shí)候,這個(gè)工作單元就結束了。假若在處理或渲染的時(shí)候有任何錯誤發(fā)生,會(huì )拋出一個(gè)異常,回滾數據庫事務(wù)。這樣,session-per-request模式就完成了。為了避免在每個(gè)servlet中都編寫(xiě)事務(wù)邊界界定的代碼,可以考慮寫(xiě)一個(gè)servlet 過(guò)濾器(filter)來(lái)更好地解決。關(guān)于這一模式的更多信息,請參閱Hibernate網(wǎng)站和Wiki,這一模式叫做Open Session in View—只要你考慮用JSP來(lái)渲染你的視圖(view),而不是在servlet中,你就會(huì )很快用到它。
1.4.2. 處理與渲染
我們來(lái)實(shí)現處理請求以及渲染頁(yè)面的工作。
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
if ( "store".equals(request.getParameter("action")) ) {
String eventDate = request.getParameter("eventDate");
out.println("<b><i>Please enter event title and date.</i></b>");
} else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Added event.</i></b>");
}
}
printEventForm(out);
listEvents(out);
out.println("</body></html>");
out.flush();
out.close();
必須承認,這種編碼風(fēng)格讓Java與HTML混合在了一起,在更復雜的應用程序中不應大量地使用—記住我們在章中僅為了展示Hibernate的基本概念。這段代碼打印出了HTML頭和尾部。在頁(yè)面中,打印出一個(gè)輸入event條目的表單,并列出數據庫中所有events。第一個(gè)方法微不足道,僅僅是輸出HTML:
private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name=‘eventTitle‘ length=‘50‘/><br/>");
out.println("Date (e.g. 24.12.2009): <input name=‘eventDate‘ length=‘10‘/><br/>");
out.println("<input type=‘submit‘ name=‘a(chǎn)ction‘ value=‘store‘/>");
out.println("</form>");
}
listEvents()方法使用綁定到當前線(xiàn)程的Hibernate Session來(lái)執行查詢(xún):
private void listEvents(PrintWriter out) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border=‘1‘>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
for (Iterator it = result.iterator(); it.hasNext();) {
Event event = (Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() + "</td>");
out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}
最后,store動(dòng)作會(huì )被導向到createAndStoreEvent()方法,它也使用當前線(xiàn)程的Session:
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
.getCurrentSession().save(theEvent);
}
大功告成,這個(gè)servlet寫(xiě)完了。Hibernate會(huì )在單一的Session 和Transaction中處理到達的servlet請求。如同在前面的獨立應用程序中那樣,Hibernate可以自動(dòng)的把這些對象綁定到當前運行的線(xiàn)程中。這給了你用任何你喜歡的方式來(lái)對代碼分層及訪(fǎng)問(wèn)SessionFactory的自由。通常,你會(huì )用更加完備的設計,把數據訪(fǎng)問(wèn)代碼轉移到數據訪(fǎng)問(wèn)對象中(DAO模式)。請參見(jiàn)Hibernate Wiki,那里有更多的例子。
1.4.3. 部署與測試
要發(fā)布這個(gè)程序,你得把它打成web發(fā)布包:WAR文件。把下面的腳本加入到你的build.xml中:
<target name="war" depends="compile">
<war destfile="hibernate-tutorial.war" webxml="web.xml">
<lib dir="${librarydir}">
<exclude name="jsdk*.jar"/>
</lib>
</war>
</target>
這段代碼在你的開(kāi)發(fā)目錄中創(chuàng )建一個(gè)hibernate-tutorial.war的文件。它把所有的類(lèi)庫和web.xml描述文件都打包進(jìn)去,web.xml 文件應該位于你的開(kāi)發(fā)根目錄中:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns=" xmlns:xsi=" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
<servlet-name>Event Manager</servlet-name>
<servlet-class>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>
請注意在你編譯和部署web應用程之前,需要一個(gè)附加的類(lèi)庫:jsdk.jar。這是Java Servlet開(kāi)發(fā)包,假若你還沒(méi)有,可以從Sun網(wǎng)站上下載,把它c(diǎn)opy到你的lib目錄。但是,它僅僅是在編譯時(shí)需要,不會(huì )被打入WAR包。
在你的開(kāi)發(fā)目錄中,調用ant war來(lái)構建、打包,然后把hibernate-tutorial.war文件拷貝到你的tomcat的webapps目錄下。假若你還沒(méi)安裝Tomcat,就去下載一個(gè),按照指南來(lái)安裝。對此應用的發(fā)布,你不需要修改任何Tomcat的配置。
在部署完,啟動(dòng)Tomcat之后,通過(guò)http://localhost:8080/hibernate-tutorial/eventmanager進(jìn)行訪(fǎng)問(wèn)你的應用,在第一次servlet 請求發(fā)生時(shí),請在Tomcat log中確認你看到Hibernate被初始化了(HibernateUtil的靜態(tài)初始化器被調用),假若有任何異常拋出,也可以看到詳細的輸出。
1.5. 總結
本章覆蓋了如何編寫(xiě)一個(gè)簡(jiǎn)單獨立的Hibernate命令行應用程序及小型的Hibernate web應用程序的基本要素。
如果你已經(jīng)對Hibernate感到自信,通過(guò)開(kāi)發(fā)指南目錄,繼續瀏覽你感興趣的內容-那些會(huì )被問(wèn)到的問(wèn)題大多是事務(wù)處理 (第 11 章 事務(wù)和并發(fā)),抓?。╢etch)的效率 (第 19 章 提升性能 ),或者API的使用 (第 10 章 與對象共事)和查詢(xún)的特性(第 10.4 節 ?查詢(xún)?)。
別忘了去Hibernate的網(wǎng)站查看更多(有針對性的)示例。
第 2 章 體系結構(Architecture)
2.1. 概況(Overview)
一個(gè)非常簡(jiǎn)要的Hibernate體系結構的概要圖:
從這個(gè)圖可以看出,Hibernate使用數據庫和配置信息來(lái)為應用程序提供持久化服務(wù)(以及持久的對象)。
我們來(lái)更詳細地看一下Hibernate運行時(shí)體系結構。由于Hibernate非常靈活,且支持多種應用方案, 所以我們這只描述一下兩種極端的情況。?輕型?的體系結構方案,要求應用程序提供自己的JDBC 連接并管理自己的事務(wù)。這種方案使用了Hibernate API的最小子集:
全面解決?的體系結構方案,將應用層從底層的JDBC/JTA API中抽象出來(lái),而讓Hibernate來(lái)處理這些細節。
圖中各個(gè)對象的定義如下:
SessionFactory (org.hibernate.SessionFactory)
針對單個(gè)數據庫映射關(guān)系經(jīng)過(guò)編譯后的內存鏡像,是線(xiàn)程安全的(不可變)。 它是生成Session的工廠(chǎng),本身要用到ConnectionProvider。 該對象可以在進(jìn)程或集群的級別上,為那些事務(wù)之間可以重用的數據提供可選的二級緩存。
Session (org.hibernate.Session)
表示應用程序與持久儲存層之間交互操作的一個(gè)單線(xiàn)程對象,此對象生存期很短。 其隱藏了JDBC連接,也是Transaction的工廠(chǎng)。 其會(huì )持有一個(gè)針對持久化對象的必選(第一級)緩存,在遍歷對象圖或者根據持久化標識查找對象時(shí)會(huì )用到。
持久的對象及其集合
帶有持久化狀態(tài)的、具有業(yè)務(wù)功能的單線(xiàn)程對象,此對象生存期很短。 這些對象可能是普通的JavaBeans/POJO,唯一特殊的是他們正與(僅僅一個(gè))Session相關(guān)聯(lián)。 一旦這個(gè)Session被關(guān)閉,這些對象就會(huì )脫離持久化狀態(tài),這樣就可被應用程序的任何層自由使用。 (例如,用作跟表示層打交道的數據傳輸對象。)
瞬態(tài)(transient)和脫管(detached)的對象及其集合
那些目前沒(méi)有與session關(guān)聯(lián)的持久化類(lèi)實(shí)例。 他們可能是在被應用程序實(shí)例化后,尚未進(jìn)行持久化的對象。 也可能是因為實(shí)例化他們的Session已經(jīng)被關(guān)閉而脫離持久化的對象。
事務(wù)Transaction (org.hibernate.Transaction)
(可選的)應用程序用來(lái)指定原子操作單元范圍的對象,它是單線(xiàn)程的,生命周期很短。 它通過(guò)抽象將應用從底層具體的JDBC、JTA以及CORBA事務(wù)隔離開(kāi)。 某些情況下,一個(gè)Session之內可能包含多個(gè)Transaction對象。 盡管是否使用該對象是可選的,但無(wú)論是使用底層的API還是使用Transaction對象,事務(wù)邊界的開(kāi)啟與關(guān)閉是必不可少的。
ConnectionProvider (org.hibernate.connection.ConnectionProvider)
(可選的)生成JDBC連接的工廠(chǎng)(同時(shí)也起到連接池的作用)。 它通過(guò)抽象將應用從底層的Datasource或DriverManager隔離開(kāi)。 僅供開(kāi)發(fā)者擴展/實(shí)現用,并不暴露給應用程序使用。
TransactionFactory (org.hibernate.TransactionFactory)
(可選的)生成Transaction對象實(shí)例的工廠(chǎng)。 僅供開(kāi)發(fā)者擴展/實(shí)現用,并不暴露給應用程序使用。
擴展接口
Hibernate提供了很多可選的擴展接口,你可以通過(guò)實(shí)現它們來(lái)定制你的持久層的行為。 具體請參考API文檔。
在特定?輕型?的體系結構中,應用程序可能繞過(guò) Transaction/TransactionFactory 以及 ConnectionProvider 等API直接跟JTA或JDBC打交道。
2.2. 實(shí)例狀態(tài)
一個(gè)持久化類(lèi)的實(shí)例可能處于三種不同狀態(tài)中的某一種。 這三種狀態(tài)的定義則與所謂的持久化上下文(persistence context)有關(guān)。 Hibernate的Session對象就是這個(gè)所謂的持久化上下文:
瞬態(tài)(transient)
該實(shí)例從未與任何持久化上下文關(guān)聯(lián)過(guò)。它沒(méi)有持久化標識(相當于主鍵值)。
持久化(persistent)
實(shí)例目前與某個(gè)持久化上下文有關(guān)聯(lián)。 它擁有持久化標識(相當于主鍵值),并且可能在數據庫中有一個(gè)對應的行。 對于某一個(gè)特定的持久化上下文,Hibernate保證持久化標識與Java標識(其值代表對象在內存中的位置)等價(jià)。
脫管(detached)
實(shí)例曾經(jīng)與某個(gè)持久化上下文發(fā)生過(guò)關(guān)聯(lián),不過(guò)那個(gè)上下文被關(guān)閉了, 或者這個(gè)實(shí)例是被序列化(serialize)到另外的進(jìn)程。 它擁有持久化標識,并且在數據庫中可能存在一個(gè)對應的行。 對于脫管狀態(tài)的實(shí)例,Hibernate不保證任何持久化標識和Java標識的關(guān)系。
2.3. JMX整合
JMX是管理Java組件(Java components)的J2EE標準。 Hibernate 可以通過(guò)一個(gè)JMX標準服務(wù)來(lái)管理。 在這個(gè)發(fā)行版本中,我們提供了一個(gè)MBean接口的實(shí)現,即 org.hibernate.jmx.HibernateService。
想要看如何在JBoss應用服務(wù)器上將Hibernate部署為一個(gè)JMX服務(wù)的例子,您可以參考JBoss用戶(hù)指南。 我們現在說(shuō)一下在Jboss應用服務(wù)器上,使用JMX來(lái)部署Hibernate的好處:
? Session管理: Hibernate的Session對象的生命周期可以 自動(dòng)跟一個(gè)JTA事務(wù)邊界綁定。這意味著(zhù)你無(wú)需手工開(kāi)關(guān)Session了, 這項 工作會(huì )由JBoss EJB 攔截器來(lái)完成。你再也不用擔心你的代碼中的事務(wù)邊界了(除非你想利用Hibernate提供可選 的Transaction API來(lái)自己寫(xiě)一個(gè)便于移植的的持久層)。 你通過(guò)調用HibernateContext來(lái)訪(fǎng)問(wèn)Session。
? HAR 部署: 通常情況下,你會(huì )使用JBoss的服務(wù)部署描述符(在EAR或/和SAR文件中)來(lái)部署Hibernate JMX服務(wù)。 這種部署方式支持所有常見(jiàn)的Hibernate SessionFactory的配置選項。 不過(guò),你仍需在部署描述符中,列出你所有的映射文件的名字。如果你使用HAR部署方式, JBoss 會(huì )自動(dòng)探測出你的HAR文件中所有的映射文件。
這些選項更多的描述,請參考JBoss 應用程序用戶(hù)指南。
將Hibernate以部署為JMX服務(wù)的另一個(gè)好處,是可以查看Hibernate的運行時(shí)統計信息。參看 第 3.4.6 節 ? Hibernate的統計(statistics)機制 ?.
2.4. 對JCA的支持
Hibernate也可以被配置為一個(gè)JCA連接器(JCA connector)。更多信息請參看網(wǎng)站。 請注意,Hibernate對JCA的支持,仍處于實(shí)驗性階段。
2.5. 上下文相關(guān)的(Contextual)Session
使用Hibernate的大多數應用程序需要某種形式的?上下文相關(guān)的? session,特定的session在整個(gè)特定的上下文范圍內始終有效。然而,對不同類(lèi)型的應用程序而言,要為什么是組成這種?上下文?下一個(gè)定義通常是困難的;不同的上下文對?當前?這個(gè)概念定義了不同的范圍。在3.0版本之前,使用Hibernate的程序要么采用自行編寫(xiě)的基于ThreadLocal的上下文session,要么采用HibernateUtil這樣的輔助類(lèi),要么采用第三方框架(比如Spring或Pico),它們提供了基于代理(proxy)或者基于攔截器(interception)的上下文相關(guān)session。
從3.0.1版本開(kāi)始,Hibernate增加了SessionFactory.getCurrentSession()方法。一開(kāi)始,它假定了采用JTA事務(wù),JTA事務(wù)定義了當前session的范圍和上下文(scope and context)。Hibernate開(kāi)發(fā)團隊堅信,因為有好幾個(gè)獨立的JTA TransactionManager實(shí)現穩定可用,不論是否被部署到一個(gè)J2EE容器中,大多數(假若不是所有的)應用程序都應該采用JTA事務(wù)管理?;谶@一點(diǎn),采用JTA的上下文相關(guān)session可以滿(mǎn)足你一切需要。
更好的是,從3.1開(kāi)始,SessionFactory.getCurrentSession()的后臺實(shí)現是可拔插的。因此,我們引入了新的擴展接口(org.hibernate.context.CurrentSessionContext)和新的配置參數(hibernate.current_session_context_class),以便對什么是?當前session?的范圍和上下文(scope and context)的定義進(jìn)行拔插。
請參閱org.hibernate.context.CurrentSessionContext接口的Javadoc,那里有關(guān)于它的契約的詳細討論。它定義了單一的方法,currentSession(),特定的實(shí)現用它來(lái)負責跟蹤當前的上下文session。Hibernate內置了此接口的兩種實(shí)現。
? org.hibernate.context.JTASessionContext - 當前session根據JTA來(lái)跟蹤和界定。這和以前的僅支持JTA的方法是完全一樣的。詳情請參閱Javadoc。
? org.hibernate.context.ThreadLocalSessionContext - 當前session通過(guò)當前執行的線(xiàn)程來(lái)跟蹤和界定。詳情也請參閱Javadoc。
這兩種實(shí)現都提供了?每數據庫事務(wù)對應一個(gè)session?的編程模型,也稱(chēng)作每次請求一個(gè)session。Hibernate session的起始和終結由數據庫事務(wù)的生存來(lái)控制。假若你采用自行編寫(xiě)代碼來(lái)管理事務(wù)(比如,在純粹的J2SE,或者JTA/UserTransaction/BMT),建議你使用Hibernate Transaction API來(lái)把底層事務(wù)實(shí)現從你的代碼中隱藏掉。如果你在支持CMT的EJB容器中執行,事務(wù)邊界是聲明式定義的,你不需要在代碼中進(jìn)行任何事務(wù)或session管理操作。請參閱第 11 章 事務(wù)和并發(fā)一節來(lái)閱讀更多的內容和示例代碼。
hibernate.current_session_context_class配置參數定義了應該采用哪個(gè)org.hibernate.context.CurrentSessionContext實(shí)現。注意,為了向下兼容,如果未配置此參數,但是存在org.hibernate.transaction.TransactionManagerLookup的配置,Hibernate會(huì )采用org.hibernate.context.JTASessionContext。一般而言,此參數的值指明了要使用的實(shí)現類(lèi)的全名,但那兩個(gè)內置的實(shí)現可以使用簡(jiǎn)寫(xiě),即"jta"和"thread"。
第 3 章 配置
由于Hibernate是為了能在各種不同環(huán)境下工作而設計的, 因此存在著(zhù)大量的配置參數. 幸運的是多數配置參數都 有比較直觀(guān)的默認值, 并有隨Hibernate一同分發(fā)的配置樣例hibernate.properties (位于etc/)來(lái)展示各種配置選項. 所需做的僅僅是將這個(gè)樣例文件復制到類(lèi)路徑 (classpath)下并做一些自定義的修改.
3.1. 可編程的配置方式
一個(gè)org.hibernate.cfg.Configuration實(shí)例代表了一個(gè)應用程序中Java類(lèi)型 到SQL數據庫映射的完整集合. Configuration被用來(lái)構建一個(gè)(不可變的 (immutable))SessionFactory. 映射定義則由不同的XML映射定義文件編譯而來(lái).
你可以直接實(shí)例化Configuration來(lái)獲取一個(gè)實(shí)例,并為它指定XML映射定義 文件. 如果映射定 義文件在類(lèi)路徑(classpath)中, 請使用addResource():
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");
一個(gè)替代方法(有時(shí)是更好的選擇)是,指定被映射的類(lèi),讓Hibernate幫你尋找映射定義文件:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);
Hibernate將會(huì )在類(lèi)路徑(classpath)中尋找名字為 /org/hibernate/auction/Item.hbm.xml和 /org/hibernate/auction/Bid.hbm.xml映射定義文件. 這種方式消除了任何對文件名的硬編碼(hardcoded).
Configuration也允許你指定配置屬性:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");
當然這不是唯一的傳遞Hibernate配置屬性的方式, 其他可選方式還包括:
? 傳一個(gè)java.util.Properties實(shí)例給 Configuration.setProperties().
? 將hibernate.properties放置在類(lèi)路徑(classpath)的根目錄下 (root directory).
? 通過(guò)java -Dproperty=value來(lái)設置系統 (System)屬性.
? 在hibernate.cfg.xml中加入元素 <property> (稍后討論).
如果想盡快體驗Hibernate, hibernate.properties是最簡(jiǎn)單的方式.
Configuration實(shí)例被設計成啟動(dòng)期間(startup-time)對象, 一旦SessionFactory創(chuàng )建完成它就被丟棄了.
3.2. 獲得SessionFactory
當所有映射定義被Configuration解析后, 應用程序必須獲得一個(gè)用于構造Session實(shí)例的工廠(chǎng). 這個(gè)工廠(chǎng)將被應用程序的所有線(xiàn)程共享:
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate允許你的應用程序創(chuàng )建多個(gè)SessionFactory實(shí)例. 這對 使用多個(gè)數據庫的應用來(lái)說(shuō)很有用.
3.3. JDBC連接
通常你希望SessionFactory來(lái)為你創(chuàng )建和緩存(pool)JDBC連接. 如果你采用這種方式, 只需要如下例所示那樣,打開(kāi)一個(gè)Session:
Session session = sessions.openSession(); // open a new Session
一旦你需要進(jìn)行數據訪(fǎng)問(wèn)時(shí), 就會(huì )從連接池(connection pool)獲得一個(gè)JDBC連接.
為了使這種方式工作起來(lái), 我們需要向Hibernate傳遞一些JDBC連接的屬性. 所有Hibernate屬性的名字和語(yǔ)義都在org.hibernate.cfg.Environment中定義. 我們現在將描述JDBC連接配置中最重要的設置.
如果你設置如下屬性,Hibernate將使用java.sql.DriverManager來(lái)獲得(和緩存)JDBC連接 :
表 3.1. Hibernate JDBC屬性
屬性名 用途
hibernate.connection.driver_class jdbc驅動(dòng)類(lèi)
hibernate.connection.url jdbc URL
hibernate.connection.username 數據庫用戶(hù)
hibernate.connection.password 數據庫用戶(hù)密碼
hibernate.connection.pool_size 連接池容量上限數目
但Hibernate自帶的連接池算法相當不成熟. 它只是為了讓你快些上手,并不適合用于產(chǎn)品系統或性能測試中。 出于最佳性能和穩定性考慮你應該使用第三方的連接池。只需要用特定連接池的設置替換 hibernate.connection.pool_size即可。這將關(guān)閉Hibernate自帶的連接池. 例如, 你可能會(huì )想用C3P0.
C3P0是一個(gè)隨Hibernate一同分發(fā)的開(kāi)源的JDBC連接池, 它位于lib目錄下。 如果你設置了hibernate.c3p0.*相關(guān)的屬性, Hibernate將使用 C3P0ConnectionProvider來(lái)緩存JDBC連接. 如果你更原意使用Proxool, 請參考發(fā) 行包中的hibernate.properties并到Hibernate網(wǎng)站獲取更多的信息.
這是一個(gè)使用C3P0的hibernate.properties樣例文件:
hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
為了能在應用程序服務(wù)器(application server)中使用Hibernate, 應當總是將Hibernate 配置成從注冊在JNDI中的Datasource處獲得連接,你至少需要設置下列屬性中的一個(gè):
表 3.2. Hibernate數據源屬性
屬性名 用途
hibernate.connection.datasource 數據源JNDI名字
hibernate.jndi.url JNDI提供者的URL (可選)
hibernate.jndi.class JNDI InitialContextFactory類(lèi) (可選)
hibernate.connection.username 數據庫用戶(hù) (可選)
hibernate.connection.password 數據庫用戶(hù)密碼 (可選)
這是一個(gè)使用應用程序服務(wù)器提供的JNDI數據源的hibernate.properties樣例文件:
hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \
org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
從JNDI數據源獲得的JDBC連接將自動(dòng)參與到應用程序服務(wù)器中容器管理的事務(wù)(container-managed transactions)中去.
任何連接(connection)屬性的屬性名都要以"hibernate.connnection"開(kāi)頭. 例如, 你可能會(huì )使用hibernate.connection.charSet來(lái)指定字符集charSet.
通過(guò)實(shí)現org.hibernate.connection.ConnectionProvider接口,你可以定義屬于 你自己的獲得JDBC連接的插件策略。通過(guò)設置hibernate.connection.provider_class, 你可以選擇一個(gè)自定義的實(shí)現.
3.4. 可選的配置屬性
有大量屬性能用來(lái)控制Hibernate在運行期的行為. 它們都是可選的, 并擁有適當的默認值.
警告: 其中一些屬性是"系統級(system-level)的". 系統級屬性只能通過(guò)java -Dproperty=value或 hibernate.properties來(lái)設置, 而不能用上面描述的其他方法來(lái)設置.
表 3.3. Hibernate配置屬性
屬性名 用途
hibernate.dialect 一個(gè)Hibernate Dialect類(lèi)名允許Hibernate針對特定的關(guān)系數據庫生成優(yōu)化的SQL.
取值 full.classname.of.Dialect
hibernate.show_sql 輸出所有SQL語(yǔ)句到控制臺. 有一個(gè)另外的選擇是把org.hibernate.SQL這個(gè)log category設為debug。
eg. true | false
hibernate.format_sql 在log和console中打印出更漂亮的SQL。
取值 true | false
hibernate.default_schema 在生成的SQL中, 將給定的schema/tablespace附加于非全限定名的表名上.
取值 SCHEMA_NAME
hibernate.default_catalog 在生成的SQL中, 將給定的catalog附加于非全限定名的表名上.
取值 CATALOG_NAME
hibernate.session_factory_name SessionFactory創(chuàng )建后,將自動(dòng)使用這個(gè)名字綁定到JNDI中.
取值 jndi/composite/name
hibernate.max_fetch_depth 為單向關(guān)聯(lián)(一對一, 多對一)的外連接抓?。╫uter join fetch)樹(shù)設置最大深度. 值為0意味著(zhù)將關(guān)閉默認的外連接抓取.
取值 建議在0到3之間取值
hibernate.default_batch_fetch_size 為Hibernate關(guān)聯(lián)的批量抓取設置默認數量.
取值 建議的取值為4, 8, 和16
hibernate.default_entity_mode 為由這個(gè)SessionFactory打開(kāi)的所有Session指定默認的實(shí)體表現模式.
取值 dynamic-map, dom4j, pojo
hibernate.order_updates 強制Hibernate按照被更新數據的主鍵,為SQL更新排序。這么做將減少在高并發(fā)系統中事務(wù)的死鎖。
取值 true | false
hibernate.generate_statistics 如果開(kāi)啟, Hibernate將收集有助于性能調節的統計數據.
取值 true | false
hibernate.use_identifer_rollback 如果開(kāi)啟, 在對象被刪除時(shí)生成的標識屬性將被重設為默認值.
取值 true | false
hibernate.use_sql_comments 如果開(kāi)啟, Hibernate將在SQL中生成有助于調試的注釋信息, 默認值為false.
取值 true | false
表 3.4. Hibernate JDBC和連接(connection)屬性
屬性名 用途
hibernate.jdbc.fetch_size 非零值,指定JDBC抓取數量的大小 (調用Statement.setFetchSize()).
hibernate.jdbc.batch_size 非零值,允許Hibernate使用JDBC2的批量更新.
取值 建議取5到30之間的值
hibernate.jdbc.batch_versioned_data 如果你想讓你的JDBC驅動(dòng)從executeBatch()返回正確的行計數 , 那么將此屬性設為true(開(kāi)啟這個(gè)選項通常是安全的). 同時(shí),Hibernate將為自動(dòng)版本化的數據使用批量DML. 默認值為false.
eg. true | false
hibernate.jdbc.factory_class 選擇一個(gè)自定義的Batcher. 多數應用程序不需要這個(gè)配置屬性.
eg. classname.of.Batcher
hibernate.jdbc.use_scrollable_resultset 允許Hibernate使用JDBC2的可滾動(dòng)結果集. 只有在使用用戶(hù)提供的JDBC連接時(shí),這個(gè)選項才是必要的, 否則Hibernate會(huì )使用連接的元數據.
取值 true | false
hibernate.jdbc.use_streams_for_binary 在JDBC讀寫(xiě)binary (二進(jìn)制)或serializable (可序列化) 的類(lèi)型時(shí)使用流(stream)(系統級屬性).
取值 true | false
hibernate.jdbc.use_get_generated_keys 在數據插入數據庫之后,允許使用JDBC3 PreparedStatement.getGeneratedKeys() 來(lái)獲取數據庫生成的key(鍵)。需要JDBC3+驅動(dòng)和JRE1.4+, 如果你的數據庫驅動(dòng)在使用Hibernate的標 識生成器時(shí)遇到問(wèn)題,請將此值設為false. 默認情況下將使用連接的元數據來(lái)判定驅動(dòng)的能力.
取值 true|false
hibernate.connection.provider_class 自定義ConnectionProvider的類(lèi)名, 此類(lèi)用來(lái)向Hibernate提供JDBC連接.
取值 classname.of.ConnectionProvider
hibernate.connection.isolation 設置JDBC事務(wù)隔離級別. 查看java.sql.Connection來(lái)了解各個(gè)值的具體意義, 但請注意多數數據庫都不支持所有的隔離級別.
取值 1, 2, 4, 8
hibernate.connection.autocommit 允許被緩存的JDBC連接開(kāi)啟自動(dòng)提交(autocommit) (不建議).
取值 true | false
hibernate.connection.release_mode 指定Hibernate在何時(shí)釋放JDBC連接. 默認情況下,直到Session被顯式關(guān)閉或被斷開(kāi)連接時(shí),才會(huì )釋放JDBC連接. 對于應用程序服務(wù)器的JTA數據源, 你應當使用after_statement, 這樣在每次JDBC調用后,都會(huì )主動(dòng)的釋放連接. 對于非JTA的連接, 使用after_transaction在每個(gè)事務(wù)結束時(shí)釋放連接是合理的. auto將為JTA和CMT事務(wù)策略選擇after_statement, 為JDBC事務(wù)策略選擇after_transaction.
取值 on_close | after_transaction | after_statement | auto
hibernate.connection.<propertyName> 將JDBC屬性propertyName傳遞到DriverManager.getConnection()中去.
hibernate.jndi.<propertyName> 將屬性propertyName傳遞到JNDI InitialContextFactory中去.
表 3.5. Hibernate緩存屬性
屬性名 用途
hibernate.cache.provider_class 自定義的CacheProvider的類(lèi)名.
取值 classname.of.CacheProvider
hibernate.cache.use_minimal_puts 以頻繁的讀操作為代價(jià), 優(yōu)化二級緩存來(lái)最小化寫(xiě)操作. 在Hibernate3中,這個(gè)設置對的集群緩存非常有用, 對集群緩存的實(shí)現而言,默認是開(kāi)啟的.
取值 true|false
hibernate.cache.use_query_cache 允許查詢(xún)緩存, 個(gè)別查詢(xún)仍然需要被設置為可緩存的.
取值 true|false
hibernate.cache.use_second_level_cache 能用來(lái)完全禁止使用二級緩存. 對那些在類(lèi)的映射定義中指定<cache>的類(lèi),會(huì )默認開(kāi)啟二級緩存.
取值 true|false
hibernate.cache.query_cache_factory 自定義實(shí)現QueryCache接口的類(lèi)名, 默認為內建的StandardQueryCache.
取值 classname.of.QueryCache
hibernate.cache.region_prefix 二級緩存區域名的前綴.
取值 prefix
hibernate.cache.use_structured_entries 強制Hibernate以更人性化的格式將數據存入二級緩存.
取值 true|false
表 3.6. Hibernate事務(wù)屬性
屬性名 用途
hibernate.transaction.factory_class 一個(gè)TransactionFactory的類(lèi)名, 用于Hibernate Transaction API (默認為JDBCTransactionFactory).
取值 classname.of.TransactionFactory
jta.UserTransaction 一個(gè)JNDI名字,被JTATransactionFactory用來(lái)從應用服務(wù)器獲取JTA UserTransaction.
取值 jndi/composite/name
hibernate.transaction.manager_lookup_class 一個(gè)TransactionManagerLookup的類(lèi)名 - 當使用JVM級緩存,或在JTA環(huán)境中使用hilo生成器的時(shí)候需要該類(lèi).
取值 classname.of.TransactionManagerLookup
hibernate.transaction.flush_before_completion 如果開(kāi)啟, session在事務(wù)完成后將被自動(dòng)清洗(flush)。 現在更好的方法是使用自動(dòng)session上下文管理。請參見(jiàn)第 2.5 節 ?上下文相關(guān)的(Contextual)Session?。
取值 true | false
hibernate.transaction.auto_close_session 如果開(kāi)啟, session在事務(wù)完成后將被自動(dòng)關(guān)閉。 現在更好的方法是使用自動(dòng)session上下文管理。請參見(jiàn)第 2.5 節 ?上下文相關(guān)的(Contextual)Session?。
取值 true | false
表 3.7. 其他屬性
屬性名 用途
hibernate.current_session_context_class 為"當前" Session指定一個(gè)(自定義的)策略。關(guān)于內置策略的詳情,請參見(jiàn)第 2.5 節 ?上下文相關(guān)的(Contextual)Session? 。
eg. jta | thread | custom.Class
hibernate.query.factory_class 選擇HQL解析器的實(shí)現.
取值 org.hibernate.hql.ast.ASTQueryTranslatorFactory or org.hibernate.hql.classic.ClassicQueryTranslatorFactory
hibernate.query.substitutions 將Hibernate查詢(xún)中的符號映射到SQL查詢(xún)中的符號 (符號可能是函數名或常量名字).
取值 hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC
hibernate.hbm2ddl.auto 在SessionFactory創(chuàng )建時(shí),自動(dòng)檢查數據庫結構,或者將數據庫schema的DDL導出到數據庫. 使用 create-drop時(shí),在顯式關(guān)閉SessionFactory時(shí),將drop掉數據庫schema.
取值 validate | update | create | create-drop
hibernate.cglib.use_reflection_optimizer 開(kāi)啟CGLIB來(lái)替代運行時(shí)反射機制(系統級屬性). 反射機制有時(shí)在除錯時(shí)比較有用. 注意即使關(guān)閉這個(gè)優(yōu)化, Hibernate還是需要CGLIB. 你不能在hibernate.cfg.xml中設置此屬性.
取值 true | false
3.4.1. SQL方言
你應當總是為你的數據庫將hibernate.dialect屬性設置成正確的 org.hibernate.dialect.Dialect子類(lèi). 如果你指定一種方言, Hibernate將為上面列出的一些屬性使用合理的默認值, 為你省去了手工指定它們的功夫.
表 3.8. Hibernate SQL方言 (hibernate.dialect)
RDBMS 方言
DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL org.hibernate.dialect.MySQLDialect
MySQL with InnoDB org.hibernate.dialect.MySQLInnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version) org.hibernate.dialect.OracleDialect
Oracle 9i/10g org.hibernate.dialect.Oracle9Dialect
Sybase org.hibernate.dialect.SybaseDialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server org.hibernate.dialect.SQLServerDialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect
3.4.2. 外連接抓取(Outer Join Fetching)
如果你的數據庫支持ANSI, Oracle或Sybase風(fēng)格的外連接, 外連接抓取通常能通過(guò)限制往返數據庫次數 (更多的工作交由數據庫自己來(lái)完成)來(lái)提高效率. 外連接抓取允許在單個(gè)SELECTSQL語(yǔ)句中, 通過(guò)many-to-one, one-to-many, many-to-many和one-to-one關(guān)聯(lián)獲取連接對象的整個(gè)對象圖.
將hibernate.max_fetch_depth設為0能在全局 范圍內禁止外連接抓取. 設為1或更高值能啟用one-to-one和many-to-oneouter關(guān)聯(lián)的外連接抓取, 它們通過(guò) fetch="join"來(lái)映射.
參見(jiàn)第 19.1 節 ? 抓取策略(Fetching strategies) ?獲得更多信息.
3.4.3. 二進(jìn)制流 (Binary Streams)
Oracle限制那些通過(guò)JDBC驅動(dòng)傳輸的字節數組的數目. 如果你希望使用二進(jìn)值 (binary)或 可序列化的 (serializable)類(lèi)型的大對象, 你應該開(kāi)啟 hibernate.jdbc.use_streams_for_binary屬性. 這是系統級屬性.
3.4.4. 二級緩存與查詢(xún)緩存
以hibernate.cache為前綴的屬性允許你在Hibernate中,使用進(jìn)程或群集范圍內的二級緩存系統. 參見(jiàn)第 19.2 節 ?二級緩存(The Second Level Cache) ?獲取更多的詳情.
3.4.5. 查詢(xún)語(yǔ)言中的替換
你可以使用hibernate.query.substitutions在Hibernate中定義新的查詢(xún)符號. 例如:
hibernate.query.substitutions true=1, false=0
將導致符號true和false在生成的SQL中被翻譯成整數常量.
hibernate.query.substitutions toLowercase=LOWER
將允許你重命名SQL中的LOWER函數.
3.4.6. Hibernate的統計(statistics)機制
如果你開(kāi)啟hibernate.generate_statistics, 那么當你通過(guò) SessionFactory.getStatistics()調整正在運行的系統時(shí),Hibernate將導出大量有用的數據. Hibernate甚至能被配置成通過(guò)JMX導出這些統計信息. 參考org.hibernate.stats中接口的Javadoc,以獲得更多信息.
3.5. 日志
Hibernate使用Apache commons-logging來(lái)為各種事件記錄日志.
commons-logging將直接輸出到Apache Log4j(如果在類(lèi)路徑中包括log4j.jar)或 JDK1.4 logging (如果運行在JDK1.4或以上的環(huán)境下). 你可以從http://jakarta.apache.org 下載Log4j. 要使用Log4j,你需要將log4j.properties文件放置在類(lèi)路徑下, 隨Hibernate 一同分發(fā)的樣例屬性文件在src/目錄下.
我們強烈建議你熟悉一下Hibernate的日志消息. 在不失可讀性的前提下, 我們做了很多工作,使Hibernate的日志可能地詳細. 這是必要的查錯利器. 最令人感興趣的日志分類(lèi)有如下這些:
表 3.9. Hibernate日志類(lèi)別
類(lèi)別 功能
org.hibernate.SQL 在所有SQL DML語(yǔ)句被執行時(shí)為它們記錄日志
org.hibernate.type 為所有JDBC參數記錄日志
org.hibernate.tool.hbm2ddl 在所有SQL DDL語(yǔ)句執行時(shí)為它們記錄日志
org.hibernate.pretty 在session清洗(flush)時(shí),為所有與其關(guān)聯(lián)的實(shí)體(最多20個(gè))的狀態(tài)記錄日志
org.hibernate.cache 為所有二級緩存的活動(dòng)記錄日志
org.hibernate.transaction 為事務(wù)相關(guān)的活動(dòng)記錄日志
org.hibernate.jdbc 為所有JDBC資源的獲取記錄日志
org.hibernate.hql.AST 在解析查詢(xún)的時(shí)候,記錄HQL和SQL的AST分析日志
org.hibernate.secure 為JAAS認證請求做日志
org.hibernate 為任何Hibernate相關(guān)信息做日志 (信息量較大, 但對查錯非常有幫助)
在使用Hibernate開(kāi)發(fā)應用程序時(shí), 你應當總是為org.hibernate.SQL 開(kāi)啟debug級別的日志記錄,或者開(kāi)啟hibernate.show_sql屬性。
3.6. 實(shí)現NamingStrategy
org.hibernate.cfg.NamingStrategy接口允許你為數據庫中的對象和schema 元素指定一個(gè)?命名標準?.
你可能會(huì )提供一些通過(guò)Java標識生成數據庫標識或將映射定義文件中"邏輯"表/列名處理成"物理"表/列名的規則. 這個(gè)特性有助于減少冗長(cháng)的映射定義文件.
在加入映射定義前,你可以調用 Configuration.setNamingStrategy()指定一個(gè)不同的命名策略:
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory();
org.hibernate.cfg.ImprovedNamingStrategy是一個(gè)內建的命名策略, 對 一些應用程序而言,可能是非常有用的起點(diǎn).
3.7. XML配置文件
另一個(gè)配置方法是在hibernate.cfg.xml文件中指定一套完整的配置. 這個(gè)文件可以當成hibernate.properties的替代。 若兩個(gè)文件同時(shí)存在,它將覆蓋前者的屬性.
XML配置文件被默認是放在CLASSPATH的根目錄下. 這是一個(gè)例子:
<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"
<session-factory
name="java:hibernate/SessionFactory">
<property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">false</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction">java:comp/UserTransaction</property>
<mapping resource="org/hibernate/auction/Item.hbm.xml"/>
<mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
<class-cache class="org.hibernate.auction.Item" usage="read-write"/>
<class-cache class="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
如你所見(jiàn), 這個(gè)方法優(yōu)勢在于,在配置文件中指出了映射定義文件的名字. 一旦你需要調整Hibernate的緩存, hibernate.cfg.xml也是更方便. 注意,使用hibernate.properties還是 hibernate.cfg.xml完全是由你來(lái)決定, 除了上面提到的XML語(yǔ)法的優(yōu)勢之外, 兩者是等價(jià)的.
使用XML配置,使得啟動(dòng)Hibernate變的異常簡(jiǎn)單, 如下所示,一行代碼就可以搞定:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
你可以使用如下代碼來(lái)添加一個(gè)不同的XML配置文件
SessionFactory sf = new Configuration()
.configure("catdb.cfg.xml")
.buildSessionFactory();
3.8. J2EE應用程序服務(wù)器的集成
針對J2EE體系,Hibernate有如下幾個(gè)集成的方面:
? 容器管理的數據源(Container-managed datasources): Hibernate能使用通過(guò)容器管理,并由JNDI提供的JDBC連接. 通常, 特別是當處理多個(gè)數據源的分布式事務(wù)的時(shí)候, 由一個(gè)JTA兼容的TransactionManager和一個(gè) ResourceManager來(lái)處理事務(wù)管理(CMT, 容器管理的事務(wù)). 當然你可以通過(guò) 編程方式來(lái)劃分事務(wù)邊界(BMT, Bean管理的事務(wù)). 或者為了代碼的可移植性,你也也許會(huì )想使用可選的 Hibernate Transaction API.
? 自動(dòng)JNDI綁定: Hibernate可以在啟動(dòng)后將 SessionFactory綁定到JNDI.
? JTA Session綁定: Hibernate Session 可以自動(dòng)綁定到JTA事務(wù)作用的范圍. 只需簡(jiǎn)單地從JNDI查找SessionFactory并獲得當前的 Session. 當JTA事務(wù)完成時(shí), 讓Hibernate來(lái)處理 Session的清洗(flush)與關(guān)閉. 事務(wù)的劃分可以是聲明式的(CMT),也可以是編程式的(BMT/UserTransaction).
? JMX部署: 如果你使用支持JMX應用程序服務(wù)器(如, JBoss AS), 那么你可以選擇將Hibernate部署成托管MBean. 這將為你省去一行從Configuration構建SessionFactory的啟動(dòng)代碼. 容器將啟動(dòng)你的HibernateService, 并完美地處理好服務(wù)間的依賴(lài)關(guān)系 (在Hibernate啟動(dòng)前,數據源必須是可用的,等等).
如果應用程序服務(wù)器拋出"connection containment"異常, 根據你的環(huán)境,也許該將配置屬性 hibernate.connection.release_mode設為after_statement.
3.8.1. 事務(wù)策略配置
在你的架構中,Hibernate的Session API是獨立于任何事務(wù)分界系統的. 如果你讓Hibernate通過(guò)連接池直接使用JDBC, 你需要調用JDBC API來(lái)打開(kāi)和關(guān)閉你的事務(wù). 如果你運行在J2EE應用程序服務(wù)器中, 你也許想用Bean管理的事務(wù)并在需要的時(shí)候調用JTA API和UserTransaction.
為了讓你的代碼在兩種(或其他)環(huán)境中可以移植,我們建議使用可選的Hibernate Transaction API, 它包裝并隱藏了底層系統. 你必須通過(guò)設置Hibernate配置屬性hibernate.transaction.factory_class來(lái)指定 一個(gè)Transaction實(shí)例的工廠(chǎng)類(lèi).
有三個(gè)標準(內建)的選擇:
org.hibernate.transaction.JDBCTransactionFactory
委托給數據庫(JDBC)事務(wù)(默認)
org.hibernate.transaction.JTATransactionFactory
如果在上下文環(huán)境中存在運行著(zhù)的事務(wù)(如, EJB會(huì )話(huà)Bean的方法), 則委托給容器管 理的事務(wù), 否則,將啟動(dòng)一個(gè)新的事務(wù),并使用Bean管理的事務(wù).
org.hibernate.transaction.CMTTransactionFactory
委托給容器管理的JTA事務(wù)
你也可以定義屬于你自己的事務(wù)策略 (如, 針對CORBA的事務(wù)服務(wù))
Hibernate的一些特性 (比如二級緩存, Contextual Sessions with JTA等等)需要訪(fǎng)問(wèn)在托管環(huán)境中的JTA TransactionManager. 由于J2EE沒(méi)有標準化一個(gè)單一的機制,Hibernate在應用程序服務(wù)器中,你必須指定Hibernate如何獲得TransactionManager的引用:
表 3.10. JTA TransactionManagers
Transaction工廠(chǎng)類(lèi) 應用程序服務(wù)器
org.hibernate.transaction.JBossTransactionManagerLookup JBoss
org.hibernate.transaction.WeblogicTransactionManagerLookup Weblogic
org.hibernate.transaction.WebSphereTransactionManagerLookup WebSphere
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup WebSphere 6
org.hibernate.transaction.OrionTransactionManagerLookup Orion
org.hibernate.transaction.ResinTransactionManagerLookup Resin
org.hibernate.transaction.JOTMTransactionManagerLookup JOTM
org.hibernate.transaction.JOnASTransactionManagerLookup JOnAS
org.hibernate.transaction.JRun4TransactionManagerLookup JRun4
org.hibernate.transaction.BESTransactionManagerLookup Borland ES
3.8.2. JNDI綁定的SessionFactory
與JNDI綁定的Hibernate的SessionFactory能簡(jiǎn)化工廠(chǎng)的查詢(xún),簡(jiǎn)化創(chuàng )建新的Session. 需要注意的是這與JNDI綁定Datasource沒(méi)有關(guān)系, 它們只是恰巧用了相同的注冊表!
如果你希望將SessionFactory綁定到一個(gè)JNDI的名字空間, 用屬性hibernate.session_factory_name指定一個(gè)名字(如, java:hibernate/SessionFactory). 如果不設置這個(gè)屬性, SessionFactory將不會(huì )被綁定到JNDI中. (在以只讀JNDI為默認實(shí)現的環(huán)境中,這個(gè)設置尤其有用, 如Tomcat.)
在將SessionFactory綁定至JNDI時(shí), Hibernate將使用hibernate.jndi.url, 和hibernate.jndi.class的值來(lái)實(shí)例化初始環(huán)境(initial context). 如果它們沒(méi)有被指定, 將使用默認的InitialContext.
在你調用cfg.buildSessionFactory()后, Hibernate會(huì )自動(dòng)將SessionFactory注冊到JNDI. 這意味這你至少需要在你應用程序的啟動(dòng)代碼(或工具類(lèi))中完成這個(gè)調用, 除非你使用HibernateService來(lái)做JMX部署 (見(jiàn)后面討論).
假若你使用JNDI SessionFactory,EJB或者任何其它類(lèi)都可以從JNDI中找到此SessionFactory。
我們建議,在受管理的環(huán)境中,把SessionFactory綁定到JNDI,在其它情況下,使用一個(gè)static(靜態(tài)的)singleton。為了在你的應用程序代碼中隱藏這些細節,我們還建議你用一個(gè)helper類(lèi)把實(shí)際查找SessionFactory的代碼隱藏起來(lái),比如HibernateUtil.getSessionFactory()。注意,這個(gè)類(lèi)也就可以方便地啟動(dòng)Hibernate,參見(jiàn)第一章。
3.8.3. 在JTA環(huán)境下使用Current Session context (當前session上下文)管理
在Hibernate中,管理Session和transaction最好的方法是自動(dòng)的"當前"Session管理。請參見(jiàn)第 2.5 節 ?上下文相關(guān)的(Contextual)Session?一節的討論。使用"jta"session上下文,假若在當前JTA事務(wù)中還沒(méi)有HibernateSession關(guān)聯(lián),第一次sessionFactory.getCurrentSession()調用會(huì )啟動(dòng)一個(gè)Session,并關(guān)聯(lián)到當前的JTA事務(wù)。在"jta"上下文中調用getCurrentSession()獲得的Session,會(huì )被設置為在transaction關(guān)閉的時(shí)候自動(dòng)flush(清洗)、在transaction關(guān)閉之后自動(dòng)關(guān)閉,每句語(yǔ)句之后主動(dòng)釋放JDBC連接。這就可以根據JTA事務(wù)的生命周期來(lái)管理與之關(guān)聯(lián)的Session,用戶(hù)代碼中就可以不再考慮這些管理。你的代碼也可以通過(guò)UserTransaction用編程方式使用JTA,或者(我們建議,為了便于移植代碼)使用Hibernate的Transaction API來(lái)設置transaction邊界。如果你的代碼運行在EJB容器中,建議對CMT使用聲明式事務(wù)聲明。
3.8.4. JMX部署
為了將SessionFactory注冊到JNDI中,cfg.buildSessionFactory()這行代碼仍需在某處被執行. 你可在一個(gè)static初始化塊(像HibernateUtil中的那樣)中執行它或將Hibernate部署為一個(gè)托管的服務(wù).
為了部署在一個(gè)支持JMX的應用程序服務(wù)器上,Hibernate和 org.hibernate.jmx.HibernateService一同分發(fā),如Jboss AS。 實(shí)際的部署和配置是由應用程序服務(wù)器提供者指定的. 這里是JBoss 4.0.x的jboss-service.xml樣例:
<?xml version="1.0"?>
<server>
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<depends>jboss.jca:service=RARDeployer</depends>
<depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<attribute name="Datasource">java:HsqlDS</attribute>
<attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute>
<attribute name="TransactionStrategy">
org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled">true</attribute>
<attribute name="AutoCloseSessionEnabled">true</attribute>
<attribute name="MaximumFetchDepth">5</attribute>
<attribute name="SecondLevelCacheEnabled">true</attribute>
<attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled">true</attribute>
<attribute name="ShowSqlEnabled">true</attribute>
<attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
這個(gè)文件是部署在META-INF目錄下的, 并會(huì )被打包到以.sar (service archive)為擴展名的JAR文件中. 同時(shí),你需要將Hibernate、它所需要的第三方庫、你編譯好的持久化類(lèi)以及你的映射定義文件打包進(jìn)同一個(gè)文檔. 你的企業(yè)Bean(一般為會(huì )話(huà)Bean)可能會(huì )被打包成它們自己的JAR文件, 但你也許會(huì )將EJB JAR文件一同包含進(jìn)能獨立(熱)部署的主服務(wù)文檔. 參考JBoss AS文檔以了解更多的JMX服務(wù)與EJB部署的信息.
第 4 章 持久化類(lèi)(Persistent Classes)
在應用程序中,用來(lái)實(shí)現業(yè)務(wù)問(wèn)題實(shí)體的(如,在電子商務(wù)應用程序中的Customer和Order) 類(lèi)就是持久化類(lèi)。不能認為所有的持久化類(lèi)的實(shí)例都是持久的狀態(tài)——一個(gè)實(shí)例的狀態(tài)也可能 是瞬時(shí)的或脫管的。
如果這些持久化類(lèi)遵循一些簡(jiǎn)單的規則,Hibernate能夠工作得更好,這些規則也被稱(chēng)作 簡(jiǎn)單傳統Java對象(POJO:Plain Old Java Object)編程模型。但是這些規則并不是必需的。 實(shí)際上,Hibernate3對于你的持久化類(lèi)幾乎不做任何設想。你可以用其他的方法來(lái)表達領(lǐng)域模型: 比如,使用Map實(shí)例的樹(shù)型結構。
4.1. 一個(gè)簡(jiǎn)單的POJO例子
大多數Java程序需要用一個(gè)持久化類(lèi)來(lái)表示貓科動(dòng)物。
package eg;
import java.util.Set;
import java.util.Date;
private Long id; // identifier
private Color color;
private char sex;
private float weight;
private int litterId;
private Set kittens = new HashSet();
this.id=id;
}
public Long getId() {
return id;
}
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
this.weight = weight;
}
public float getWeight() {
return weight;
}
return color;
}
void setColor(Color color) {
this.color = color;
}
this.sex=sex;
}
public char getSex() {
return sex;
}
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}
這里要遵循四條主要的規則:
4.1.1. 實(shí)現一個(gè)默認的(即無(wú)參數的)構造方法(constructor)
Cat有一個(gè)無(wú)參數的構造方法。所有的持久化類(lèi)都必須有一個(gè) 默認的構造方法(可以不是public的),這樣的話(huà)Hibernate就可以使用 Constructor.newInstance()來(lái)實(shí)例化它們。 我們強烈建議,在Hibernate中,為了運行期代理的生成,構造方法至少是 包(package)內可見(jiàn)的。
4.1.2. 提供一個(gè)標識屬性(identifier property)(可選)
Cat有一個(gè)屬性叫做id。這個(gè)屬性映射數據庫表的主 鍵字段。這個(gè)屬性可以叫任何名字,其類(lèi)型可以是任何的原始類(lèi)型、原始類(lèi)型的包裝類(lèi)型、 java.lang.String 或者是 java.util.Date。 (如果你的遺留數據庫表有聯(lián)合主鍵,你甚至可以用一個(gè)用戶(hù)自定義的類(lèi),該類(lèi)擁有這些類(lèi)型 的屬性。參見(jiàn)后面的關(guān)于聯(lián)合標識符的章節。)
標識符屬性是可選的??梢圆挥霉芩?,讓Hibernate內部來(lái)追蹤對象的識別。 但是我們并不推薦這樣做。
實(shí)際上,一些功能只對那些聲明了標識符屬性的類(lèi)起作用:
? 托管對象的傳播性再連接(級聯(lián)更新或級聯(lián)合并) ——參閱 第 10.11 節 ?傳播性持久化(transitive persistence)?
? Session.saveOrUpdate()
? Session.merge()
我們建議你對持久化類(lèi)聲明命名一致的標識屬性。我們還建議你使用一 個(gè)可以為空(也就是說(shuō),不是原始類(lèi)型)的類(lèi)型。
4.1.3. 使用非final的類(lèi) (可選)
代理(proxies)是Hibernate的一個(gè)重要的功能,它依賴(lài)的條件是,持久 化類(lèi)或者是非final的,或者是實(shí)現了一個(gè)所有方法都聲明為public的接口。
你可以用Hibernate持久化一個(gè)沒(méi)有實(shí)現任何接口的final類(lèi),但是你 不能使用代理來(lái)延遲關(guān)聯(lián)加載,這會(huì )限制你進(jìn)行性能優(yōu)化的選擇。
你也應該避免在非final類(lèi)中聲明 public final的方法。如果你想使用一 個(gè)有public final方法的類(lèi),你必須通過(guò)設置lazy="false" 來(lái)明確地禁用代理。
4.1.4. 為持久化字段聲明訪(fǎng)問(wèn)器(accessors)和是否可變的標志(mutators)(可選)
Cat為它的所有持久化字段聲明了訪(fǎng)問(wèn)方法。很多其他ORM工具直接對 實(shí)例變量進(jìn)行持久化。我們相信,在關(guān)系數據庫schema和類(lèi)的內部數據結構之間引入間接層(原文為"非直接",indirection)會(huì )好一些。默認情況下Hibernate持久化JavaBeans風(fēng)格的屬性,認可 getFoo,isFoo 和 setFoo這種形式的方法名。 如果需要,你可以對某些特定屬性實(shí)行直接字段訪(fǎng)問(wèn)。
屬性不需要要聲明為public的。Hibernate可以持久化一個(gè)有 default、protected或private的get/set方法對 的屬性進(jìn)行持久化。
4.2. 實(shí)現繼承(Inheritance)
子類(lèi)也必須遵守第一條和第二條規則。它從超類(lèi)Cat繼承了標識屬性。
package eg;
private String name;
return name;
}
protected void setName(String name) {
this.name=name;
}
}
4.3. 實(shí)現equals()和hashCode()
如果你有如下需求,你必須重載 equals() 和 hashCode()方法:
? 想把持久類(lèi)的實(shí)例放入Set中(當表示多值關(guān)聯(lián)時(shí),推薦這么做)
? 想重用脫管實(shí)例
Hibernate保證,僅在特定會(huì )話(huà)范圍內,持久化標識(數據庫的行)和Java標識是等價(jià)的。因此,一旦 我們混合了從不同會(huì )話(huà)中獲取的實(shí)例,如果希望Set有明確的語(yǔ)義,就必 須實(shí)現equals() 和hashCode()。
實(shí)現equals()/hashCode()最顯而易見(jiàn)的方法是比較兩個(gè)對象 標識符的值。如果值相同,則兩個(gè)對象對應于數據庫的同一行,因此它們是相等的(如果都被添加到 Set,則在Set中只有一個(gè)元素)。不幸的是,對生成的標識不能 使用這種方法。Hibernate僅對那些持久化對象賦標識值,一個(gè)新創(chuàng )建的實(shí)例將不會(huì )有任何標識值。此外, 如果一個(gè)實(shí)例沒(méi)有被保存(unsaved),并且它當前正在一個(gè)Set中,保存它將會(huì )給這個(gè)對象 賦一個(gè)標識值。如果equals() 和 hashCode()是基于標識值 實(shí)現的,則其哈希碼將會(huì )改變,這違反了Set的契約。建議去Hibernate的站點(diǎn)閱讀關(guān)于這個(gè) 問(wèn)題的全部討論。注意,這不是Hibernate的問(wèn)題,而是一般的Java對象標識和Java對象等價(jià)的語(yǔ)義問(wèn)題。
我們建議使用業(yè)務(wù)鍵值相等(Business key equality)來(lái)實(shí)現equals() 和 hashCode()。業(yè)務(wù)鍵值相等的意思是,equals()方法 僅僅比較形成業(yè)務(wù)鍵的屬性,它能在現實(shí)世界里標識我們的實(shí)例(是一個(gè)自然的候選碼)。
public class Cat {
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
}
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
注意,業(yè)務(wù)鍵不必像數據庫的主鍵那樣固定不變(參見(jiàn)第 11.1.3 節 ?關(guān)注對象標識(Considering object identity)?)。 對業(yè)務(wù)鍵而言,不可變或唯一的屬性是不錯的選擇。
4.4. 動(dòng)態(tài)模型(Dynamic models)
注意,以下特性在當前處于試驗階段,將來(lái)可能會(huì )有變化。
運行期的持久化實(shí)體沒(méi)有必要一定表示為像POJO類(lèi)或JavaBean對象那樣的形式。Hibernate也支持動(dòng)態(tài)模型 (在運行期使用Map的Map)和象DOM4J的樹(shù)模型那 樣的實(shí)體表示。使用這種方法,你不用寫(xiě)持久化類(lèi),只寫(xiě)映射文件就行了。
Hibernate默認工作在普通POJO模式。你可以使用配置選項default_entity_mode, 對特定的SessionFactory,設置一個(gè)默認的實(shí)體表示模式。 (參見(jiàn)表 3.3 ? Hibernate配置屬性 ?。)
下面是用Map來(lái)表示的例子。首先,在映射文件中,要聲明 entity-name來(lái)代替一個(gè)類(lèi)名(或作為一種附屬)。
<hibernate-mapping>
type="long"
column="ID">
<generator class="sequence"/>
</id>
column="NAME"
type="string"/>
column="ADDRESS"
type="string"/>
column="ORGANIZATION_ID"
class="Organization"/>
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</hibernate-mapping>
注意,雖然是用目標類(lèi)名來(lái)聲明關(guān)聯(lián)的,但是關(guān)聯(lián)的目標類(lèi)型除了是POJO之外,也可以 是一個(gè)動(dòng)態(tài)的實(shí)體。
在使用dynamic-map為SessionFactory 設置了默認的實(shí)體模式之后,可以在運行期使用Map的 Map。
Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();
Map david = new HashMap();
david.put("name", "David");
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
david.put("organization", foobar);
s.save("Customer", david);
s.save("Organization", foobar);
s.close();
動(dòng)態(tài)映射的好處是,變化所需要的時(shí)間少了,因為原型不需要實(shí)現實(shí)體類(lèi)。然而,你無(wú)法進(jìn)行 編譯期的類(lèi)型檢查,并可能由此會(huì )處理很多的運行期異常。幸虧有了Hibernate映射,它使得數 據庫的schema能容易的規格化和合理化,并允許稍后在此之上添加合適的領(lǐng)域模型實(shí)現。
實(shí)體表示模式也能在每個(gè)Session的基礎上設置:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
請注意,用EntityMode調用getSession()是在 Session的API中,而不是SessionFactory。 這樣,新的Session共享底層的JDBC連接,事務(wù),和其他的上下文信 息。這意味著(zhù),你不需要在第二個(gè)Session中調用 flush()和close(),同樣的,把事務(wù)和連接的處理 交給原來(lái)的工作單元。
關(guān)于XML表示能力的更多信息可以在第 18 章 XML映射中找到。
4.5. 元組片斷映射(Tuplizers)
org.hibernate.tuple.Tuplizer,以及其子接口,負責根據給定的org.hibernate.EntityMode,來(lái)復現片斷數據。如果給定的片斷數據被認為其是一種數據結構,"tuplizer"就是一個(gè)知道如何創(chuàng )建這樣的數據結構,以及如何給這個(gè)數據結構賦值的東西。比如說(shuō),對于POJO這種Entity Mode,對應的tuplizer知道通過(guò)其構造方法來(lái)創(chuàng )建一個(gè)POJO,再通過(guò)其屬性訪(fǎng)問(wèn)器來(lái)訪(fǎng)問(wèn)POJO屬性。有兩大類(lèi)高層Tuplizer,分別是org.hibernate.tuple.EntityTuplizer 和org.hibernate.tuple.ComponentTuplizer接口。EntityTuplizer負責管理上面提到的實(shí)體的契約,而ComponentTuplizer則是針對組件的。
用戶(hù)也可以插入其自定義的tuplizer?;蛟S您需要一種不同于dynamic-map entity-mode中使用的java.util.HashMap的java.util.Map實(shí)現;或許您需要與默認策略不同的代理生成策略(proxy generation strategy)。通過(guò)自定義tuplizer實(shí)現,這兩個(gè)目標您都可以達到。Tuplizer定義被附加到它們期望管理的entity或者component映射中?;氐轿覀兊腸ustomer entity例子:
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<generator class="sequence"/>
</id>
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
extends org.hibernate.tuple.DynamicMapEntityTuplizer {
// override the buildInstantiator() method to plug in our custom map...
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}
TODO:property和proxy包里的用戶(hù)擴展框架文檔。
第 5 章 對象/關(guān)系數據庫映射基礎(Basic O/R Mapping)
5.1. 映射定義(Mapping declaration)
對象和關(guān)系數據庫之間的映射通常是用一個(gè)XML文檔(XML document)來(lái)定義的。這個(gè)映射文檔被設計為易讀的, 并且可以手工修改。映射語(yǔ)言是以Java為中心,這意味著(zhù)映射文檔是按照持久化類(lèi)的定義來(lái)創(chuàng )建的, 而非表的定義。
請注意,雖然很多Hibernate用戶(hù)選擇手寫(xiě)XML映射文檔,但也有一些工具可以用來(lái)生成映射文檔, 包括XDoclet,Middlegen和AndroMDA。
讓我們從一個(gè)映射的例子開(kāi)始:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"
table="cats"
discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
type="character"/>
type="date"
not-null="true"
update="false"/>
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
not-null="true"
update="false"/>
column="litterId"
update="false"/>
column="mother_id"
update="false"/>
inverse="true"
order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
discriminator-value="D">
type="string"/>
<!-- mapping for Dog could go here -->
</class>
我們現在開(kāi)始討論映射文檔的內容。我們只描述Hibernate在運行時(shí)用到的文檔元素和屬性。 映射文檔還包括一些額外的可選屬性和元素,它們在使用schema導出工具的時(shí)候會(huì )影響導出的數據庫schema結果。 (比如, not-null 屬性。)
5.1.1. Doctype
所有的XML映射都需要定義如上所示的doctype。DTD可以從上述URL中獲取, 也可以從hibernate-x.x.x/src/net/sf/hibernate目錄中、 或hibernate.jar文件中找到。Hibernate總是會(huì )首先在它的classptah中搜索DTD文件。 如果你發(fā)現它是通過(guò)連接Internet查找DTD文件,就對照你的classpath目錄檢查XML文件里的DTD聲明。
5.1.2. hibernate-mapping
這個(gè)元素包括一些可選的屬性。schema和catalog屬性, 指明了這個(gè)映射所連接(refer)的表所在的schema和/或catalog名稱(chēng)。 假若指定了這個(gè)屬性,表名會(huì )加上所指定的schema和catalog的名字擴展為全限定名。假若沒(méi)有指定,表名就不會(huì )使用全限定名。 default-cascade指定了未明確注明cascade屬性的Java屬性和 集合類(lèi)Hibernate會(huì )采取什么樣的默認級聯(lián)風(fēng)格。auto-import屬性默認讓我們在查詢(xún)語(yǔ)言中可以使用 非全限定名的類(lèi)名。
<hibernate-mapping
schema="schemaName" (1)
catalog="catalogName" (2)
default-cascade="cascade_style" (3)
default-access="field|property|ClassName" (4)
default-lazy="true|false" (5)
auto-import="true|false" (6)
package="package.name" (7)
/>
(1) schema (可選): 數據庫schema的名稱(chēng)。
(2) catalog (可選): 數據庫catalog的名稱(chēng)。
(3) default-cascade (可選 - 默認為 none): 默認的級聯(lián)風(fēng)格。
(4) default-access (可選 - 默認為 property): Hibernate用來(lái)訪(fǎng)問(wèn)所有屬性的策略??梢酝ㄟ^(guò)實(shí)現PropertyAccessor接口 自定義。
(5) default-lazy (可選 - 默認為 true): 指定了未明確注明lazy屬性的Java屬性和集合類(lèi), Hibernate會(huì )采取什么樣的默認加載風(fēng)格。
(6) auto-import (可選 - 默認為 true): 指定我們是否可以在查詢(xún)語(yǔ)言中使用非全限定的類(lèi)名(僅限于本映射文件中的類(lèi))。
(7) package (可選): 指定一個(gè)包前綴,如果在映射文檔中沒(méi)有指定全限定的類(lèi)名, 就使用這個(gè)作為包名。
假若你有兩個(gè)持久化類(lèi),它們的非全限定名是一樣的(就是兩個(gè)類(lèi)的名字一樣,所在的包不一樣--譯者注), 你應該設置auto-import="false"。如果你把一個(gè)?import過(guò)?的名字同時(shí)對應兩個(gè)類(lèi), Hibernate會(huì )拋出一個(gè)異常。
注意hibernate-mapping 元素允許你嵌套多個(gè)如上所示的 <class>映射。但是最好的做法(也許一些工具需要的)是一個(gè) 持久化類(lèi)(或一個(gè)類(lèi)的繼承層次)對應一個(gè)映射文件,并以持久化的超類(lèi)名稱(chēng)命名,例如: Cat.hbm.xml, Dog.hbm.xml,或者如果使用繼承,Animal.hbm.xml。
5.1.3. class
你可以使用class元素來(lái)定義一個(gè)持久化類(lèi):
<class
name="ClassName" (1)
table="tableName" (2)
discriminator-value="discriminator_value" (3)
mutable="true|false" (4)
schema="owner" (5)
catalog="catalog" (6)
proxy="ProxyInterface" (7)
dynamic-update="true|false" (8)
dynamic-insert="true|false" (9)
select-before-update="true|false" (10)
polymorphism="implicit|explicit" (11)
where="arbitrary sql where condition" (12)
persister="PersisterClass" (13)
batch-size="N" (14)
optimistic-lock="none|version|dirty|all" (15)
lazy="true|false" (16)
entity-name="EntityName" (17)
check="arbitrary sql check condition" (18)
rowid="rowid" (19)
subselect="SQL expression" (20)
abstract="true|false" (21)
node="element-name"
/>
(1) name (可選): 持久化類(lèi)(或者接口)的Java全限定名。 如果這個(gè)屬性不存在,Hibernate將假定這是一個(gè)非POJO的實(shí)體映射。
(2) table (可選 - 默認是類(lèi)的非全限定名): 對應的數據庫表名。
(3) discriminator-value (可選 - 默認和類(lèi)名一樣): 一個(gè)用于區分不同的子類(lèi)的值,在多態(tài)行為時(shí)使用。它可以接受的值包括 null 和 not null。
(4) mutable (可選,默認值為true): 表明該類(lèi)的實(shí)例是可變的或者不可變的。
(5) schema (可選): 覆蓋在根<hibernate-mapping>元素中指定的schema名字。
(6) catalog (可選): 覆蓋在根<hibernate-mapping>元素中指定的catalog名字。
(7) proxy (可選): 指定一個(gè)接口,在延遲裝載時(shí)作為代理使用。 你可以在這里使用該類(lèi)自己的名字。
(8) dynamic-update (可選, 默認為 false): 指定用于UPDATE 的SQL將會(huì )在運行時(shí)動(dòng)態(tài)生成,并且只更新那些改變過(guò)的字段。
(9) dynamic-insert (可選, 默認為 false): 指定用于INSERT的 SQL 將會(huì )在運行時(shí)動(dòng)態(tài)生成,并且只包含那些非空值字段。
(10) select-before-update (可選, 默認為 false): 指定Hibernate除非確定對象真正被修改了(如果該值為true-譯注),否則不會(huì )執行SQL UPDATE操作。在特定場(chǎng)合(實(shí)際上,它只在一個(gè)瞬時(shí)對象(transient object)關(guān)聯(lián)到一個(gè) 新的session中時(shí)執行的update()中生效),這說(shuō)明Hibernate會(huì )在UPDATE 之前執行一次額外的SQL SELECT操作,來(lái)決定是否應該執行 UPDATE。
(11) polymorphism(多態(tài)) (可選, 默認值為 implicit (隱式) ): 界定是隱式還是顯式的使用多態(tài)查詢(xún)(這只在Hibernate的具體表繼承策略中用到-譯注)。
(12) where (可選) 指定一個(gè)附加的SQLWHERE 條件, 在抓取這個(gè)類(lèi)的對象時(shí)會(huì )一直增加這個(gè)條件。
(13) persister (可選): 指定一個(gè)定制的ClassPersister。
(14) batch-size (可選,默認是1) 指定一個(gè)用于 根據標識符(identifier)抓取實(shí)例時(shí)使用的"batch size"(批次抓取數量)。
(15) optimistic-lock(樂(lè )觀(guān)鎖定) (可選,默認是version): 決定樂(lè )觀(guān)鎖定的策略。
(16) lazy (可選): 通過(guò)設置lazy="false", 所有的延遲加載(Lazy fetching)功能將被全部禁用(disabled)。
(17) entity-name (可選,默認為類(lèi)名): Hibernate3允許一個(gè)類(lèi)進(jìn)行多次映射( 前提是映射到不同的表),并且允許使用Maps或XML代替Java層次的實(shí)體映射 (也就是實(shí)現動(dòng)態(tài)領(lǐng)域模型,不用寫(xiě)持久化類(lèi)-譯注)。 更多信息請看第 4.4 節 ?動(dòng)態(tài)模型(Dynamic models)? and 第 18 章 XML映射。
(19) rowid (可選): Hibernate可以使用數據庫支持的所謂的ROWIDs,例如: Oracle數據庫,如果你設置這個(gè)可選的rowid, Hibernate可以使用額外的字段rowid實(shí)現快速更新。ROWID是這個(gè)功能實(shí)現的重點(diǎn), 它代表了一個(gè)存儲元組(tuple)的物理位置。
(20) subselect (可選): 它將一個(gè)不可變(immutable)并且只讀的實(shí)體映射到一個(gè)數據庫的 子查詢(xún)中。當你想用視圖代替一張基本表的時(shí)候,這是有用的,但最好不要這樣做。更多的介紹請看下面內容。
(21) abstract (可選): 用于在<union-subclass>的繼承結構 (hierarchies)中標識抽象超類(lèi)。
若指明的持久化類(lèi)實(shí)際上是一個(gè)接口,這也是完全可以接受的。 之后你可以用元素<subclass>來(lái)指定該接口的實(shí)際實(shí)現類(lèi)。 你可以持久化任何static(靜態(tài)的)內部類(lèi)。 你應該使用標準的類(lèi)名格式來(lái)指定類(lèi)名,比如:Foo$Bar。
不可變類(lèi),mutable="false"不可以被應用程序更新或者刪除。 這可以讓Hibernate做一些小小的性能優(yōu)化。
可選的proxy屬性允許延遲加載類(lèi)的持久化實(shí)例。 Hibernate開(kāi)始會(huì )返回實(shí)現了這個(gè)命名接口的CGLIB代理。當代理的某個(gè)方法被實(shí)際調用的時(shí)候, 真實(shí)的持久化對象才會(huì )被裝載。參見(jiàn)下面的?用于延遲裝載的代理?。
Implicit (隱式)的多態(tài)是指,如果查詢(xún)時(shí)給出的是任何超類(lèi)、該類(lèi)實(shí)現的接口或者該類(lèi)的 名字,都會(huì )返回這個(gè)類(lèi)的實(shí)例;如果查詢(xún)中給出的是子類(lèi)的名字,則會(huì )返回子類(lèi)的實(shí)例。 Explicit (顯式)的多態(tài)是指,只有在查詢(xún)時(shí)給出明確的該類(lèi)名字時(shí)才會(huì )返回這個(gè)類(lèi)的實(shí)例; 同時(shí)只有在這個(gè)<class>的定義中作為<subclass> 或者<joined-subclass>出現的子類(lèi),才會(huì )可能返回。 在大多數情況下,默認的polymorphism="implicit"都是合適的。 顯式的多態(tài)在有兩個(gè)不同的類(lèi)映射到同一個(gè)表的時(shí)候很有用。(允許一個(gè)?輕型?的類(lèi),只包含部分表字段)。
persister屬性可以讓你定制這個(gè)類(lèi)使用的持久化策略。 你可以指定你自己實(shí)現 org.hibernate.persister.EntityPersister的子類(lèi),你甚至可以完全從頭開(kāi)始編寫(xiě)一個(gè) org.hibernate.persister.ClassPersister接口的實(shí)現, 比如是用儲存過(guò)程調用、序列化到文件或者LDAP數據庫來(lái)實(shí)現。 參閱org.hibernate.test.CustomPersister,這是一個(gè)簡(jiǎn)單的例子 (?持久化?到一個(gè)Hashtable)。
請注意dynamic-update和dynamic-insert的設置并不會(huì )繼承到子類(lèi), 所以在<subclass>或者<joined-subclass>元素中可能 需要再次設置。這些設置是否能夠提高效率要視情形而定。請用你的智慧決定是否使用。
使用select-before-update通常會(huì )降低性能。如果你重新連接一個(gè)脫管(detache)對象實(shí)例 到一個(gè)Session中時(shí),它可以防止數據庫不必要的觸發(fā)update。 這就很有用了。
如果你打開(kāi)了dynamic-update,你可以選擇幾種樂(lè )觀(guān)鎖定的策略:
? version(版本檢查) 檢查version/timestamp字段
? all(全部) 檢查全部字段
? dirty(臟檢查)只檢察修改過(guò)的字段
? none(不檢查)不使用樂(lè )觀(guān)鎖定
我們非常強烈建議你在Hibernate中使用version/timestamp字段來(lái)進(jìn)行樂(lè )觀(guān)鎖定。 對性能來(lái)說(shuō),這是最好的選擇,并且這也是唯一能夠處理在session外進(jìn)行操作的策略(例如: 在使用Session.merge()的時(shí)候)。
對Hibernate映射來(lái)說(shuō)視圖和表是沒(méi)有區別的,這是因為它們在數據層都是透明的( 注意:一些數據庫不支持視圖屬性,特別是更新的時(shí)候)。有時(shí)你想使用視圖,但卻不能在數據庫 中創(chuàng )建它(例如:在遺留的schema中)。這樣的話(huà),你可以映射一個(gè)不可變的(immutable)并且是 只讀的實(shí)體到一個(gè)給定的SQL子查詢(xún)表達式:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class>
定義這個(gè)實(shí)體用到的表為同步(synchronize),確保自動(dòng)刷新(auto-flush)正確執行, 并且依賴(lài)原實(shí)體的查詢(xún)不會(huì )返回過(guò)期數據。<subselect>在屬性元素 和一個(gè)嵌套映射元素中都可見(jiàn)。
5.1.4. id
被映射的類(lèi)必須定義對應數據庫表主鍵字段。大多數類(lèi)有一個(gè)JavaBeans風(fēng)格的屬性, 為每一個(gè)實(shí)例包含唯一的標識。<id> 元素定義了該屬性到數據庫表主鍵字段的映射。
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="null|any|none|undefined|id_value" (4)
access="field|property|ClassName" (5)
node="
</id>
(1) name (可選): 標識屬性的名字。
(2) type (可選): 標識Hibernate類(lèi)型的名字。
(3) column (可選 - 默認為屬性名): 主鍵字段的名字。
(4) unsaved-value (可選 - 默認為一個(gè)切合實(shí)際(sensible)的值): 一個(gè)特定的標識屬性值,用來(lái)標志該實(shí)例是剛剛創(chuàng )建的,尚未保存。 這可以把這種實(shí)例和從以前的session中裝載過(guò)(可能又做過(guò)修改--譯者注) 但未再次持久化的實(shí)例區分開(kāi)來(lái)。
(5) access (可選 - 默認為property): Hibernate用來(lái)訪(fǎng)問(wèn)屬性值的策略。
如果 name屬性不存在,會(huì )認為這個(gè)類(lèi)沒(méi)有標識屬性。
unsaved-value 屬性在Hibernate3中幾乎不再需要。
還有一個(gè)另外的<composite-id>定義可以訪(fǎng)問(wèn)舊式的多主鍵數據。 我們強烈不建議使用這種方式。
5.1.4.1. Generator
可選的<generator>子元素是一個(gè)Java類(lèi)的名字, 用來(lái)為該持久化類(lèi)的實(shí)例生成唯一的標識。如果這個(gè)生成器實(shí)例需要某些配置值或者初始化參數, 用<param>元素來(lái)傳遞。
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都實(shí)現org.hibernate.id.IdentifierGenerator接口。 這是一個(gè)非常簡(jiǎn)單的接口;某些應用程序可以選擇提供他們自己特定的實(shí)現。當然, Hibernate提供了很多內置的實(shí)現。下面是一些內置生成器的快捷名字:
increment
用于為long, short或者int類(lèi)型生成 唯一標識。只有在沒(méi)有其他進(jìn)程往同一張表中插入數據時(shí)才能使用。 在集群下不要使用。
identity
對DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的內置標識字段提供支持。 返回的標識符是long, short 或者int類(lèi)型的。
sequence
在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的標識符是long, short或者 int類(lèi)型的。
hilo
使用一個(gè)高/低位算法高效的生成long, short 或者 int類(lèi)型的標識符。給定一個(gè)表和字段(默認分別是 hibernate_unique_key 和next_hi)作為高位值的來(lái)源。 高/低位算法生成的標識符只在一個(gè)特定的數據庫中是唯一的。
seqhilo
使用一個(gè)高/低位算法來(lái)高效的生成long, short 或者 int類(lèi)型的標識符,給定一個(gè)數據庫序列(sequence)的名字。
uuid
用一個(gè)128-bit的UUID算法生成字符串類(lèi)型的標識符, 這在一個(gè)網(wǎng)絡(luò )中是唯一的(使用了IP地址)。UUID被編碼為一個(gè)32位16進(jìn)制數字的字符串。
guid
在MS SQL Server 和 MySQL 中使用數據庫生成的GUID字符串。
native
根據底層數據庫的能力選擇identity, sequence 或者hilo中的一個(gè)。
assigned
讓?xiě)贸绦蛟趕ave()之前為對象分配一個(gè)標示符。這是 <generator>元素沒(méi)有指定時(shí)的默認生成策略。
select
通過(guò)數據庫觸發(fā)器選擇一些唯一主鍵的行并返回主鍵值來(lái)分配一個(gè)主鍵。
foreign
使用另外一個(gè)相關(guān)聯(lián)的對象的標識符。通常和<one-to-one>聯(lián)合起來(lái)使用。
5.1.4.2. 高/低位算法(Hi/Lo Algorithm)
hilo 和 seqhilo生成器給出了兩種hi/lo算法的實(shí)現, 這是一種很令人滿(mǎn)意的標識符生成算法。第一種實(shí)現需要一個(gè)?特殊?的數據庫表來(lái)保存下一個(gè)可用的?hi?值。 第二種實(shí)現使用一個(gè)Oracle風(fēng)格的序列(在被支持的情況下)。
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id>
<id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id>
很不幸,你在為Hibernate自行提供Connection時(shí)無(wú)法使用hilo。 當Hibernate使用JTA獲取應用服務(wù)器的數據源連接時(shí),你必須正確地配置 hibernate.transaction.manager_lookup_class。
5.1.4.3. UUID算法(UUID Algorithm )
UUID包含:IP地址,JVM的啟動(dòng)時(shí)間(精確到1/4秒),系統時(shí)間和一個(gè)計數器值(在JVM中唯一)。 在Java代碼中不可能獲得MAC地址或者內存地址,所以這已經(jīng)是我們在不使用JNI的前提下的能做的最好實(shí)現了。
5.1.4.4. 標識字段和序列(Identity columns and Sequences)
對于內部支持標識字段的數據庫(DB2,MySQL,Sybase,MS SQL),你可以使用identity關(guān)鍵字生成。 對于內部支持序列的數據庫(DB2,Oracle, PostgreSQL, Interbase, McKoi,SAP DB), 你可以使用sequence風(fēng)格的關(guān)鍵字生成。 這兩種方式對于插入一個(gè)新的對象都需要兩次SQL查詢(xún)。
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id>
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>
對于跨平臺開(kāi)發(fā),native策略會(huì )從identity, sequence 和hilo中進(jìn)行選擇,選擇哪一個(gè),這取決于底層數據庫的支持能力。
5.1.4.5. 程序分配的標識符(Assigned Identifiers)
如果你需要應用程序分配一個(gè)標示符(而非Hibernate來(lái)生成),你可以使用assigned 生成器。這種特殊的生成器會(huì )使用已經(jīng)分配給對象的標識符屬性的標識符值。 這個(gè)生成器使用一個(gè)自然鍵(natural key,有商業(yè)意義的列-譯注)作為主鍵,而不是使用一個(gè)代理鍵( surrogate key,沒(méi)有商業(yè)意義的列-譯注)。這是沒(méi)有指定<generator>元素時(shí)的默認行為
當選擇assigned生成器時(shí),除非有一個(gè)version或timestamp屬性,或者你定義了 Interceptor.isUnsaved(),否則需要讓Hiberante使用 unsaved-value="undefined",強制Hibernatet查詢(xún)數據庫來(lái)確定一個(gè)實(shí)例是瞬時(shí)的(transient) 還是脫管的(detached)。
5.1.4.6. 觸發(fā)器實(shí)現的主鍵生成器(Primary keys assigned by triggers)
僅僅用于遺留的schema中 (Hibernate不能使用觸發(fā)器生成DDL)。
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>
在上面的例子中,類(lèi)定義了一個(gè)命名為socialSecurityNumber的唯一值屬性, 它是一個(gè)自然鍵(natural key),命名為person_id的代理鍵(surrogate key) 的值由觸發(fā)器生成。
5.1.5. composite-id
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName"
node="element-name|."
>
<key-many-to-one name="propertyName class="ClassName" column="column_name"/>
......
</composite-id>
如果表使用聯(lián)合主鍵,你可以映射類(lèi)的多個(gè)屬性為標識符屬性。 <composite-id>元素接受<key-property> 屬性映射和<key-many-to-one>屬性映射作為子元素。
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>
你的持久化類(lèi)必須重載equals()和 hashCode()方法,來(lái)實(shí)現組合的標識符的相等判斷。 實(shí)現Serializable接口也是必須的。
不幸的是,這種組合關(guān)鍵字的方法意味著(zhù)一個(gè)持久化類(lèi)是它自己的標識。除了對象自己之外, 沒(méi)有什么方便的?把手?可用。你必須初始化持久化類(lèi)的實(shí)例,填充它的標識符屬性,再load() 組合關(guān)鍵字關(guān)聯(lián)的持久狀態(tài)。我們把這種方法稱(chēng)為embedded(嵌入式)的組合標識符,在重要的應用中不鼓勵使用這種用法。
第二種方法我們稱(chēng)為mapped(映射式)組合標識符 (mapped composite identifier),<composite-id>元素中列出的標識屬性不但在持久化類(lèi)出現,還形成一個(gè)獨立的標識符類(lèi)。
<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>
在這個(gè)例子中,組合標識符類(lèi)MedicareId和實(shí)體類(lèi)都含有medicareNumber和dependent屬性。標識符類(lèi)必須重載equals()和hashCode()并且實(shí)現Serializable接口。這種方法的缺點(diǎn)是出現了明顯的代碼重復。
下面列出的屬性是用來(lái)指定一個(gè)映射式組合標識符的:
? mapped (可選, 默認為false): 指明使用一個(gè)映射式組合標識符,其包含的屬性映射同時(shí)在實(shí)體類(lèi)和組合標識符類(lèi)中出現。
? class (可選,但對映射式組合標識符必須指定): 作為組合標識符類(lèi)使用的類(lèi)名.
在第 8.4 節 ?組件作為聯(lián)合標識符(Components as composite identifiers)?一節中,我們會(huì )描述第三種方式,那就是把組合標識符實(shí)現為一個(gè)組件(component)類(lèi),這是更方便的方法。下面的屬性?xún)H對第三種方法有效:
? name (可選,但對這種方法而言必須): 包含此組件標識符的組件類(lèi)型的名字 (參閱第9章).
? access (可選 - 默認為property): Hibernate應該使用的訪(fǎng)問(wèn)此屬性值的策略
? class (可選 - 默認會(huì )用反射來(lái)自動(dòng)判定屬性類(lèi)型 ): 用來(lái)作為組合標識符的組件類(lèi)的類(lèi)名(參閱下一節)
第三種方式,被稱(chēng)為identifier component(標識符組件)是我們對幾乎所有應用都推薦使用的方式。
5.1.6. 鑒別器(discriminator)
在"一棵對象繼承樹(shù)對應一個(gè)表"的策略中,<discriminator>元素是必需的, 它定義了表的鑒別器字段。鑒別器字段包含標志值,用于告知持久化層應該為某個(gè)特定的行創(chuàng )建哪一個(gè)子類(lèi)的實(shí)例。 如下這些受到限制的類(lèi)型可以使用: string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator
column="discriminator_column" (1)
type="discriminator_type" (2)
force="true|false" (3)
insert="true|false" (4)
formula="arbitrary sql expression" (5)
/>
(1) column (可選 - 默認為 class) 鑒別器字段的名字
(2) type (可選 - 默認為 string) 一個(gè)Hibernate字段類(lèi)型的名字
(3) force(強制) (可選 - 默認為 false) "強制"Hibernate指定允許的鑒別器值,即使當取得的所有實(shí)例都是根類(lèi)的。
(4) insert (可選 - 默認為true) 如果你的鑒別器字段也是映射為復合標識(composite identifier)的一部分,則需將 這個(gè)值設為false。(告訴Hibernate在做SQL INSERT 時(shí)不包含該列)
(5) formula (可選) 一個(gè)SQL表達式,在類(lèi)型判斷(判斷是父類(lèi)還是具體子類(lèi)-譯注)時(shí)執行??捎糜诨趦热莸蔫b別器。
鑒別器字段的實(shí)際值是根據<class>和<subclass>元素中 的discriminator-value屬性得來(lái)的。
force屬性?xún)H僅在這種情況下有用的:表中包含沒(méi)有被映射到持久化類(lèi)的附加辨別器值。 這種情況不會(huì )經(jīng)常遇到。
使用formula屬性你可以定義一個(gè)SQL表達式,用來(lái)判斷一個(gè)行數據的類(lèi)型。
<discriminator
formula="case when CLASS_TYPE in (‘a(chǎn)‘, ‘b‘, ‘c‘) then 0 else 1 end"
type="integer"/>
5.1.7. 版本(version)(可選)
<version>元素是可選的,表明表中包含附帶版本信息的數據。 這在你準備使用 長(cháng)事務(wù)(long transactions)的時(shí)候特別有用。(見(jiàn)后)
<version
column="version_column" (1)
name="propertyName" (2)
type="typename" (3)
access="field|property|ClassName" (4)
unsaved-value="null|negative|undefined" (5)
generated="never|always" (6)
insert="true|false" (7)
node="/>
(1) column (可選 - 默認為屬性名): 指定持有版本號的字段名。
(2) name: 持久化類(lèi)的屬性名。
(3) type (可選 - 默認是 integer): 版本號的類(lèi)型。
(4) access (可選 - 默認是 property): Hibernate用于訪(fǎng)問(wèn)屬性值的策略。
(5) unsaved-value (可選 - 默認是undefined): 用于標明某個(gè)實(shí)例時(shí)剛剛被實(shí)例化的(尚未保存)版本屬性值,依靠這個(gè)值就可以把這種情況 和已經(jīng)在先前的session中保存或裝載的脫管(detached)實(shí)例區分開(kāi)來(lái)。 (undefined指明應被使用的標識屬性值。)
(6) generated (可選 - 默認是 never): 表明此版本屬性值是否實(shí)際上是由數據庫生成的。請參閱第 5.6 節 ?數據庫生成屬性(Generated Properties)?部分的討論。
(7) insert (可選 - 默認是 true): 表明此版本列應該包含在SQL插入語(yǔ)句中。只有當數據庫字段有默認值0的時(shí)候,才可以設置為false。
版本號必須是以下類(lèi)型:long, integer, short, timestamp或者calendar。
一個(gè)脫管(detached)實(shí)例的version或timestamp屬性不能為空(null),因為Hibernate不管 unsaved-value被指定為何種策略,它將任何屬性為空的version或timestamp 實(shí)例看作為瞬時(shí)(transient)實(shí)例。 避免Hibernate中的傳遞重附(transitive reattachment)問(wèn)題的一個(gè)簡(jiǎn)單方法是 定義一個(gè)不能為空的version或timestamp屬性,特別是在人們使用程序分配的標識符(assigned identifiers) 或復合主鍵時(shí)非常有用!
5.1.8. timestamp (可選)
可選的<timestamp>元素指明了表中包含時(shí)間戳數據。 這用來(lái)作為版本的替代。時(shí)間戳本質(zhì)上是一種對樂(lè )觀(guān)鎖定的一種不是特別安全的實(shí)現。當然, 有時(shí)候應用程序可能在其他方面使用時(shí)間戳。
<timestamp
column="timestamp_column" (1)
name="propertyName" (2)
access="field|property|ClassName" (3)
unsaved-value="null|undefined" (4)
source="vm|db" (5)
generated="never|always" (6)
node="/>
(1) column (可選 - 默認為屬性名): 持有時(shí)間戳的字段名。
(2) name: 在持久化類(lèi)中的JavaBeans風(fēng)格的屬性名, 其Java類(lèi)型是 Date 或者 Timestamp的。
(3) access (可選 - 默認是 property): Hibernate用于訪(fǎng)問(wèn)屬性值的策略。
(4) unsaved-value (可選 - 默認是null): 用于標明某個(gè)實(shí)例時(shí)剛剛被實(shí)例化的(尚未保存)版本屬性值,依靠這個(gè)值就可以把這種情況和 已經(jīng)在先前的session中保存或裝載的脫管(detached)實(shí)例區分開(kāi)來(lái)。(undefined 指明使用標識屬性值進(jìn)行這種判斷。)
(5) source (可選 - 默認是 vm): Hibernate如何才能獲取到時(shí)間戳的值呢?從數據庫,還是當前JVM?從數據庫獲取會(huì )帶來(lái)一些負擔,因為Hibernate必須訪(fǎng)問(wèn)數據庫來(lái)獲得?下一個(gè)值?,但是在集群環(huán)境中會(huì )更安全些。還要注意,并不是所有的Dialect(方言)都支持獲得數據庫的當前時(shí)間戳的,而支持的數據庫中又有一部分因為精度不足,用于鎖定是不安全的(例如Oracle 8)。
(6) generated (可選 - 默認是 never): 指出時(shí)間戳值是否實(shí)際上是由數據庫生成的.請參閱第 5.6 節 ?數據庫生成屬性(Generated Properties)?的討論。
5.1.9. property
<property>元素為類(lèi)定義了一個(gè)持久化的,JavaBean風(fēng)格的屬性。
<property
name="propertyName" (1)
column="column_name" (2)
type="typename" (3)
update="true|false" (4)
insert="true|false" (4)
formula="arbitrary SQL expression" (5)
access="field|property|ClassName" (6)
lazy="true|false" (7)
unique="true|false" (8)
not-null="true|false" (9)
optimistic-lock="true|false" (10)
generated="never|insert|always" (11)
node="
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
(1) name: 屬性的名字,以小寫(xiě)字母開(kāi)頭。
(2) column (可選 - 默認為屬性名字): 對應的數據庫字段名。 也可以通過(guò)嵌套的<column>元素指定。
(3) type (可選): 一個(gè)Hibernate類(lèi)型的名字。
(4) update, insert (可選 - 默認為 true) : 表明用于UPDATE 和/或 INSERT 的SQL語(yǔ)句中是否包含這個(gè)被映射了的字段。這二者如果都設置為false 則表明這是一個(gè)?外源性(derived)?的屬性,它的值來(lái)源于映射到同一個(gè)(或多個(gè)) 字段的某些其他屬性,或者通過(guò)一個(gè)trigger(觸發(fā)器)或其他程序生成。
(5) formula (可選): 一個(gè)SQL表達式,定義了這個(gè)計算 (computed) 屬性的值。計算屬性沒(méi)有和它對應的數據庫字段。
(6) access (可選 - 默認值為 property): Hibernate用來(lái)訪(fǎng)問(wèn)屬性值的策略。
(7) lazy (可選 - 默認為 false): 指定 指定實(shí)例變量第一次被訪(fǎng)問(wèn)時(shí),這個(gè)屬性是否延遲抓?。╢etched lazily)( 需要運行時(shí)字節碼增強)。
(8) unique (可選): 使用DDL為該字段添加唯一的約束。 同樣,允許它作為property-ref引用的目標。
(9) not-null (可選): 使用DDL為該字段添加可否為空(nullability)的約束。
(10) optimistic-lock (可選 - 默認為 true): 指定這個(gè)屬性在做更新時(shí)是否需要獲得樂(lè )觀(guān)鎖定(optimistic lock)。 換句話(huà)說(shuō),它決定這個(gè)屬性發(fā)生臟數據時(shí)版本(version)的值是否增長(cháng)。
(11) generated (可選 - 默認為 never): 表明此屬性值是否實(shí)際上是由數據庫生成的。請參閱第 5.6 節 ?數據庫生成屬性(Generated Properties)?的討論。
typename可以是如下幾種:
? Hibernate基本類(lèi)型名(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。
? 一個(gè)Java類(lèi)的名字,這個(gè)類(lèi)屬于一種默認基礎類(lèi)型 (比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
? 一個(gè)可以序列化的Java類(lèi)的名字。
? 一個(gè)自定義類(lèi)型的類(lèi)的名字。(比如: com.illflow.type.MyCustomType)。
如果你沒(méi)有指定類(lèi)型,Hibernarte會(huì )使用反射來(lái)得到這個(gè)名字的屬性,以此來(lái)猜測正確的Hibernate類(lèi)型。 Hibernate會(huì )按照規則2,3,4的順序對屬性讀取器(getter方法)的返回類(lèi)進(jìn)行解釋。然而,這還不夠。 在某些情況下你仍然需要type屬性。(比如,為了區別Hibernate.DATE 和Hibernate.TIMESTAMP,或者為了指定一個(gè)自定義類(lèi)型。)
access屬性用來(lái)讓你控制Hibernate如何在運行時(shí)訪(fǎng)問(wèn)屬性。在默認情況下, Hibernate會(huì )使用屬性的get/set方法對(pair)。如果你指明access="field", Hibernate會(huì )忽略get/set方法對,直接使用反射來(lái)訪(fǎng)問(wèn)成員變量。你也可以指定你自己的策略, 這就需要你自己實(shí)現org.hibernate.property.PropertyAccessor接口, 再在access中設置你自定義策略類(lèi)的名字。
衍生屬性(derive propertie)是一個(gè)特別強大的特征。這些屬性應該定義為只讀,屬性值在裝載時(shí)計算生成。 你用一個(gè)SQL表達式生成計算的結果,它會(huì )在這個(gè)實(shí)例轉載時(shí)翻譯成一個(gè)SQL查詢(xún)的SELECT 子查詢(xún)語(yǔ)句。
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
注意,你可以使用實(shí)體自己的表,而不用為這個(gè)特別的列定義別名( 上面例子中的customerId)。同時(shí)注意,如果你不喜歡使用屬性, 你可以使用嵌套的<formula>映射元素。
5.1.10. 多對一(many-to-one)
通過(guò)many-to-one元素,可以定義一種常見(jiàn)的與另一個(gè)持久化類(lèi)的關(guān)聯(lián)。 這種關(guān)系模型是多對一關(guān)聯(lián)(實(shí)際上是一個(gè)對象引用-譯注):這個(gè)表的一個(gè)外鍵引用目標表的 主鍵字段。
<many-to-one
name="propertyName" (1)
column="column_name" (2)
class="ClassName" (3)
cascade="cascade_style" (4)
fetch="join|select" (5)
update="true|false" (6)
insert="true|false" (6)
property-ref="propertyNameFromAssociatedClass" (7)
access="field|property|ClassName" (8)
unique="true|false" (9)
not-null="true|false" (10)
optimistic-lock="true|false" (11)
lazy="proxy|no-proxy|false" (12)
not-found="ignore|exception" (13)
entity-name="EntityName" (14)
formula="arbitrary SQL expression" (15)
node="
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
(1) name: 屬性名。
(2) column (可選): 外間字段名。它也可以通過(guò)嵌套的 <column>元素指定。
(3) class (可選 - 默認是通過(guò)反射得到屬性類(lèi)型): 關(guān)聯(lián)的類(lèi)的名字。
(4) cascade(級聯(lián)) (可選): 指明哪些操作會(huì )從父對象級聯(lián)到關(guān)聯(lián)的對象。
(5) fetch (可選 - 默認為 select): 在外連接抓?。╫uter-join fetching)和序列選擇抓?。╯equential select fetching)兩者中選擇其一。
(6) update, insert (可選 - 默認為 true) 指定對應的字段是否包含在用于UPDATE 和/或 INSERT 的SQL語(yǔ)句中。如果二者都是false,則這是一個(gè)純粹的 ?外源性(derived)?關(guān)聯(lián),它的值是通過(guò)映射到同一個(gè)(或多個(gè))字段的某些其他屬性得到 或者通過(guò)trigger(觸發(fā)器)、或其他程序生成。
(6) property-ref: (可選) 指定關(guān)聯(lián)類(lèi)的一個(gè)屬性,這個(gè)屬性將會(huì )和本外鍵相對應。 如果沒(méi)有指定,會(huì )使用對方關(guān)聯(lián)類(lèi)的主鍵。
(7) access (可選 - 默認是 property): Hibernate用來(lái)訪(fǎng)問(wèn)屬性的策略。
(8) unique (可選): 使用DDL為外鍵字段生成一個(gè)唯一約束。此外, 這也可以用作property-ref的目標屬性。這使關(guān)聯(lián)同時(shí)具有 一對一的效果。
(9) not-null (可選): 使用DDL為外鍵字段生成一個(gè)非空約束。
(10) optimistic-lock (可選 - 默認為 true): 指定這個(gè)屬性在做更新時(shí)是否需要獲得樂(lè )觀(guān)鎖定(optimistic lock)。 換句話(huà)說(shuō),它決定這個(gè)屬性發(fā)生臟數據時(shí)版本(version)的值是否增長(cháng)。
(11) lazy (可選 - 默認為 proxy): 默認情況下,單點(diǎn)關(guān)聯(lián)是經(jīng)過(guò)代理的。lazy="no-proxy"指定此屬性應該在實(shí)例變量第一次被訪(fǎng)問(wèn)時(shí)應該延遲抓?。╢etche lazily)(需要運行時(shí)字節碼的增強)。 lazy="false"指定此關(guān)聯(lián)總是被預先抓取。
(12) not-found (可選 - 默認為 exception): 指定外鍵引用的數據不存在時(shí)如何處理: ignore會(huì )將行數據不存在視為一個(gè)空(null)關(guān)聯(lián)。
(13) entity-name (可選): 被關(guān)聯(lián)的類(lèi)的實(shí)體名。
(14) formula (可選): SQL表達式,用于定義computed(計算出的)外鍵值。
cascade屬性設置為除了none以外任何有意義的值, 它將把特定的操作傳遞到關(guān)聯(lián)對象中。這個(gè)值就代表著(zhù)Hibernate基本操作的名稱(chēng), persist, merge, delete, save-update, evict, replicate, lock, refresh, 以及特別的值delete-orphan和all,并且可以用逗號分隔符 來(lái)組合這些操作,例如,cascade="persist,merge,evict"或 cascade="all,delete-orphan"。更全面的解釋請參考第 10.11 節 ?傳播性持久化(transitive persistence)?. 注意,單值關(guān)聯(lián) (many-to-one 和 one-to-one 關(guān)聯(lián)) 不支持刪除孤兒(orphan delete,刪除不再被引用的值).
一個(gè)典型的簡(jiǎn)單many-to-one定義例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref屬性只應該用來(lái)對付遺留下來(lái)的數據庫系統, 可能有外鍵指向對方關(guān)聯(lián)表的是個(gè)非主鍵字段(但是應該是一個(gè)惟一關(guān)鍵字)的情況下。 這是一種十分丑陋的關(guān)系模型。比如說(shuō),假設Product類(lèi)有一個(gè)惟一的序列號, 它并不是主鍵。(unique屬性控制Hibernate通過(guò)SchemaExport工具進(jìn)行的DDL生成。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那么關(guān)于OrderItem 的映射可能是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
當然,我們決不鼓勵這種用法。
如果被引用的唯一主鍵由關(guān)聯(lián)實(shí)體的多個(gè)屬性組成,你應該在名稱(chēng)為<properties>的元素 里面映射所有關(guān)聯(lián)的屬性。
假若被引用的唯一主鍵是組件的屬性,你可以指定屬性路徑:
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
5.1.11. 一對一
持久化對象之間一對一的關(guān)聯(lián)關(guān)系是通過(guò)one-to-one元素定義的。
<one-to-one
name="propertyName" (1)
class="ClassName" (2)
cascade="cascade_style" (3)
constrained="true|false" (4)
fetch="join|select" (5)
property-ref="propertyNameFromAssociatedClass" (6)
access="field|property|ClassName" (7)
formula="any SQL expression" (8)
lazy="proxy|no-proxy|false" (9)
entity-name="EntityName" (10)
node="
foreign-key="foreign_key_name"
/>
(1) name: 屬性的名字。
(2) class (可選 - 默認是通過(guò)反射得到的屬性類(lèi)型):被關(guān)聯(lián)的類(lèi)的名字。
(3) cascade(級聯(lián)) (可選) 表明操作是否從父對象級聯(lián)到被關(guān)聯(lián)的對象。
(4) constrained(約束) (可選) 表明該類(lèi)對應的表對應的數據庫表,和被關(guān)聯(lián)的對象所對應的數據庫表之間,通過(guò)一個(gè)外鍵引用對主鍵進(jìn)行約束。 這個(gè)選項影響save()和delete()在級聯(lián)執行時(shí)的先后順序以及 決定該關(guān)聯(lián)能否被委托(也在schema export tool中被使用).
(5) fetch (可選 - 默認設置為選擇): 在外連接抓取或者序列選擇抓取選擇其一.
(6) property-ref: (可選) 指定關(guān)聯(lián)類(lèi)的屬性名,這個(gè)屬性將會(huì )和本類(lèi)的主鍵相對應。如果沒(méi)有指定,會(huì )使用對方關(guān)聯(lián)類(lèi)的主鍵。
(7) access (可選 - 默認是 property): Hibernate用來(lái)訪(fǎng)問(wèn)屬性的策略。
(8) formula (可選):絕大多數一對一的關(guān)聯(lián)都指向其實(shí)體的主鍵。在一些少見(jiàn)的情況中, 你可能會(huì )指向其他的一個(gè)或多個(gè)字段,或者是一個(gè)表達式,這些情況下,你可以用一個(gè)SQL公式來(lái)表示。 (可以在org.hibernate.test.onetooneformula找到例子)
(9) lazy (可選 - 默認為 proxy): 默認情況下,單點(diǎn)關(guān)聯(lián)是經(jīng)過(guò)代理的。lazy="no-proxy"指定此屬性應該在實(shí)例變量第一次被訪(fǎng)問(wèn)時(shí)應該延遲抓?。╢etche lazily)(需要運行時(shí)字節碼的增強)。 lazy="false"指定此關(guān)聯(lián)總是被預先抓取。注意,如果constrained="false", 不可能使用代理,Hibernate會(huì )采取預先抓??!
(10) entity-name (可選): 被關(guān)聯(lián)的類(lèi)的實(shí)體名。
有兩種不同的一對一關(guān)聯(lián):
? 主鍵關(guān)聯(lián)
? 惟一外鍵關(guān)聯(lián)
主鍵關(guān)聯(lián)不需要額外的表字段;如果兩行是通過(guò)這種一對一關(guān)系相關(guān)聯(lián)的,那么這兩行就共享同樣的主關(guān)鍵字值。所以如果你希望兩個(gè)對象通過(guò)主鍵一對一關(guān)聯(lián),你必須確認它們被賦予同樣的標識值!
比如說(shuō),對下面的Employee和Person進(jìn)行主鍵一對一關(guān)聯(lián):
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
現在我們必須確保PERSON和EMPLOYEE中相關(guān)的字段是相等的。我們使用一個(gè)被成為foreign的特殊的hibernate標識符生成策略:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class>
一個(gè)剛剛保存的Person實(shí)例被賦予和該Person的employee屬性所指向的Employee實(shí)例同樣的關(guān)鍵字值。
另一種方式是一個(gè)外鍵和一個(gè)惟一關(guān)鍵字對應,上面的Employee和Person的例子,如果使用這種關(guān)聯(lián)方式,可以表達成:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
如果在Person的映射加入下面幾句,這種關(guān)聯(lián)就是雙向的:
<one-to-one name"employee" class="Employee" property-ref="person"/>
5.1.12. 自然ID(natural-id)
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id>
我們建議使用代用鍵(鍵值不具備實(shí)際意義)作為主鍵,我們仍然應該嘗試為所有的實(shí)體采用自然的鍵值作為(附加——譯者注)標示。自然鍵(natural key)是單個(gè)或組合屬性,他們必須唯一且非空。如果它還是不可變的那就更理想了。在<natural-id>元素中列出自然鍵的屬性。Hibernate會(huì )幫你生成必須的唯一鍵值和非空約束,你的映射會(huì )更加的明顯易懂(原文是self-documenting,自我注解)。
我們強烈建議你實(shí)現equals() 和hashCode()方法,來(lái)比較實(shí)體的自然鍵屬性。
這一映射不是為了把自然鍵作為主鍵而準備的。
? mutable (可選, 默認為false): 默認情況下,自然標識屬性被假定為不可變的(常量)。
5.1.13. 組件(component), 動(dòng)態(tài)組件(dynamic-component)
<component>元素把子對象的一些元素與父類(lèi)對應的表的一些字段映射起來(lái)。 然后組件可以定義它們自己的屬性、組件或者集合。參見(jiàn)后面的?Components?一章。
<component
name="propertyName" (1)
class="className" (2)
insert="true|false" (3)
update="true|false" (4)
access="field|property|ClassName" (5)
lazy="true|false" (6)
optimistic-lock="true|false" (7)
unique="true|false" (8)
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component>
(1) name: 屬性名
(2) class (可選 - 默認為通過(guò)反射得到的屬性類(lèi)型):組件(子)類(lèi)的名字。
(3) insert: 被映射的字段是否出現在SQL的INSERT語(yǔ)句中?
(4) update: 被映射的字段是否出現在SQL的UPDATE語(yǔ)句中?
(5) access (可選 - 默認是 property): Hibernate用來(lái)訪(fǎng)問(wèn)屬性的策略。
(6) lazy (可選 - 默認是 false): 表明此組件應在實(shí)例變量第一次被訪(fǎng)問(wèn)的時(shí)候延遲加載(需要編譯時(shí)字節碼裝置器)
(7) optimistic-lock (可選 - 默認是 true):表明更新此組件是否需要獲取樂(lè )觀(guān)鎖。換句話(huà)說(shuō),當這個(gè)屬性變臟時(shí),是否增加版本號(Version)
(8) unique (可選 - 默認是 false):表明組件映射的所有字段上都有唯一性約束
其<property>子標簽為子類(lèi)的一些屬性與表字段之間建立映射。
<component>元素允許加入一個(gè)<parent>子元素,在組件類(lèi)內部就可以有一個(gè)指向其容器的實(shí)體的反向引用。
<dynamic-component>元素允許把一個(gè)Map映射為組件,其屬性名對應map的鍵值。 參見(jiàn)第 8.5 節 ?動(dòng)態(tài)組件 (Dynamic components)?.
5.1.14. properties
<properties> 元素允許定義一個(gè)命名的邏輯分組(grouping)包含一個(gè)類(lèi)中的多個(gè)屬性。 這個(gè)元素最重要的用處是允許多個(gè)屬性的組合作為property-ref的目標(target)。 這也是定義多字段唯一約束的一種方便途徑。
<properties
name="logicalName" (1)
insert="true|false" (2)
update="true|false" (3)
optimistic-lock="true|false" (4)
unique="true|false" (5)
>
<property ...../>
<many-to-one .... />
........
</properties>
(1) name: 分組的邏輯名稱(chēng) - 不是 實(shí)際屬性的名稱(chēng).
(2) insert: 被映射的字段是否出現在SQL的 INSERT語(yǔ)句中?
(3) update: 被映射的字段是否出現在SQL的 UPDATE語(yǔ)句中?
(4) optimistic-lock (可選 - 默認是 true):表明更新此組件是否需要獲取樂(lè )觀(guān)鎖。換句話(huà)說(shuō),當這個(gè)屬性變臟時(shí),是否增加版本號(Version)
(5) unique (可選 - 默認是 false):表明組件映射的所有字段上都有唯一性約束
例如,如果我們有如下的<properties>映射:
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class>
然后,我們可能有一些遺留的數據關(guān)聯(lián),引用 Person表的這個(gè)唯一鍵,而不是主鍵。
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>
我們并不推薦這樣使用,除非在映射遺留數據的情況下。
5.1.15. 子類(lèi)(subclass)
最后,多態(tài)持久化需要為父類(lèi)的每個(gè)子類(lèi)都進(jìn)行定義。對于?每一棵類(lèi)繼承樹(shù)對應一個(gè)表?的策略來(lái)說(shuō),就需要使用<subclass>定義。
<subclass
name="ClassName" (1)
discriminator-value="discriminator_value" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
.....
</subclass>
(1) name: 子類(lèi)的全限定名。
(2) discriminator-value(辨別標志) (可選 - 默認為類(lèi)名):一個(gè)用于區分每個(gè)獨立的子類(lèi)的值。
(3) proxy(代理) (可選): 指定一個(gè)類(lèi)或者接口,在延遲裝載時(shí)作為代理使用。
(4) lazy (可選, 默認是true): 設置為 lazy="false" 禁止使用延遲抓取
每個(gè)子類(lèi)都應該定義它自己的持久化屬性和子類(lèi)。 <version> 和<id> 屬性可以從根父類(lèi)繼承下來(lái)。在一棵繼承樹(shù)上的每個(gè)子類(lèi)都必須定義一個(gè)唯一的discriminator-value。如果沒(méi)有指定,就會(huì )使用Java類(lèi)的全限定名。
更多關(guān)于繼承映射的信息, 參考 第 9 章 繼承映射(Inheritance Mappings)章節.
5.1.16. 連接的子類(lèi)(joined-subclass)
此外,每個(gè)子類(lèi)可能被映射到他自己的表中(每個(gè)子類(lèi)一個(gè)表的策略)。被繼承的狀態(tài)通過(guò)和超類(lèi)的表關(guān)聯(lián)得到。我們使用<joined-subclass>元素。
<joined-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
.....
</joined-subclass>
(1) name: 子類(lèi)的全限定名。
(2) table: 子類(lèi)的表名.
(3) proxy (可選): 指定一個(gè)類(lèi)或者接口,在延遲裝載時(shí)作為代理使用。
(4) lazy (可選, 默認是 true): 設置為 lazy="false" 禁止使用延遲裝載。
這種映射策略不需要指定辨別標志(discriminator)字段。但是,每一個(gè)子類(lèi)都必須使用<key>元素指定一個(gè)表字段來(lái)持有對象的標識符。本章開(kāi)始的映射可以被用如下方式重寫(xiě):
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<!-- mapping for Dog could go here -->
</class>
更多關(guān)于繼承映射的信息,參考第 9 章 繼承映射(Inheritance Mappings)。
5.1.17. 聯(lián)合子類(lèi)(union-subclass)
第三種選擇是僅僅映射類(lèi)繼承樹(shù)中具體類(lèi)部分到表中(每個(gè)具體類(lèi)一張表的策略)。其中,每張表定義了類(lèi)的所有持久化狀態(tài),包括繼承的狀態(tài)。在 Hibernate 中,并不需要完全顯式地映射這樣的繼承樹(shù)。你可以簡(jiǎn)單地使用單獨的<class>定義映射每個(gè)類(lèi)。然而,如果你想使用多態(tài)關(guān)聯(lián)(例如,一個(gè)對類(lèi)繼承樹(shù)中超類(lèi)的關(guān)聯(lián)),你需要使用<union-subclass>映射。
<union-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
.....
</union-subclass>
(1) name: 子類(lèi)的全限定名。
(2) table: 子類(lèi)的表名
(3) proxy (可選): 指定一個(gè)類(lèi)或者接口,在延遲裝載時(shí)作為代理使用。
(4) lazy (可選, 默認是 true): 設置為 lazy="false" 禁止使用延遲裝載。
這種映射策略不需要指定辨別標志(discriminator)字段。
更多關(guān)于繼承映射的信息,參考第 9 章 繼承映射(Inheritance Mappings)。
5.1.18. 連接(join)
使用 <join> 元素,可以將一個(gè)類(lèi)的屬性映射到多張表中。
<join
table="tablename" (1)
schema="owner" (2)
catalog="catalog" (3)
fetch="join|select" (4)
inverse="true|false" (5)
optional="true|false"> (6)
<key ... />
<property ... />
...
</join>
(1) table: 被連接表的名稱(chēng)。
(2) schema (可選):覆蓋由根<hibernate-mapping>元素指定的模式名稱(chēng)。
(3) catalog (可選): 覆蓋由根 <hibernate-mapping>元素指定的目錄名稱(chēng)。
(4) fetch (可選 - 默認是 join): 如果設置為默認值join, Hibernate 將使用一個(gè)內連接來(lái)得到這個(gè)類(lèi)或其超類(lèi)定義的<join>,而使用一個(gè)外連接來(lái)得到其子類(lèi)定義的<join>。如果設置為select,則 Hibernate 將為子類(lèi)定義的 <join>使用順序選擇。這僅在一行數據表示一個(gè)子類(lèi)的對象的時(shí)候才會(huì )發(fā)生。對這個(gè)類(lèi)和其超類(lèi)定義的<join>,依然會(huì )使用內連接得到。
(5) inverse (可選 - 默認是 false): 如果打開(kāi),Hibernate 不會(huì )插入或者更新此連接定義的屬性。
(6) optional (可選 - 默認是 false): 如果打開(kāi),Hibernate 只會(huì )在此連接定義的屬性非空時(shí)插入一行數據,并且總是使用一個(gè)外連接來(lái)得到這些屬性。
例如,一個(gè)人(person)的地址(address)信息可以被映射到單獨的表中(并保留所有屬性的值類(lèi)型語(yǔ)義):
<class name="Person"
table="PERSON">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...
此特性常常對遺留數據模型有用,我們推薦表個(gè)數比類(lèi)個(gè)數少,以及細粒度的領(lǐng)域模型。然而,在單獨的繼承樹(shù)上切換繼承映射策略是有用的,后面會(huì )解釋這點(diǎn)。
5.1.19. 鍵(key)
我們目前已經(jīng)見(jiàn)到過(guò)<key>元素多次了。 這個(gè)元素在父映射元素定義了對新表的連接,并且在被連接表中定義了一個(gè)外鍵引用原表的主鍵的情況下經(jīng)常使用。
<key
column="columnname" (1)
on-delete="noaction|cascade" (2)
property-ref="propertyName" (3)
not-null="true|false" (4)
update="true|false" (5)
unique="true|false" (6)
/>
(1) column (可選): 外鍵字段的名稱(chēng)。也可以通過(guò)嵌套的 <column>指定。
(2) on-delete (可選, 默認是 noaction): 表明外鍵關(guān)聯(lián)是否打開(kāi)數據庫級別的級聯(lián)刪除。
(3) property-ref (可選): 表明外鍵引用的字段不是原表的主鍵(提供給遺留數據)。
(4) not-null (可選): 表明外鍵的字段不可為空(這意味著(zhù)無(wú)論何時(shí)外鍵都是主鍵的一部分)。
(5) update (可選): 表明外鍵決不應該被更新(這意味著(zhù)無(wú)論何時(shí)外鍵都是主鍵的一部分)。
(6) unique (可選): 表明外鍵應有唯一性約束 (這意味著(zhù)無(wú)論何時(shí)外鍵都是主鍵的一部分)。
對那些看重刪除性能的系統,我們推薦所有的鍵都應該定義為on-delete="cascade",這樣 Hibernate 將使用數據庫級的ON CASCADE DELETE約束,而不是多個(gè)DELETE語(yǔ)句。 注意,這個(gè)特性會(huì )繞過(guò) Hibernate 通常對版本數據(versioned data)采用的樂(lè )觀(guān)鎖策略。
not-null 和 update 屬性在映射單向一對多關(guān)聯(lián)的時(shí)候有用。如果你映射一個(gè)單向一對多關(guān)聯(lián)到非空的(non-nullable)外鍵,你必須 用<key not-null="true">定義此鍵字段。
5.1.20. 字段和規則元素(column and formula elements)
任何接受column屬性的映射元素都可以選擇接受<column> 子元素。同樣的,formula子元素也可以替換<formula>屬性。
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"
default="SQL expression"/>
<formula>SQL expression</formula>
column 和 formula 屬性甚至可以在同一個(gè)屬性或關(guān)聯(lián)映射中被合并來(lái)表達,例如,一些奇異的連接條件。
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>‘MAILING‘</formula>
</many-to-one>
5.1.21. 引用(import)
假設你的應用程序有兩個(gè)同樣名字的持久化類(lèi),但是你不想在Hibernate查詢(xún)中使用他們的全限定名。除了依賴(lài)auto-import="true"以外,類(lèi)也可以被顯式地?import(引用)?。你甚至可以引用沒(méi)有被明確映射的類(lèi)和接口。
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName" (1)
rename="ShortName" (2)
/>
(1) class: 任何Java類(lèi)的全限定名。
(2) rename (可選 - 默認為類(lèi)的全限定名): 在查詢(xún)語(yǔ)句中可以使用的名字。
5.1.22. any
這是屬性映射的又一種類(lèi)型。<any> 映射元素定義了一種從多個(gè)表到類(lèi)的多態(tài)關(guān)聯(lián)。這種類(lèi)型的映射常常需要多于一個(gè)字段。第一個(gè)字段持有被關(guān)聯(lián)實(shí)體的類(lèi)型,其他的字段持有標識符。對這種類(lèi)型的關(guān)聯(lián)來(lái)說(shuō),不可能指定一個(gè)外鍵約束,所以這當然不是映射(多態(tài))關(guān)聯(lián)的通常的方式。你只應該在非常特殊的情況下使用它(比如,審計log,用戶(hù)會(huì )話(huà)數據等等)。
meta-type 屬性使得應用程序能指定一個(gè)將數據庫字段的值映射到持久化類(lèi)的自定義類(lèi)型。這個(gè)持久化類(lèi)包含有用id-type指定的標識符屬性。 你必須指定從meta-type的值到類(lèi)名的映射。
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any>
<any
name="propertyName" (1)
id-type="idtypename" (2)
meta-type="metatypename" (3)
cascade="cascade_style" (4)
access="field|property|ClassName" (5)
optimistic-lock="true|false" (6)
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any>
(1) name: 屬性名
(2) id-type: 標識符類(lèi)型
(3) meta-type (可選 -默認是 string): 允許辨別標志(discriminator)映射的任何類(lèi)型
(4) cascade (可選 -默認是none): 級聯(lián)的類(lèi)型
(5) access (可選 -默認是 property): Hibernate 用來(lái)訪(fǎng)問(wèn)屬性值的策略。
(6) optimistic-lock (可選 -默認是 true): 表明更新此組件是否需要獲取樂(lè )觀(guān)鎖。換句話(huà)說(shuō),當這個(gè)屬性變臟時(shí),是否增加版本號(Version)
5.2. Hibernate 的類(lèi)型
5.2.1. 實(shí)體(Entities)和值(values)
為了理解很多與持久化服務(wù)相關(guān)的Java語(yǔ)言級對象的行為,我們需要把它們分為兩類(lèi):
實(shí)體entity 獨立于任何持有實(shí)體引用的對象。與通常的Java模型相比,不再被引用的對象會(huì )被當作垃圾收集掉。實(shí)體必須被顯式的保存和刪除(除非保存和刪除是從父實(shí)體向子實(shí)體引發(fā)的級聯(lián))。這和ODMG模型中關(guān)于對象通過(guò)可觸及保持持久性有一些不同——比較起來(lái)更加接近應用程序對象通常在一個(gè)大系統中的使用方法。實(shí)體支持循環(huán)引用和交叉引用,它們也可以加上版本信息。
一個(gè)實(shí)體的持久狀態(tài)包含指向其他實(shí)體和值類(lèi)型實(shí)例的引用。值可以是原始類(lèi)型,集合(不是集合中的對象),組件或者特定的不可變對象。與實(shí)體不同,值(特別是集合和組件)是通過(guò)可觸及性來(lái)進(jìn)行持久化和刪除的。因為值對象(和原始類(lèi)型數據)是隨著(zhù)包含他們的實(shí)體而被持久化和刪除的,他們不能被獨立的加上版本信息。值沒(méi)有獨立的標識,所以他們不能被兩個(gè)實(shí)體或者集合共享。
直到現在,我們都一直使用術(shù)語(yǔ)?持久類(lèi)?(persistent class)來(lái)代表實(shí)體。我們仍然會(huì )這么做。 然而嚴格說(shuō)來(lái),不是所有的用戶(hù)自定義的,帶有持久化狀態(tài)的類(lèi)都是實(shí)體。組件就是用戶(hù)自定義類(lèi),卻是值語(yǔ)義的。java.lang.String類(lèi)型的java屬性也是值語(yǔ)義的。給了這個(gè)定義以后,我們可以說(shuō)所有JDK提供的類(lèi)型(類(lèi))都是值類(lèi)型的語(yǔ)義,而用于自定義類(lèi)型可能被映射為實(shí)體類(lèi)型或值類(lèi)型語(yǔ)義。采用哪種類(lèi)型的語(yǔ)義取決于開(kāi)發(fā)人員。在領(lǐng)域模型中,尋找實(shí)體類(lèi)的一個(gè)好線(xiàn)索是共享引用指向這個(gè)類(lèi)的單一實(shí)例,而組合或聚合通常被轉化為值類(lèi)型。
我們會(huì )在本文檔中重復碰到這兩個(gè)概念。
挑戰在于將java類(lèi)型系統(和開(kāi)發(fā)者定義的實(shí)體和值類(lèi)型)映射到 SQL/數據庫類(lèi)型系統。Hibernate提供了連接兩個(gè)系統之間的橋梁:對于實(shí)體類(lèi)型,我們使用<class>, <subclass> 等等。對于值類(lèi)型,我們使用 <property>, <component> 及其他,通常跟隨著(zhù)type屬性。這個(gè)屬性的值是Hibernate 的映射類(lèi)型的名字。Hibernate提供了許多現成的映射(標準的JDK值類(lèi)型)。你也可以編寫(xiě)自己的映射類(lèi)型并實(shí)現自定義的變換策略,隨后我們會(huì )看到這點(diǎn)。
所有的Hibernate內建類(lèi)型,除了collections以外,都支持空(null)語(yǔ)義。
5.2.2. 基本值類(lèi)型
內建的 基本映射類(lèi)型可以大致分為
integer, long, short, float, double, character, byte, boolean, yes_no, true_false
這些類(lèi)型都對應Java的原始類(lèi)型或者其封裝類(lèi),來(lái)符合(特定廠(chǎng)商的)SQL 字段類(lèi)型。boolean, yes_no 和 true_false都是Java 中boolean 或者java.lang.Boolean的另外說(shuō)法。
string
從java.lang.String 到 VARCHAR (或者 Oracle的 VARCHAR2)的映射。
date, time, timestamp
從java.util.Date和其子類(lèi)到SQL類(lèi)型DATE, TIME 和TIMESTAMP (或等價(jià)類(lèi)型)的映射。
calendar, calendar_date
從java.util.Calendar 到SQL 類(lèi)型TIMESTAMP和 DATE(或等價(jià)類(lèi)型)的映射。
big_decimal, big_integer
從java.math.BigDecimal和java.math.BigInteger到NUMERIC (或者 Oracle 的NUMBER類(lèi)型)的映射。
locale, timezone, currency
從java.util.Locale, java.util.TimeZone 和java.util.Currency 到VARCHAR (或者 Oracle 的VARCHAR2類(lèi)型)的映射. Locale和 Currency 的實(shí)例被映射為它們的ISO代碼。TimeZone的實(shí)例被影射為它的ID。
class
從java.lang.Class 到 VARCHAR (或者 Oracle 的VARCHAR2類(lèi)型)的映射。Class被映射為它的全限定名。
binary
把字節數組(byte arrays)映射為對應的 SQL二進(jìn)制類(lèi)型。
text
把長(cháng)Java字符串映射為SQL的CLOB或者TEXT類(lèi)型。
serializable
把可序列化的Java類(lèi)型映射到對應的SQL二進(jìn)制類(lèi)型。你也可以為一個(gè)并非默認為基本類(lèi)型的可序列化Java類(lèi)或者接口指定Hibernate類(lèi)型serializable。
clob, blob
JDBC 類(lèi) java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不適合使用這個(gè)類(lèi)型,因為blob和clob對象可能在一個(gè)事務(wù)之外是無(wú)法重用的。(而且, 驅動(dòng)程序對這種類(lèi)型的支持充滿(mǎn)著(zhù)補丁和前后矛盾。)
imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary
一般來(lái)說(shuō),映射類(lèi)型被假定為是可變的Java類(lèi)型,只有對不可變Java類(lèi)型,Hibernate會(huì )采取特定的優(yōu)化措施,應用程序會(huì )把這些對象作為不可變對象處理。比如,你不應該對作為imm_timestamp映射的Date執行Date.setTime()。要改變屬性的值,并且保存這一改變,應用程序必須對這一屬性重新設置一個(gè)新的(不一樣的)對象。
實(shí)體及其集合的唯一標識可以是除了binary、 blob 和 clob之外的任何基礎類(lèi)型。(聯(lián)合標識也是允許的,后面會(huì )說(shuō)到。)
在org.hibernate.Hibernate中,定義了基礎類(lèi)型對應的Type常量。比如,Hibernate.STRING代表string 類(lèi)型。
5.2.3. 自定義值類(lèi)型
開(kāi)發(fā)者創(chuàng )建屬于他們自己的值類(lèi)型也是很容易的。比如說(shuō),你可能希望持久化java.lang.BigInteger類(lèi)型的屬性,持久化成為VARCHAR字段。Hibernate沒(méi)有內置這樣一種類(lèi)型。自定義類(lèi)型能夠映射一個(gè)屬性(或集合元素)到不止一個(gè)數據庫表字段。比如說(shuō),你可能有這樣的Java屬性:getName()/setName(),這是java.lang.String類(lèi)型的,對應的持久化到三個(gè)字段:FIRST_NAME, INITIAL, SURNAME。
要實(shí)現一個(gè)自定義類(lèi)型,可以實(shí)現org.hibernate.UserType或org.hibernate.CompositeUserType中的任一個(gè),并且使用類(lèi)型的Java全限定類(lèi)名來(lái)定義屬性。請查看org.hibernate.test.DoubleStringType這個(gè)例子,看看它是怎么做的。
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property>
注意使用<column>標簽來(lái)把一個(gè)屬性映射到多個(gè)字段的做法。
CompositeUserType, EnhancedUserType, UserCollectionType, 和 UserVersionType 接口為更特殊的使用方式提供支持。
你甚至可以在一個(gè)映射文件中提供參數給一個(gè)UserType。 為了這樣做,你的UserType必須實(shí)現org.hibernate.usertype.ParameterizedType接口。為了給自定義類(lèi)型提供參數,你可以在映射文件中使用<type>元素。
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property>
現在,UserType 可以從傳入的Properties對象中得到default 參數的值。
如果你非常頻繁地使用某一UserType,可以為他定義一個(gè)簡(jiǎn)稱(chēng)。這可以通過(guò)使用 <typedef>元素來(lái)實(shí)現。Typedefs為一自定義類(lèi)型賦予一個(gè)名稱(chēng),并且如果此類(lèi)型是參數化的,還可以包含一系列默認的參數值。
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default">0</param>
</typedef>
<property name="priority" type="default_zero"/>
也可以根據具體案例通過(guò)屬性映射中的類(lèi)型參數覆蓋在typedef中提供的參數。
盡管 Hibernate 內建的豐富的類(lèi)型和對組件的支持意味著(zhù)你可能很少 需要使用自定義類(lèi)型。不過(guò),為那些在你的應用中經(jīng)常出現的(非實(shí)體)類(lèi)使用自定義類(lèi)型也是一個(gè)好方法。例如,一個(gè)MonetaryAmount類(lèi)使用CompositeUserType來(lái)映射是不錯的選擇,雖然他可以很容易地被映射成組件。這樣做的動(dòng)機之一是抽象。使用自定義類(lèi)型,以后假若你改變表示金額的方法時(shí),它可以保證映射文件不需要修改。
5.3. 多次映射同一個(gè)類(lèi)
對特定的持久化類(lèi),映射多次是允許的。這種情形下,你必須指定entity name來(lái)區別不同映射實(shí)體的對象實(shí)例。(默認情況下,實(shí)體名字和類(lèi)名是相同的。) Hibernate在操作持久化對象、編寫(xiě)查詢(xún)條件,或者把關(guān)聯(lián)映射到指定實(shí)體時(shí),允許你指定這個(gè)entity name(實(shí)體名字)。
<class name="Contract" table="Contracts"
entity-name="CurrentContract">
...
<set name="history" inverse="true"
order-by="effectiveEndDate desc">
<key column="currentContractId"/>
<one-to-many entity-name="HistoricalContract"/>
</set>
</class>
entity-name="HistoricalContract">
...
<many-to-one name="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class>
注意這里關(guān)聯(lián)是如何用entity-name來(lái)代替class的。
5.4. SQL中引號包圍的標識符
你可通過(guò)在映射文檔中使用反向引號(`)把表名或者字段名包圍起來(lái),以強制Hibernate在生成的SQL中把標識符用引號包圍起來(lái)。Hibernate會(huì )使用相應的SQLDialect(方言)來(lái)使用正確的引號風(fēng)格(通常是雙引號,但是在SQL Server中是括號,MySQL中是反向引號)。
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class>
5.5. 其他元數據(Metadata)
XML 并不適用于所有人, 因此有其他定義Hibernate O/R 映射元數據(metadata)的方法。
5.5.1. 使用 XDoclet 標記
很多Hibernate使用者更喜歡使用XDoclet@hibernate.tags將映射信息直接嵌入到源代碼中。我們不會(huì )在本文檔中涉及這個(gè)方法,因為嚴格說(shuō)來(lái),這屬于XDoclet的一部分。然而,我們包含了如下使用XDoclet映射的Cat類(lèi)的例子。
package eg;
import java.util.Set;
import java.util.Date;
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
* @hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother() {
return mother;
}
void setMother(Cat mother) {
this.mother = mother;
}
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}
參考Hibernate網(wǎng)站更多的Xdoclet和Hibernate的例子
5.5.2. 使用 JDK 5.0 的注解(Annotation)
JDK 5.0 在語(yǔ)言級別引入了 XDoclet 風(fēng)格的標注,并且是類(lèi)型安全的,在編譯期進(jìn)行檢查。這一機制比XDoclet的注解更為強大,有更好的工具和IDE支持。例如, IntelliJ IDEA,支持JDK 5.0注解的自動(dòng)完成和語(yǔ)法高亮 。EJB規范的新修訂版(JSR-220)使用 JDK 5.0的注解作為entity beans的主要元數據(metadata)機制。Hibernate 3 實(shí)現了JSR-220 (the persistence API)的EntityManager,支持通過(guò)Hibernate Annotations包定義映射元數據。這個(gè)包作為單獨的部分下載,支持EJB3 (JSR-220)和Hibernate3的元數據。
這是一個(gè)被注解為EJB entity bean 的POJO類(lèi)的例子
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
Long id;
String lastName;
Date birthday;
Integer age;
private Address homeAddress;
@JoinColumn(name="CUSTOMER_ID")
Set<Order> orders;
}
注意:對 JDK 5.0 注解 (和 JSR-220)支持的工作仍然在進(jìn)行中,并未完成。更多細節請參閱Hibernate Annotations 模塊。
5.6. 數據庫生成屬性(Generated Properties)
Generated properties指的是其值由數據庫生成的屬性。一般來(lái)說(shuō),如果對象有任何屬性由數據庫生成值,Hibernate應用程序需要進(jìn)行刷新(refresh)。但如果把屬性標明為generated,就可以轉由Hibernate來(lái)負責這個(gè)動(dòng)作。實(shí)際上。對定義了generated properties的實(shí)體,每當Hibernate執行一條SQL INSERT或者UPDATE語(yǔ)句,會(huì )立刻執行一條select來(lái)獲得生成的值。
被標明為generated的屬性還必須是 non-insertable和 non-updateable的。只有第 5.1.7 節 ?版本(version)(可選)?,第 5.1.8 節 ?timestamp (可選)?和第 5.1.9 節 ?property?可以被標明為generated。
never (默認) 標明此屬性值不是從數據庫中生成。
insert - 標明此屬性值在insert的時(shí)候生成,但是不會(huì )在隨后的update時(shí)重新生成。比如說(shuō)創(chuàng )建日期就歸屬于這類(lèi)。注意雖然第 5.1.7 節 ?版本(version)(可選)?和第 5.1.8 節 ?timestamp (可選)?屬性可以被標注為generated,但是不適用這個(gè)選項...
always - 標明此屬性值在insert和update時(shí)都會(huì )被生成。
5.7. 輔助數據庫對象(Auxiliary Database Objects)
Allows CREATE and DROP of arbitrary database objects, in conjunction with Hibernate‘s schema evolution tools, to provide the ability to fully define a user schema within the Hibernate mapping files. Although designed specifically for creating and dropping things like triggers or stored procedures, really any SQL command that can be run via a java.sql.Statement.execute() method is valid here (ALTERs, INSERTS, etc). There are essentially two modes for defining auxiliary database objects... 幫助CREATE和DROP任意數據庫對象,與Hibernate的schema交互工具組合起來(lái),可以提供在Hibernate映射文件中完全定義用戶(hù)schema的能力。雖然這是為創(chuàng )建和銷(xiāo)毀trigger(觸發(fā)器)或stored procedure(存儲過(guò)程)等特別設計的,實(shí)際上任何可以在java.sql.Statement.execute()方法中執行的SQL命令都可以在此使用(比如ALTER, INSERT,等等)。本質(zhì)上有兩種模式來(lái)定義輔助數據庫對象...
第一種模式是在映射文件中顯式聲明CREATE和DROP命令:
<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping>
第二種模式是提供一個(gè)類(lèi),這個(gè)類(lèi)知道如何組織CREATE和DROP命令。這個(gè)特別類(lèi)必須實(shí)現org.hibernate.mapping.AuxiliaryDatabaseObject接口。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>
還有,這些數據庫對象可以特別指定為僅在特定的方言中才使用。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9Dialect"/>
<dialect-scope name="org.hibernate.dialect.OracleDialect"/>
</database-object>
</hibernate-mapping>
第 6 章 集合類(lèi)(Collections)映射
6.1. 持久化集合類(lèi)(Persistent collections)
(譯者注:在閱讀本章的時(shí)候,以后整個(gè)手冊的閱讀過(guò)程中,我們都會(huì )面臨一個(gè)名詞方面的問(wèn)題,那就是?集合?。"Collections"和"Set"在中文里對應都被翻譯為?集合?,但是他們的含義很不一樣。Collections是一個(gè)超集,Set是其中的一種。大部分情況下,本譯稿中泛指的未加英文注明的?集合?,都應當理解為?Collections?。在有些二者同時(shí)出現,可能造成混淆的地方,我們用?集合類(lèi)?來(lái)特指?Collecions?,?集合(Set)?來(lái)指"Set",一般都會(huì )在后面的括號中給出英文。希望大家在閱讀時(shí)聯(lián)系上下文理解,不要造成誤解。 與此同時(shí),?元素?一詞對應的英文?element?,也有兩個(gè)不同的含義。其一為集合的元素,是內存中的一個(gè)變量;另一含義則是XML文檔中的一個(gè)標簽所代表的元素。也請注意區別。 本章中,特別是后半部分是需要反復閱讀才能理解清楚的。如果遇到任何疑問(wèn),請記住,英文版本的reference是惟一標準的參考資料。)
Hibernate要求持久化集合值字段必須聲明為接口,比如:
public class Product {
private String serialNumber;
private Set parts = new HashSet();
public Set getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
}
實(shí)際的接口可能是java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap 或者...任何你喜歡的類(lèi)型!("任何你喜歡的類(lèi)型" 代表你需要編寫(xiě) org.hibernate.usertype.UserCollectionType的實(shí)現.)
注意我們是如何用一個(gè)HashSet實(shí)例來(lái)初始化實(shí)例變量的.這是用于初始化新創(chuàng )建(尚未持久化)的類(lèi)實(shí)例中集合值屬性的最佳方法。當你持久化這個(gè)實(shí)例時(shí)——比如通過(guò)調用persist()——Hibernate 會(huì )自動(dòng)把HashSet替換為Hibernate自己的Set實(shí)現。觀(guān)察下面的錯誤:
Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); //Okay, kittens collection is a Set
(HashSet) cat.getKittens(); //Error!
根據不同的接口類(lèi)型,被Hibernate注射的持久化集合類(lèi)的表現類(lèi)似HashMap, HashSet, TreeMap, TreeSet or ArrayList。
集合類(lèi)實(shí)例具有值類(lèi)型的通常行為。當被持久化對象引用后,他們會(huì )自動(dòng)被持久化,當不再被引用后,自動(dòng)被刪除。假若實(shí)例被從一個(gè)持久化對象傳遞到另一個(gè),它的元素可能從一個(gè)表轉移到另一個(gè)表。兩個(gè)實(shí)體不能共享同一個(gè)集合類(lèi)實(shí)例的引用。因為底層關(guān)系數據庫模型的原因,集合值屬性無(wú)法支持空值語(yǔ)義;Hibernate對空的集合引用和空集合不加區別。
你不需要過(guò)多的為此擔心。就如同你平時(shí)使用普通的Java集合類(lèi)一樣來(lái)使用持久化集合類(lèi)。只是要確認你理解了雙向關(guān)聯(lián)的語(yǔ)義(后文討論)。
6.2. 集合映射( Collection mappings )
用于映射集合類(lèi)的Hibernate映射元素取決于接口的類(lèi)型。比如, <set> 元素用來(lái)映射Set類(lèi)型的屬性。
<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class>
除了<set>,還有<list>, <map>, <bag>, <array> 和 <primitive-array> 映射元素。<map>具有代表性:
<map
name="propertyName" (1)
table="table_name" (2)
schema="schema_name" (3)
lazy="true|extra|false" (4)
inverse="true|false" (5)
cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan"
sort="unsorted|natural|comparatorClass" (7)
order-by="column_name asc|desc" (8)
where="arbitrary sql where condition" (9)
fetch="join|select|subselect" (10)
batch-size="N" (11)
access="field|property|ClassName" (12)
optimistic-lock="true|false" (13)
mutable="true|false" (14)
node="element-name|."
embed-xml="true|false"
>
<map-key .... />
<element .... />
</map>
(1) name 集合屬性的名稱(chēng)
(2) table (可選——默認為屬性的名稱(chēng))這個(gè)集合表的名稱(chēng)(不能在一對多的關(guān)聯(lián)關(guān)系中使用)
(3) schema (可選) 表的schema的名稱(chēng), 他將覆蓋在根元素中定義的schema
(4) lazy (可選--默認為true) 可以用來(lái)關(guān)閉延遲加載(false),指定一直使用預先抓取,或者打開(kāi)"extra-lazy" 抓取,此時(shí)大多數操作不會(huì )初始化集合類(lèi)(適用于非常大的集合)
(5) inverse (可選——默認為false) 標記這個(gè)集合作為雙向關(guān)聯(lián)關(guān)系中的方向一端。
(6) cascade (可選——默認為none) 讓操作級聯(lián)到子實(shí)體
(7) sort(可選)指定集合的排序順序, 其可以為自然的(natural)或者給定一個(gè)用來(lái)比較的類(lèi)。
(8) order-by (可選, 僅用于jdk1.4) 指定表的字段(一個(gè)或幾個(gè))再加上asc或者desc(可選), 定義Map,Set和Bag的迭代順序
(9) where (可選) 指定任意的SQL where條件, 該條件將在重新載入或者刪除這個(gè)集合時(shí)使用(當集合中的數據僅僅是所有可用數據的一個(gè)子集時(shí)這個(gè)條件非常有用)
(10) fetch (可選, 默認為select) 用于在外連接抓取、通過(guò)后續select抓取和通過(guò)后續subselect抓取之間選擇。
(11) batch-size (可選, 默認為1) 指定通過(guò)延遲加載取得集合實(shí)例的批處理塊大?。?batch size")。
(12) access(可選-默認為屬性property):Hibernate取得集合屬性值時(shí)使用的策略
(13) 樂(lè )觀(guān)鎖 (可選 - 默認為 true): 對集合的狀態(tài)的改變會(huì )是否導致其所屬的實(shí)體的版本增長(cháng)。 (對一對多關(guān)聯(lián)來(lái)說(shuō),關(guān)閉這個(gè)屬性常常是有理的)
(14) mutable(可變) (可選 - 默認為true): 若值為false,表明集合中的元素不會(huì )改變(在某些情況下可以進(jìn)行一些小的性能優(yōu)化)。
6.2.1. 集合外鍵(Collection foreign keys)
集合實(shí)例在數據庫中依靠持有集合的實(shí)體的外鍵加以辨別。此外鍵作為集合關(guān)鍵字段(collection key column)(或多個(gè)字段)加以引用。集合關(guān)鍵字段通過(guò)<key> 元素映射。
在外鍵字段上可能具有非空約束。對于大多數集合來(lái)說(shuō),這是隱含的。對單向一對多關(guān)聯(lián)來(lái)說(shuō),外鍵字段默認是可以為空的,因此你可能需要指明 not-null="true"。
<key column="productSerialNumber" not-null="true"/>
外鍵約束可以使用ON DELETE CASCADE。
<key column="productSerialNumber" on-delete="cascade"/>
對<key> 元素的完整定義,請參閱前面的章節。
6.2.2. 集合元素(Collection elements)
集合幾乎可以包含任何其他的Hibernate類(lèi)型,包括所有的基本類(lèi)型、自定義類(lèi)型、組件,當然還有對其他實(shí)體的引用。存在一個(gè)重要的區別:位于集合中的對象可能是根據?值?語(yǔ)義來(lái)操作(其聲明周期完全依賴(lài)于集合持有者),或者它可能是指向另一個(gè)實(shí)體的引用,具有其自己的生命周期。在后者的情況下,被作為集合持有的狀態(tài)考慮的,只有兩個(gè)對象之間的?連接?。
被包容的類(lèi)型被稱(chēng)為集合元素類(lèi)型(collection element type)。集合元素通過(guò)<element>或<composite-element>映射,或在其是實(shí)體引用的時(shí)候,通過(guò)<one-to-many> 或<many-to-many>映射。前兩種用于使用值語(yǔ)義映射元素,后兩種用于映射實(shí)體關(guān)聯(lián)。
6.2.3. 索引集合類(lèi)(Indexed collections)
所有的集合映射,除了set和bag語(yǔ)義的以外,都需要指定一個(gè)集合表的索引字段(index column)——用于對應到數組索引,或者List的索引,或者M(jìn)ap的關(guān)鍵字。通過(guò)<map-key>,Map 的索引可以是任何基礎類(lèi)型;若通過(guò)<map-key-many-to-many>,它也可以是一個(gè)實(shí)體引用;若通過(guò)<composite-map-key>,它還可以是一個(gè)組合類(lèi)型。數組或列表的索引必須是integer類(lèi)型,并且使用 <list-index>元素定義映射。被映射的字段包含有順序排列的整數(默認從0開(kāi)始)。
<map-key
column="column_name" (1)
formula="any SQL expression" (2)
type="type_name" (3)
node="@attribute-name"
length="N"/>
(1) column(可選):保存集合索引值的字段名。
(2) formula (可選): 用于計算map關(guān)鍵字的SQL公式
(3) type (必須):映射鍵(map key)的類(lèi)型。
<map-key-many-to-many
column="column_name" (1)
formula="any SQL expression" (2)(3)
class="ClassName"
/>
(1) column(可選):集合索引值中外鍵字段的名稱(chēng)
(2) formula (可選): 用于計算map關(guān)鍵字的外鍵的SQL公式
(3) class (必需):映射的鍵(map key)使用的實(shí)體類(lèi)。
假若你的表沒(méi)有一個(gè)索引字段,當你仍然希望使用List作為屬性類(lèi)型,你應該把此屬性映射為Hibernate <bag>。從數據庫中獲取的時(shí)候,bag不維護其順序,但也可選擇性的進(jìn)行排序。
從集合類(lèi)可以產(chǎn)生很大一部分映射,覆蓋了很多常見(jiàn)的關(guān)系模型。我們建議你試驗schema生成工具,來(lái)體會(huì )一下不同的映射聲明是如何被翻譯為數據庫表的。
6.2.4. 值集合于多對多關(guān)聯(lián)(Collections of values and many-to-many associations)
任何值集合或者多對多關(guān)聯(lián)需要專(zhuān)用的具有一個(gè)或多個(gè)外鍵字段的collection table、一個(gè)或多個(gè)collection element column,以及還可能有一個(gè)或多個(gè)索引字段。
對于一個(gè)值集合, 我們使用<element>標簽。
<element
column="column_name" (1)
formula="any SQL expression" (2)
type="typename" (3)
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="element-name"
/>
(1) column(可選):保存集合元素值的字段名。
(2) formula (可選): 用于計算元素的SQL公式
(3) type (必需):集合元素的類(lèi)型
多對多關(guān)聯(lián)(many-to-many association) 使用 <many-to-many>元素定義.
<many-to-many
column="column_name" (1)
formula="any SQL expression" (2)
class="ClassName" (3)
fetch="select|join" (4)
unique="true|false" (5)
not-found="ignore|exception" (6)
entity-name="EntityName" (7)
property-ref="propertyNameFromAssociatedClass" (8)
node="element-name"
embed-xml="true|false"
/>
(1) column(可選): 這個(gè)元素的外鍵關(guān)鍵字段名
(2) formula (可選): 用于計算元素外鍵值的SQL公式.
(3) class (必需): 關(guān)聯(lián)類(lèi)的名稱(chēng)
(3) outer-join (可選 - 默認為auto): 在Hibernate系統參數中hibernate.use_outer_join被打開(kāi)的情況下,該參數用來(lái)允許使用outer join來(lái)載入此集合的數據。
(4) 為此關(guān)聯(lián)打開(kāi)外連接抓取或者后續select抓取。這是特殊情況;對于一個(gè)實(shí)體及其指向其他實(shí)體的多對多關(guān)聯(lián)進(jìn)全預先抓?。ㄊ褂靡粭l單獨的SELECT),你不僅需要對集合自身打開(kāi)join,也需要對<many-to-many>這個(gè)內嵌元素打開(kāi)此屬性。
(5) 對外鍵字段允許DDL生成的時(shí)候生成一個(gè)惟一約束。這使關(guān)聯(lián)變成了一個(gè)高效的一對多關(guān)聯(lián)。(此句存疑:原文為T(mén)his makes the association multiplicity effectively one to many.)
(6) not-found (可選 - 默認為 exception): 指明引用的外鍵中缺少某些行該如何處理: ignore 會(huì )把缺失的行作為一個(gè)空引用處理。
(7) entity-name (可選): 被關(guān)聯(lián)的類(lèi)的實(shí)體名,作為class的替代。
(8) property-ref: (可選) 被關(guān)聯(lián)到此外鍵(foreign key)的類(lèi)中的對應屬性的名字。若未指定,使用被關(guān)聯(lián)類(lèi)的主鍵。
例子:首先, 一組字符串:
<set name="names" table="NAMES">
<key column="GROUPID"/>
<element column="NAME" type="string"/>
</set>
包含一組整數的bag(還設置了order-by參數指定了迭代的順序):
<bag name="sizes"
table="item_sizes"
order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag>
一個(gè)實(shí)體數組,在這個(gè)案例中是一個(gè)多對多的關(guān)聯(lián)(注意這里的實(shí)體是自動(dòng)管理生命周期的對象(lifecycle objects),cascade="all"):
<array name="addresses"
table="PersonAddress"
cascade="persist">
<key column="personId"/>
<list-index column="sortOrder"/>
<many-to-many column="addressId" class="Address"/>
</array>
一個(gè)map,通過(guò)字符串的索引來(lái)指明日期:
<map name="holidays"
table="holidays"
schema="dbo"
order-by="hol_name asc">
<key column="id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>
一個(gè)組件的列表:(下一章討論)
<list name="carComponents"
table="CarComponents">
<key column="carId"/>
<list-index column="sortOrder"/>
<composite-element class="CarComponent">
<property name="price"/>
<property name="type"/>
<property name="serialNumber" column="serialNum"/>
</composite-element>
</list>
6.2.5. 一對多關(guān)聯(lián)(One-to-many Associations)
一對多關(guān)聯(lián)通過(guò)外鍵連接兩個(gè)類(lèi)對應的表,而沒(méi)有中間集合表。 這個(gè)關(guān)系模型失去了一些Java集合的語(yǔ)義:
? 一個(gè)被包含的實(shí)體的實(shí)例只能被包含在一個(gè)集合的實(shí)例中
? 一個(gè)被包含的實(shí)體的實(shí)例只能對應于集合索引的一個(gè)值中
一個(gè)從Product到Part的關(guān)聯(lián)需要關(guān)鍵字字段,可能還有一個(gè)索引字段指向Part所對應的表。 <one-to-many>標記指明了一個(gè)一對多的關(guān)聯(lián)。
<one-to-many
class="ClassName" (1)
not-found="ignore|exception" (2)
entity-name="EntityName" (3)
node="element-name"
embed-xml="true|false"
/>
(1) class(必須):被關(guān)聯(lián)類(lèi)的名稱(chēng)。
(2) not-found (可選 - 默認為exception): 指明若緩存的標示值關(guān)聯(lián)的行缺失,該如何處理: ignore 會(huì )把缺失的行作為一個(gè)空關(guān)聯(lián)處理。
(3) entity-name (可選): 被關(guān)聯(lián)的類(lèi)的實(shí)體名,作為class的替代。
例子
<set name="bars">
<key column="foo_id"/>
<one-to-many class="org.hibernate.Bar"/>
</set>
注意:<one-to-many>元素不需要定義任何字段。 也不需要指定表名。
重要提示:如果一對多關(guān)聯(lián)中的外鍵字段定義成NOT NULL,你必須把<key>映射聲明為not-null="true",或者使用雙向關(guān)聯(lián),并且標明inverse="true"。參閱本章后面關(guān)于雙向關(guān)聯(lián)的討論。
下面的例子展示一個(gè)Part實(shí)體的map,把name作為關(guān)鍵字。( partName 是Part的持久化屬性)。注意其中的基于公式的索引的用法。
<map name="parts"
cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map>
6.3. 高級集合映射(Advanced collection mappings)
6.3.1. 有序集合(Sorted collections)
Hibernate支持實(shí)現java.util.SortedMap和java.util.SortedSet的集合。你必須在映射文件中指定一個(gè)比較器:
<set name="aliases"
table="person_aliases"
sort="natural">
<key column="person"/>
<element column="name" type="string"/>
</set>
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>
sort屬性中允許的值包括unsorted,natural和某個(gè)實(shí)現了java.util.Comparator的類(lèi)的名稱(chēng)。
分類(lèi)集合的行為事實(shí)上象java.util.TreeSet或者java.util.TreeMap。
如果你希望數據庫自己對集合元素排序,可以利用set,bag或者map映射中的order-by屬性。這個(gè)解決方案只能在jdk1.4或者更高的jdk版本中才可以實(shí)現(通過(guò)LinkedHashSet或者 LinkedHashMap實(shí)現)。 它是在SQL查詢(xún)中完成排序,而不是在內存中。
<set name="aliases" table="person_aliases" order-by="lower(name) asc">
<key column="person"/>
<element column="name" type="string"/>
</set>
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>
注意: 這個(gè)order-by屬性的值是一個(gè)SQL排序子句而不是HQL的!
關(guān)聯(lián)還可以在運行時(shí)使用集合filter()根據任意的條件來(lái)排序。
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
6.3.2. 雙向關(guān)聯(lián)(Bidirectional associations)
雙向關(guān)聯(lián)允許通過(guò)關(guān)聯(lián)的任一端訪(fǎng)問(wèn)另外一端。在Hibernate中, 支持兩種類(lèi)型的雙向關(guān)聯(lián):
一對多(one-to-many)
Set或者bag值在一端, 單獨值(非集合)在另外一端
多對多(many-to-many)
兩端都是set或bag值
要建立一個(gè)雙向的多對多關(guān)聯(lián),只需要映射兩個(gè)many-to-many關(guān)聯(lián)到同一個(gè)數據庫表中,并再定義其中的一端為inverse(使用哪一端要根據你的選擇,但它不能是一個(gè)索引集合)。
這里有一個(gè)many-to-many的雙向關(guān)聯(lián)的例子;每一個(gè)category都可以有很多items,每一個(gè)items可以屬于很多categories:
<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<id name="id" column="CATEGORY_ID"/>
...
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class>
如果只對關(guān)聯(lián)的反向端進(jìn)行了改變,這個(gè)改變不會(huì )被持久化。 這表示Hibernate為每個(gè)雙向關(guān)聯(lián)在內存中存在兩次表現,一個(gè)從A連接到B,另一個(gè)從B連接到A。如果你回想一下Java對象模型,我們是如何在Java中創(chuàng )建多對多關(guān)系的,這可以讓你更容易理解:
category.getItems().add(item); // The category now "knows" about the relationship
item.getCategories().add(category); // The item now "knows" about the relationship
session.persist(category); // The relationship will be saved
非反向端用于把內存中的表示保存到數據庫中。
要建立一個(gè)一對多的雙向關(guān)聯(lián),你可以通過(guò)把一個(gè)一對多關(guān)聯(lián),作為一個(gè)多對一關(guān)聯(lián)映射到到同一張表的字段上,并且在"多"的那一端定義inverse="true"。
<class name="Parent">
<id name="id" column="parent_id"/>
....
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
在?一?這一端定義inverse="true"不會(huì )影響級聯(lián)操作,二者是正交的概念!
6.3.3. 雙向關(guān)聯(lián),涉及有序集合類(lèi)
對于有一端是<list>或者<map>的雙向關(guān)聯(lián),需要加以特別考慮。假若子類(lèi)中的一個(gè)屬性映射到索引字段,沒(méi)問(wèn)題,我們仍然可以在集合類(lèi)映射上使用inverse="true":
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children" inverse="true">
<key column="parent_id"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<id name="id" column="child_id"/>
....
<property name="name"
not-null="true"/>
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>
但是,假若子類(lèi)中沒(méi)有這樣的屬性存在,我們不能認為這個(gè)關(guān)聯(lián)是真正的雙向關(guān)聯(lián)(信息不對稱(chēng),在關(guān)聯(lián)的一端有一些另外一端沒(méi)有的信息)。在這種情況下,我們不能使用inverse="true"。我們需要這樣用:
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children">
<key column="parent_id"
not-null="true"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class>
注意在這個(gè)映射中,關(guān)聯(lián)中集合類(lèi)"值"一端負責來(lái)更新外鍵.TODO: Does this really result in some unnecessary update statements?
6.3.4. 三重關(guān)聯(lián)(Ternary associations)
有三種可能的途徑來(lái)映射一個(gè)三重關(guān)聯(lián)。第一種是使用一個(gè)Map,把一個(gè)關(guān)聯(lián)作為其索引:
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map>
<map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map>
第二種方法是簡(jiǎn)單的把關(guān)聯(lián)重新建模為一個(gè)實(shí)體類(lèi)。這使我們最經(jīng)常使用的方法。
最后一種選擇是使用復合元素,我們會(huì )在后面討論
6.3.5. 使用<idbag>
如果你完全信奉我們對于?聯(lián)合主鍵(composite keys)是個(gè)壞東西?,和?實(shí)體應該使用(無(wú)機的)自己生成的代用標識符(surrogate keys)?的觀(guān)點(diǎn),也許你會(huì )感到有一些奇怪,我們目前為止展示的多對多關(guān)聯(lián)和值集合都是映射成為帶有聯(lián)合主鍵的表的!現在,這一點(diǎn)非常值得爭辯;看上去一個(gè)單純的關(guān)聯(lián)表并不能從代用標識符中獲得什么好處(雖然使用組合值的集合可能會(huì )獲得一點(diǎn)好處)。不過(guò),Hibernate提供了一個(gè)(一點(diǎn)點(diǎn)試驗性質(zhì)的)功能,讓你把多對多關(guān)聯(lián)和值集合應得到一個(gè)使用代用標識符的表去。
<idbag> 屬性讓你使用bag語(yǔ)義來(lái)映射一個(gè)List (或Collection)。
<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag>
你可以理解,<idbag>人工的id生成器,就好像是實(shí)體類(lèi)一樣!集合的每一行都有一個(gè)不同的人造關(guān)鍵字。但是,Hibernate沒(méi)有提供任何機制來(lái)讓你取得某個(gè)特定行的人造關(guān)鍵字。
注意<idbag>的更新性能要比普通的<bag>高得多!Hibernate可以有效的定位到不同的行,分別進(jìn)行更新或刪除工作,就如同處理一個(gè)list, map或者set一樣。
在目前的實(shí)現中,還不支持使用identity標識符生成器策略來(lái)生成<idbag>集合的標識符。
6.4. 集合例子(Collection example)
在前面的幾個(gè)章節的確非常令人迷惑。 因此讓我們來(lái)看一個(gè)例子。這個(gè)類(lèi):
package eg;
import java.util.Set;
private long id;
private Set children;
private void setId(long id) { this.id=id; }
private void setChildren(Set children) { this.children=children; }
....
}
這個(gè)類(lèi)有一個(gè)Child的實(shí)例集合。如果每一個(gè)子實(shí)例至多有一個(gè)父實(shí)例, 那么最自然的映射是一個(gè)one-to-many的關(guān)聯(lián)關(guān)系:
<hibernate-mapping>
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
在以下的表定義中反應了這個(gè)映射關(guān)系:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent
如果父親是必須的, 那么就可以使用雙向one-to-many的關(guān)聯(lián)了:
<hibernate-mapping>
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
請注意NOT NULL的約束:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent
另外,如果你絕對堅持這個(gè)關(guān)聯(lián)應該是單向的,你可以對<key>映射聲明NOT NULL約束:
<hibernate-mapping>
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
另外一方面,如果一個(gè)子實(shí)例可能有多個(gè)父實(shí)例, 那么就應該使用many-to-many關(guān)聯(lián):
<hibernate-mapping>
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
表定義:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
child_id bigint not null,
primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child
更多的例子,以及一個(gè)完整的父/子關(guān)系映射的排練,請參閱第 21 章 示例:父子關(guān)系(Parent Child Relationships).
甚至可能出現更加復雜的關(guān)聯(lián)映射,我們會(huì )在下一章中列出所有可能性。
第 7 章 關(guān)聯(lián)關(guān)系映射
7.1. 介紹
關(guān)聯(lián)關(guān)系映射通常情況是最難配置正確的。在這個(gè)部分中,我們從單向關(guān)系映射開(kāi)始,然后考慮雙向關(guān)系映射,由淺至深講述一遍典型的案例。在所有的例子中,我們都使用 Person和Address。
我們根據映射關(guān)系是否涉及連接表以及多樣性來(lái)劃分關(guān)聯(lián)類(lèi)型。
在傳統的數據建模中,允許為Null值的外鍵被認為是一種不好的實(shí)踐,因此我們所有的例子中都使用不允許為Null的外鍵。這并不是Hibernate的要求,即使你刪除掉不允許為Null的約束,Hibernate映射一樣可以工作的很好。
7.2. 單向關(guān)聯(lián)(Unidirectional associations)
7.2.1. 多對一(many to one)
單向many-to-one關(guān)聯(lián)是最常見(jiàn)的單向關(guān)聯(lián)關(guān)系。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
7.2.2. 一對一(one to one)
基于外鍵關(guān)聯(lián)的單向一對一關(guān)聯(lián)和單向多對一關(guān)聯(lián)幾乎是一樣的。唯一的不同就是單向一對一關(guān)聯(lián)中的外鍵字段具有唯一性約束。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基于主鍵關(guān)聯(lián)的單向一對一關(guān)聯(lián)通常使用一個(gè)特定的id生成器。(請注意,在這個(gè)例子中我們掉換了關(guān)聯(lián)的方向。)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
7.2.3. 一對多(one to many)
基于外鍵關(guān)聯(lián)的單向一對多關(guān)聯(lián)是一種很少見(jiàn)的情況,并不推薦使用。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId"
not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )
我們認為對于這種關(guān)聯(lián)關(guān)系最好使用連接表。
7.3. 使用連接表的單向關(guān)聯(lián)(Unidirectional associations with join tables)
7.3.1. 一對多(one to many)
基于連接表的單向一對多關(guān)聯(lián) 應該優(yōu)先被采用。請注意,通過(guò)指定unique="true",我們可以把多樣性從多對多改變?yōu)橐粚Χ唷?
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
7.3.2. 多對一(many to one)
基于連接表的單向多對一關(guān)聯(lián)在關(guān)聯(lián)關(guān)系可選的情況下應用也很普遍。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</join>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
7.3.3. 一對一(one to one)
基于連接表的單向一對一關(guān)聯(lián)非常少見(jiàn),但也是可行的。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
7.3.4. 多對多(many to many)
最后,還有 單向多對多關(guān)聯(lián).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
7.4. 雙向關(guān)聯(lián)(Bidirectional associations)
7.4.1. 一對多(one to many) / 多對一(many to one)
雙向多對一關(guān)聯(lián) 是最常見(jiàn)的關(guān)聯(lián)關(guān)系。(這也是標準的父/子關(guān)聯(lián)關(guān)系。)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
如果你使用List(或者其他有序集合類(lèi)),你需要設置外鍵對應的key列為 not null,讓Hibernate來(lái)從集合端管理關(guān)聯(lián),維護每個(gè)元素的索引(通過(guò)設置update="false" and insert="false"來(lái)對另一端反向操作)。
<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class>
假若集合映射的<key>元素對應的底層外鍵字段是NOT NULL的,那么為這一key元素定義not-null="true"是很重要的。不要僅僅為可能的嵌套<column>元素定義not-null="true",<key>元素也是需要的。
7.4.2. 一對一(one to one)
基于外鍵關(guān)聯(lián)的雙向一對一關(guān)聯(lián)也很常見(jiàn)。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person"
property-ref="address"/>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基于主鍵關(guān)聯(lián)的一對一關(guān)聯(lián)需要使用特定的id生成器。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person"
constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
7.5. 使用連接表的雙向關(guān)聯(lián)(Bidirectional associations with join tables)
7.5.1. 一對多(one to many) /多對一( many to one)
基于連接表的雙向一對多關(guān)聯(lián)。注意inverse="true"可以出現在關(guān)聯(lián)的任意一端,即collection端或者join端。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses"
table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
inverse="true"
optional="true">
<key column="addressId"/>
<many-to-one name="person"
column="personId"
not-null="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
7.5.2. 一對一(one to one)
基于連接表的雙向一對一關(guān)聯(lián)極為罕見(jiàn),但也是可行的。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true"
inverse="true">
<key column="addressId"
unique="true"/>
<many-to-one name="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
7.5.3. 多對多(many to many)
最后,還有 雙向多對多關(guān)聯(lián).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
7.6. 更復雜的關(guān)聯(lián)映射
更復雜的關(guān)聯(lián)連接極為罕見(jiàn)。 通過(guò)在映射文檔中嵌入SQL片斷,Hibernate也可以處理更為復雜的情況。比如,假若包含歷史帳戶(hù)數據的表定義了accountNumber, effectiveEndDate 和effectiveStartDate字段,按照下面映射:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>
那么我們可以對目前(current)實(shí)例(其effectiveEndDate為null)使用這樣的關(guān)聯(lián)映射:
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula>‘1‘</formula>
</many-to-one>
更復雜的例子,假想Employee和Organization之間的關(guān)聯(lián)是通過(guò)一個(gè)Employment中間表維護的,而中間表中填充了很多歷史雇員數據。那?雇員的最新雇主?這個(gè)關(guān)聯(lián)(最新雇主就是startDate最后的那個(gè))可以這樣映射:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join>
使用這一功能時(shí)可以充滿(mǎn)創(chuàng )意,但通常更加實(shí)用的是用HQL或條件查詢(xún)來(lái)處理這些情形。
第 8 章 組件(Component)映射
組件(Component)這個(gè)概念在Hibernate中幾處不同的地方為了不同的目的被重復使用.
8.1. 依賴(lài)對象(Dependent objects)
組件(Component)是一個(gè)被包含的對象,在持久化的過(guò)程中,它被當作值類(lèi)型,而并非一個(gè)實(shí)體的引用。在這篇文檔中,組件這一術(shù)語(yǔ)指的是面向對象的合成概念(而并不是系統構架層次上的組件的概念)。舉個(gè)例子, 你對人(Person)這個(gè)概念可以像下面這樣來(lái)建模:
public class Person {
private java.util.Date birthday;
private Name name;
private String key;
public String getKey() {
return key;
}
private void setKey(String key) {
this.key=key;
}
public java.util.Date getBirthday() {
return birthday;
}
public void setBirthday(java.util.Date birthday) {
this.birthday = birthday;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}
public class Name {
char initial;
String first;
String last;
public String getFirst() {
return first;
}
void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
void setLast(String last) {
this.last = last;
}
public char getInitial() {
return initial;
}
void setInitial(char initial) {
this.initial = initial;
}
}
在持久化的過(guò)程中,姓名(Name)可以作為人(Person)的一個(gè)組件。需要注意的是:你應該為姓名的持久化屬性定義getter和setter方法,但是你不需要實(shí)現任何的接口或申明標識符字段。
以下是這個(gè)例子的Hibernate映射文件:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class>
人員(Person)表中將包括pid, birthday, initial, first和 last等字段。
就像所有的值類(lèi)型一樣, 組件不支持共享引用。 換句話(huà)說(shuō),兩個(gè)人可能重名,但是兩個(gè)Person對象應該包含兩個(gè)獨立的Name對象,只不過(guò)這兩個(gè)Name對象具有?同樣?的值。 組件的值可以為空,其定義如下。 每當Hibernate重新加載一個(gè)包含組件的對象,如果該組件的所有字段為空,Hibernate將假定整個(gè)組件為空。 在大多數情況下,這樣假定應該是沒(méi)有問(wèn)題的。
組件的屬性可以是任意一種Hibernate類(lèi)型(包括集合, 多對多關(guān)聯(lián), 以及其它組件等等)。嵌套組件不應該被當作一種特殊的應用(Nested components should not be considered an exotic usage)。 Hibernate傾向于支持細致的(fine-grained)對象模型。
<component> 元素還允許有 <parent>子元素,用來(lái)表明component類(lèi)中的一個(gè)屬性是指向包含它的實(shí)體的引用。
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date">
<component name="Name" class="eg.Name" unique="true">
<parent name="namedPerson"/> <!-- reference back to the Person -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class>
8.2. 在集合中出現的依賴(lài)對象 (Collections of dependent objects)
Hibernate支持組件的集合(例如: 一個(gè)元素是姓名(Name)這種類(lèi)型的數組)。 你可以使用<composite-element>標簽替代<element>標簽來(lái)定義你的組件集合。
<set name="someNames" table="some_names" lazy="true">
<key column="id"/>
<composite-element class="eg.Name"> <!-- class attribute required -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>;
</composite-element>
</set>
注意,如果你定義的Set包含組合元素(composite-element),正確地實(shí)現equals()和hashCode()是非常重要的。
組合元素可以包含組件,但是不能包含集合。如果你的組合元素自身包含組件, 你必須使用<nested-composite-element>標簽。這是一個(gè)相當特殊的案例 - 在一個(gè)組件的集合里,那些組件本身又可以包含其他的組件。這個(gè)時(shí)候你就應該考慮一下使用one-to-many關(guān)聯(lián)是否會(huì )更恰當。 嘗試對這個(gè)組合元素重新建模為一個(gè)實(shí)體-但是需要注意的是,雖然Java模型和重新建模前是一樣的,關(guān)系模型和持久性語(yǔ)義會(huì )有細微的變化。
請注意如果你使用<set>標簽,一個(gè)組合元素的映射不支持可能為空的屬性. 當刪除對象時(shí), Hibernate必須使用每一個(gè)字段的值來(lái)確定一條記錄(在組合元素表中,沒(méi)有單獨的關(guān)鍵字段), 如果有為null的字段,這樣做就不可能了。你必須作出一個(gè)選擇,要么在組合元素中使用不能為空的屬性,要么選擇使用<list>,<map>,<bag> 或者 <idbag>而不是 <set>。
組合元素有個(gè)特別的用法是它可以包含一個(gè)<many-to-one>元素。類(lèi)似這樣的映射允許你將一個(gè)many-to-many關(guān)聯(lián)表映射為組合元素的集合。(A mapping like this allows you to map extra columns of a many-to-many association table to the composite element class.) 接下來(lái)的的例子是從Order到Item的一個(gè)多對多的關(guān)聯(lián)關(guān)系, 關(guān)聯(lián)屬性是 purchaseDate, price 和 quantity 。
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.Purchase">
<property name="purchaseDate"/>
<property name="price"/>
<property name="quantity"/>
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
</composite-element>
</set>
</class>
當然,當你定義Item時(shí),你無(wú)法引用這些purchase,因此你無(wú)法實(shí)現雙向關(guān)聯(lián)查詢(xún)。記住組件是值類(lèi)型,并且不允許共享引用。某一個(gè)特定的Purchase 可以放在Order的集合中,但它不能同時(shí)被Item所引用。
其實(shí)組合元素的這個(gè)用法可以擴展到三重或多重關(guān)聯(lián):
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.OrderLine">
<many-to-one name="purchaseDetails" class="eg.Purchase"/>
<many-to-one name="item" class="eg.Item"/>
</composite-element>
</set>
</class>
在查詢(xún)中,表達組合元素的語(yǔ)法和關(guān)聯(lián)到其他實(shí)體的語(yǔ)法是一樣的。
8.3. 組件作為Map的索引(Components as Map indices )
<composite-map-key>元素允許你映射一個(gè)組件類(lèi)作為一個(gè)Map的key,前提是你必須正確的在這個(gè)類(lèi)中重寫(xiě)了hashCode() 和 equals()方法。
8.4. 組件作為聯(lián)合標識符(Components as composite identifiers)
你可以使用一個(gè)組件作為一個(gè)實(shí)體類(lèi)的標識符。 你的組件類(lèi)必須滿(mǎn)足以下要求:
? 它必須實(shí)現java.io.Serializable接口
? 它必須重新實(shí)現equals()和hashCode()方法, 始終和組合關(guān)鍵字在數據庫中的概念保持一致
注意:在Hibernate3中,第二個(gè)要求并非是Hibernate強制必須的。但最好這樣做。
你不能使用一個(gè)IdentifierGenerator產(chǎn)生組合關(guān)鍵字。一個(gè)應用程序必須分配它自己的標識符。
使用<composite-id> 標簽(并且內嵌<key-property>元素)代替通常的<id>標簽。比如,OrderLine類(lèi)具有一個(gè)主鍵,這個(gè)主鍵依賴(lài)于Order的(聯(lián)合)主鍵。
<class name="OrderLine">
<composite-id name="id" class="OrderLineId">
<key-property name="lineId"/>
<key-property name="orderId"/>
<key-property name="customerId"/>
</composite-id>
<property name="name"/>
<many-to-one name="order" class="Order"
insert="false" update="false">
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>
....
</class>
現在,任何指向OrderLine的外鍵都是復合的。在你的映射文件中,必須為其他類(lèi)也這樣聲明。例如,一個(gè)指向OrderLine的關(guān)聯(lián)可能被這樣映射:
<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>
(注意在各個(gè)地方<column>標簽都是column屬性的替代寫(xiě)法。)
指向OrderLine的多對多關(guān)聯(lián)也使用聯(lián)合外鍵:
<set name="undeliveredOrderLines">
<key column name="warehouseId"/>
<many-to-many class="OrderLine">
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-many>
</set>
在Order中,OrderLine的集合則是這樣:
<set name="orderLines" inverse="true">
<key>
<column name="orderId"/>
<column name="customerId"/>
</key>
<one-to-many class="OrderLine"/>
</set>
(與通常一樣,<one-to-many>元素不聲明任何列.)
假若OrderLine本身?yè)碛幸粋€(gè)集合,它也具有組合外鍵。
<class name="OrderLine">
....
....
<list name="deliveryAttempts">
<key> <!-- a collection inherits the composite key type -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</key>
<list-index column="attemptId" base="1"/>
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set>
</class>
8.5. 動(dòng)態(tài)組件 (Dynamic components)
你甚至可以映射Map類(lèi)型的屬性:
<dynamic-component name="userAttributes">
<property name="foo" column="FOO" type="string"/>
<property name="bar" column="BAR" type="integer"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>
從<dynamic-component>映射的語(yǔ)義上來(lái)講,它和<component>是相同的。 這種映射類(lèi)型的優(yōu)點(diǎn)在于通過(guò)修改映射文件,就可以具有在部署時(shí)檢測真實(shí)屬性的能力。利用一個(gè)DOM解析器,也可以在程序運行時(shí)操作映射文件。 更好的是,你可以通過(guò)Configuration對象來(lái)訪(fǎng)問(wèn)(或者修改)Hibernate的運行時(shí)元模型。
第 9 章 繼承映射(Inheritance Mappings)
9.1. 三種策略
Hibernate支持三種基本的繼承映射策略:
? 每個(gè)類(lèi)分層結構一張表(table per class hierarchy)
? 每個(gè)子類(lèi)一張表(table per subclass)
? 每個(gè)具體類(lèi)一張表(table per concrete class)
此外,Hibernate還支持第四種稍有不同的多態(tài)映射策略:
? 隱式多態(tài)(implicit polymorphism)
對于同一個(gè)繼承層次內的不同分支,可以采用不同的映射策略,然后用隱式多 態(tài)來(lái)完成跨越整個(gè)層次的多態(tài)。但是在同一個(gè)<class>根元素 下,Hibernate不支持混合了元素<subclass>、 <joined-subclass>和<union-subclass> 的映射。在同一個(gè)<class>元素下,可以混合使用 ?每個(gè)類(lèi)分層結構一張表?(table per hierarchy) 和?每個(gè)子類(lèi)一張表?(table per subclass) 這兩種映射策略,這是通過(guò)結合元素<subclass>和 <join>來(lái)實(shí)現的(見(jiàn)后)。
在多個(gè)映射文件中,可以直接在hibernate-mapping根下定義subclass,union-subclass和joined-subclass。也就是說(shuō),你可以?xún)H加入一個(gè)新的映射文件來(lái)擴展類(lèi)層次。你必須在subclass的映射中指明extends屬性,給出一個(gè)之前定義的超類(lèi)的名字。注意,在以前,這一功能對映射文件的順序有嚴格的要求,從Hibernate 3開(kāi)始,使用extends關(guān)鍵字的時(shí)侯,對映射文件的順序不再有要求;但在每個(gè)映射文件里,超類(lèi)必須在子類(lèi)之前定義。
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>
9.1.1. 每個(gè)類(lèi)分層結構一張表(Table per class hierarchy)
假設我們有接口Payment和它的幾個(gè)實(shí)現類(lèi): CreditCardPayment, CashPayment, 和ChequePayment。則?每個(gè)類(lèi)分層結構一張表?(Table per class hierarchy)的映射代碼如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<property name="creditCardType" column="CCTYPE"/>
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
采用這種策略只需要一張表即可。它有一個(gè)很大的限制:要求那些由子類(lèi)定義的字段, 如CCTYPE,不能有非空(NOT NULL)約束。
9.1.2. 每個(gè)子類(lèi)一張表(Table per subclass)
對于上例中的幾個(gè)類(lèi)而言,采用?每個(gè)子類(lèi)一張表?的映射策略,代碼如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
</class>
需要四張表。三個(gè)子類(lèi)表通過(guò)主鍵關(guān)聯(lián)到超類(lèi)表(因而關(guān)系模型實(shí)際上是一對一關(guān)聯(lián))。
9.1.3. 每個(gè)子類(lèi)一張表(Table per subclass),使用辨別標志(Discriminator)
注意,對?每個(gè)子類(lèi)一張表?的映射策略,Hibernate的實(shí)現不需要辨別字段,而其他 的對象/關(guān)系映射工具使用了一種不同于Hibernate的實(shí)現方法,該方法要求在超類(lèi) 表中有一個(gè)類(lèi)型辨別字段(type discriminator column)。Hibernate采用的方法更 難實(shí)現,但從關(guān)系(數據庫)的角度來(lái)看,按理說(shuō)它更正確。若你愿意使用帶有辨別字 段的?每個(gè)子類(lèi)一張表?的策略,你可以結合使用<subclass> 與<join>,如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
<join table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
</class>
可選的聲明fetch="select",是用來(lái)告訴Hibernate,在查詢(xún)超類(lèi)時(shí), 不要使用外部連接(outer join)來(lái)抓取子類(lèi)ChequePayment的數據。
9.1.4. 混合使用?每個(gè)類(lèi)分層結構一張表?和?每個(gè)子類(lèi)一張表?
你甚至可以采取如下方法混和使用?每個(gè)類(lèi)分層結構一張表?和?每個(gè)子類(lèi)一張表?這兩種策略:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
對上述任何一種映射策略而言,指向根類(lèi)Payment的 關(guān)聯(lián)是使用<many-to-one>進(jìn)行映射的。
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
9.1.5. 每個(gè)具體類(lèi)一張表(Table per concrete class)
對于?每個(gè)具體類(lèi)一張表?的映射策略,可以采用兩種方法。第一種方法是使用 <union-subclass>。
<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="sequence"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</union-subclass>
<union-subclass name="CashPayment" table="CASH_PAYMENT">
...
</union-subclass>
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class>
這里涉及三張與子類(lèi)相關(guān)的表。每張表為對應類(lèi)的所有屬性(包括從超類(lèi)繼承的屬性)定義相應字段。
這種方式的局限在于,如果一個(gè)屬性在超類(lèi)中做了映射,其字段名必須與所有子類(lèi) 表中定義的相同。(我們可能會(huì )在Hibernate的后續發(fā)布版本中放寬此限制。) 不允許在聯(lián)合子類(lèi)(union subclass)的繼承層次中使用標識生成器策略(identity generator strategy), 實(shí)際上, 主鍵的種子(primary key seed)不得不為同一繼承層次中的全部被聯(lián)合子類(lèi)所共用.
假若超類(lèi)是抽象類(lèi),請使用abstract="true"。當然,假若它不是抽象的,需要一個(gè)額外的表(上面的例子中,默認是PAYMENT),來(lái)保存超類(lèi)的實(shí)例。
9.1.6. Table per concrete class, using implicit polymorphism
9.1.6. Table per concrete class, using implicit polymorphism
另一種可供選擇的方法是采用隱式多態(tài):
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CREDIT_AMOUNT"/>
...
</class>
<id name="id" type="long" column="CASH_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CASH_AMOUNT"/>
...
</class>
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</class>
注意,我們沒(méi)有在任何地方明確的提及接口Payment。同時(shí)注意 Payment的屬性在每個(gè)子類(lèi)中都進(jìn)行了映射。如果你想避免重復, 可以考慮使用XML實(shí)體(例如:位于DOCTYPE聲明內的 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] 和映射中的&allproperties;)。
這種方法的缺陷在于,在Hibernate執行多態(tài)查詢(xún)時(shí)(polymorphic queries)無(wú)法生成帶 UNION的SQL語(yǔ)句。
對于這種映射策略而言,通常用<any>來(lái)實(shí)現到 Payment的多態(tài)關(guān)聯(lián)映射。
<any name="payment" meta-type="string" id-type="long">
<meta-value value="CREDIT" class="CreditCardPayment"/>
<meta-value value="CASH" class="CashPayment"/>
<meta-value value="CHEQUE" class="ChequePayment"/>
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any>
9.1.7. 隱式多態(tài)和其他繼承映射混合使用
對這一映射還有一點(diǎn)需要注意。因為每個(gè)子類(lèi)都在各自獨立的元素<class> 中映射(并且Payment只是一個(gè)接口),每個(gè)子類(lèi)可以很容易的成為另一 個(gè)繼承體系中的一部分!(你仍然可以對接口Payment使用多態(tài)查詢(xún)。)
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="CREDIT_CARD" type="string"/>
<property name="amount" column="CREDIT_AMOUNT"/>
...
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
<subclass name="VisaPayment" discriminator-value="VISA"/>
</class>
<id name="id" type="long" column="TXN_ID">
<generator class="native"/>
</id>
...
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class>
我們還是沒(méi)有明確的提到Payment。 如果我們針對接口Payment執行查詢(xún) ——如from Payment—— Hibernate 自動(dòng)返回CreditCardPayment(和它的子類(lèi),因為 它們也實(shí)現了接口Payment)、 CashPayment和Chequepayment的實(shí)例, 但不返回NonelectronicTransaction的實(shí)例。
9.2. 限制
對?每個(gè)具體類(lèi)映射一張表?(table per concrete-class)的映射策略而言,隱式多態(tài)的 方式有一定的限制。而<union-subclass>映射的限制則沒(méi)有那 么嚴格。
下面表格中列出了在Hibernte中?每個(gè)具體類(lèi)一張表?的策略和隱式多態(tài)的限制。
表 9.1. 繼承映射特性(Features of inheritance mappings)
繼承策略(Inheritance strategy) 多態(tài)多對一 多態(tài)一對一 多態(tài)一對多 多態(tài)多對多 多態(tài) load()/get() 多態(tài)查詢(xún) 多態(tài)連接(join) 外連接(Outer join)讀取
每個(gè)類(lèi)分層結構一張表 <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p 支持
每個(gè)子類(lèi)一張表 <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p 支持
每個(gè)具體類(lèi)一張表(union-subclass) <many-to-one> <one-to-one> <one-to-many> (僅對于inverse="true"的情況) <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p 支持
每個(gè)具體類(lèi)一張表(隱式多態(tài)) <any> 不支持 不支持 <many-to-any> s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() from Payment p 不支持 不支持
第 10 章 與對象共事
Hibernate是完整的對象/關(guān)系映射解決方案,它提供了對象狀態(tài)管理(state management)的功能,使開(kāi)發(fā)者不再需要理會(huì )底層數據庫系統的細節。 也就是說(shuō),相對于常見(jiàn)的JDBC/SQL持久層方案中需要管理SQL語(yǔ)句,Hibernate采用了更自然的面向對象的視角來(lái)持久化Java應用中的數據。
換句話(huà)說(shuō),使用Hibernate的開(kāi)發(fā)者應該總是關(guān)注對象的狀態(tài)(state),不必考慮SQL語(yǔ)句的執行。 這部分細節已經(jīng)由Hibernate掌管妥當,只有開(kāi)發(fā)者在進(jìn)行系統性能調優(yōu)的時(shí)候才需要進(jìn)行了解。
10.1. Hibernate對象狀態(tài)(object states)
Hibernate定義并支持下列對象狀態(tài)(state):
? 瞬時(shí)(Transient) - 由new操作符創(chuàng )建,且尚未與Hibernate Session 關(guān)聯(lián)的對象被認定為瞬時(shí)(Transient)的。瞬時(shí)(Transient)對象不會(huì )被持久化到數據庫中,也不會(huì )被賦予持久化標識(identifier)。 如果瞬時(shí)(Transient)對象在程序中沒(méi)有被引用,它會(huì )被垃圾回收器(garbage collector)銷(xiāo)毀。 使用Hibernate Session可以將其變?yōu)槌志?Persistent)狀態(tài)。(Hibernate會(huì )自動(dòng)執行必要的SQL語(yǔ)句)
? 持久(Persistent) - 持久(Persistent)的實(shí)例在數據庫中有對應的記錄,并擁有一個(gè)持久化標識(identifier)。 持久(Persistent)的實(shí)例可能是剛被保存的,或剛被加載的,無(wú)論哪一種,按定義,它存在于相關(guān)聯(lián)的Session作用范圍內。 Hibernate會(huì )檢測到處于持久(Persistent)狀態(tài)的對象的任何改動(dòng),在當前操作單元(unit of work)執行完畢時(shí)將對象數據(state)與數據庫同步(synchronize)。 開(kāi)發(fā)者不需要手動(dòng)執行UPDATE。將對象從持久(Persistent)狀態(tài)變成瞬時(shí)(Transient)狀態(tài)同樣也不需要手動(dòng)執行DELETE語(yǔ)句。
? 脫管(Detached) - 與持久(Persistent)對象關(guān)聯(lián)的Session被關(guān)閉后,對象就變?yōu)槊摴?Detached)的。 對脫管(Detached)對象的引用依然有效,對象可繼續被修改。脫管(Detached)對象如果重新關(guān)聯(lián)到某個(gè)新的Session上, 會(huì )再次轉變?yōu)槌志?Persistent)的(在Detached其間的改動(dòng)將被持久化到數據庫)。 這個(gè)功能使得一種編程模型,即中間會(huì )給用戶(hù)思考時(shí)間(user think-time)的長(cháng)時(shí)間運行的操作單元(unit of work)的編程模型成為可能。 我們稱(chēng)之為應用程序事務(wù),即從用戶(hù)觀(guān)點(diǎn)看是一個(gè)操作單元(unit of work)。
接下來(lái)我們來(lái)細致的討論下?tīng)顟B(tài)(states)及狀態(tài)間的轉換(state transitions)(以及觸發(fā)狀態(tài)轉換的Hibernate方法)。
10.2. 使對象持久化
Hibernate認為持久化類(lèi)(persistent class)新實(shí)例化的對象是瞬時(shí)(Transient)的。 我們可通過(guò)將瞬時(shí)(Transient)對象與session關(guān)聯(lián)而把它變?yōu)槌志?Persistent)的。
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex(‘M‘);
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
如果Cat的持久化標識(identifier)是generated類(lèi)型的, 那么該標識(identifier)會(huì )自動(dòng)在save()被調用時(shí)產(chǎn)生并分配給cat。 如果Cat的持久化標識(identifier)是assigned類(lèi)型的,或是一個(gè)復合主鍵(composite key), 那么該標識(identifier)應當在調用save()之前手動(dòng)賦予給cat。 你也可以按照EJB3 early draft中定義的語(yǔ)義,使用persist()替代save()。
此外,你可以用一個(gè)重載版本的save()方法。
DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex(‘F‘);
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );
如果你持久化的對象有關(guān)聯(lián)的對象(associated objects)(例如上例中的kittens集合) 那么對這些對象(譯注:pk和kittens)進(jìn)行持久化的順序是任意的(也就是說(shuō)可以先對kittens進(jìn)行持久化也可以先對pk進(jìn)行持久化), 除非你在外鍵列上有NOT NULL約束。 Hibernate不會(huì )違反外鍵約束,但是如果你用錯誤的順序持久化對象(譯注:在pk持久化之前持久化kitten),那么可能會(huì )違反NOT NULL約束。
通常你不會(huì )為這些細節煩心,因為你很可能會(huì )使用Hibernate的 傳播性持久化(transitive persistence)功能自動(dòng)保存相關(guān)聯(lián)那些對象。 這樣連違反NOT NULL約束的情況都不會(huì )出現了 - Hibernate會(huì )管好所有的事情。 傳播性持久化(transitive persistence)將在本章稍后討論。
10.3. 裝載對象
如果你知道某個(gè)實(shí)例的持久化標識(identifier),你就可以使用Session的load()方法 來(lái)獲取它。 load()的另一個(gè)參數是指定類(lèi)的.class對象。 本方法會(huì )創(chuàng )建指定類(lèi)的持久化實(shí)例,并從數據庫加載其數據(state)。
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
此外, 你可以把數據(state)加載到指定的對象實(shí)例上(覆蓋掉該實(shí)例原來(lái)的數據)。
Cat cat = new DomesticCat();
// load pk‘s state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();
請注意如果沒(méi)有匹配的數據庫記錄,load()方法可能拋出無(wú)法恢復的異常(unrecoverable exception)。 如果類(lèi)的映射使用了代理(proxy),load()方法會(huì )返回一個(gè)未初始化的代理,直到你調用該代理的某方法時(shí)才會(huì )去訪(fǎng)問(wèn)數據庫。 若你希望在某對象中創(chuàng )建一個(gè)指向另一個(gè)對象的關(guān)聯(lián),又不想在從數據庫中裝載該對象時(shí)同時(shí)裝載相關(guān)聯(lián)的那個(gè)對象,那么這種操作方式就用得上的了。 如果為相應類(lèi)映射關(guān)系設置了batch-size, 那么使用這種操作方式允許多個(gè)對象被一批裝載(因為返回的是代理,無(wú)需從數據庫中抓取所有對象的數據)。
如果你不確定是否有匹配的行存在,應該使用get()方法,它會(huì )立刻訪(fǎng)問(wèn)數據庫,如果沒(méi)有對應的記錄,會(huì )返回null。
Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;
你甚至可以選用某個(gè)LockMode,用SQL的SELECT ... FOR UPDATE裝載對象。 請查閱API文檔以獲取更多信息。
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
注意,任何關(guān)聯(lián)的對象或者包含的集合都不會(huì )被以FOR UPDATE方式返回, 除非你指定了lock或者all作為關(guān)聯(lián)(association)的級聯(lián)風(fēng)格(cascade style)。
任何時(shí)候都可以使用refresh()方法強迫裝載對象和它的集合。如果你使用數據庫觸發(fā)器功能來(lái)處理對象的某些屬性,這個(gè)方法就很有用了。
sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)
此處通常會(huì )出現一個(gè)重要問(wèn)題: Hibernate會(huì )從數據庫中裝載多少東西?會(huì )執行多少條相應的SQLSELECT語(yǔ)句? 這取決于抓取策略(fetching strategy),會(huì )在第 19.1 節 ? 抓取策略(Fetching strategies) ?中解釋。
10.4. 查詢(xún)
如果不知道所要尋找的對象的持久化標識,那么你需要使用查詢(xún)。Hibernate支持強大且易于使用的面向對象查詢(xún)語(yǔ)言(HQL)。 如果希望通過(guò)編程的方式創(chuàng )建查詢(xún),Hibernate提供了完善的按條件(Query By Criteria, QBC)以及按樣例(Query By Example, QBE)進(jìn)行查詢(xún)的功能。 你也可以用原生SQL(native SQL)描述查詢(xún),Hibernate額外提供了將結果集(result set)轉化為對象的支持。
10.4.1. 執行查詢(xún)
HQL和原生SQL(native SQL)查詢(xún)要通過(guò)為org.hibernate.Query的實(shí)例來(lái)表達。 這個(gè)接口提供了參數綁定、結果集處理以及運行實(shí)際查詢(xún)的方法。 你總是可以通過(guò)當前Session獲取一個(gè)Query對象:
List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
一個(gè)查詢(xún)通常在調用list()時(shí)被執行,執行結果會(huì )完全裝載進(jìn)內存中的一個(gè)集合(collection)。 查詢(xún)返回的對象處于持久(persistent)狀態(tài)。如果你知道的查詢(xún)只會(huì )返回一個(gè)對象,可使用list()的快捷方式uniqueResult()。 注意,使用集合預先抓取的查詢(xún)往往會(huì )返回多次根對象(他們的集合類(lèi)都被初始化了)。你可以通過(guò)一個(gè)集合來(lái)過(guò)濾這些重復對象。
10.4.1.1. 迭代式獲取結果(Iterating results)
某些情況下,你可以使用iterate()方法得到更好的性能。 這通常是你預期返回的結果在session,或二級緩存(second-level cache)中已經(jīng)存在時(shí)的情況。 如若不然,iterate()會(huì )比list()慢,而且可能簡(jiǎn)單查詢(xún)也需要進(jìn)行多次數據庫訪(fǎng)問(wèn): iterate()會(huì )首先使用1條語(yǔ)句得到所有對象的持久化標識(identifiers),再根據持久化標識執行n條附加的select語(yǔ)句實(shí)例化實(shí)際的對象。
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // fetch the object
// something we couldnt express in the query
if ( qux.calculateComplicatedAlgorithm() ) {
// delete the current instance
iter.remove();
// dont need to process the rest
break;
}
}
10.4.1.2. 返回元組(tuples)的查詢(xún)
(譯注:元組(tuples)指一條結果行包含多個(gè)對象) Hibernate查詢(xún)有時(shí)返回元組(tuples),每個(gè)元組(tuples)以數組的形式返回:
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = tuple[0];
Cat mother = tuple[1];
....
}
10.4.1.3. 標量(Scalar)結果
查詢(xún)可在select從句中指定類(lèi)的屬性,甚至可以調用SQL統計(aggregate)函數。 屬性或統計結果被認定為"標量(Scalar)"的結果(而不是持久(persistent state)的實(shí)體)。
Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
10.4.1.4. 綁定參數
接口Query提供了對命名參數(named parameters)、JDBC風(fēng)格的問(wèn)號(?)參數進(jìn)行綁定的方法。 不同于JDBC,Hibernate對參數從0開(kāi)始計數。 命名參數(named parameters)在查詢(xún)字符串中是形如:name的標識符。 命名參數(named parameters)的優(yōu)點(diǎn)是:
? 命名參數(named parameters)與其在查詢(xún)串中出現的順序無(wú)關(guān)
? 它們可在同一查詢(xún)串中多次出現
? 它們本身是自我說(shuō)明的
//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();
10.4.1.5. 分頁(yè)
如果你需要指定結果集的范圍(希望返回的最大行數/或開(kāi)始的行數),應該使用Query接口提供的方法:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate 知道如何將這個(gè)有限定條件的查詢(xún)轉換成你的數據庫的原生SQL(native SQL)。
10.4.1.6. 可滾動(dòng)遍歷(Scrollable iteration)
如果你的JDBC驅動(dòng)支持可滾動(dòng)的ResuleSet,Query接口可以使用ScrollableResults,允許你在查詢(xún)結果中靈活游走。
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
cats.close()
請注意,使用此功能需要保持數據庫連接(以及游標(cursor))處于一直打開(kāi)狀態(tài)。 如果你需要斷開(kāi)連接使用分頁(yè)功能,請使用setMaxResult()/setFirstResult()
10.4.1.7. 外置命名查詢(xún)(Externalizing named queries)
你可以在映射文件中定義命名查詢(xún)(named queries)。 (如果你的查詢(xún)串中包含可能被解釋為XML標記(markup)的字符,別忘了用CDATA包裹起來(lái)。)
<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>
參數綁定及執行以編程方式(programatically)完成:
Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
請注意實(shí)際的程序代碼與所用的查詢(xún)語(yǔ)言無(wú)關(guān),你也可在元數據中定義原生SQL(native SQL)查詢(xún), 或將原有的其他的查詢(xún)語(yǔ)句放在配置文件中,這樣就可以讓Hibernate統一管理,達到遷移的目的。
10.4.2. 過(guò)濾集合
集合過(guò)濾器(filter)是一種用于一個(gè)持久化集合或者數組的特殊的查詢(xún)。查詢(xún)字符串中可以使用"this"來(lái)引用集合中的當前元素。
Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
返回的集合可以被認為是一個(gè)包(bag, 無(wú)順序可重復的集合(collection)),它是所給集合的副本。 原來(lái)的集合不會(huì )被改動(dòng)(這與?過(guò)濾器(filter)?的隱含的含義不符,不過(guò)與我們期待的行為一致)。
請注意過(guò)濾器(filter)并不需要from子句(當然需要的話(huà)它們也可以加上)。過(guò)濾器(filter)不限定于只能返回集合元素本身。
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();
即使無(wú)條件的過(guò)濾器(filter)也是有意義的。例如,用于加載一個(gè)大集合的子集:
Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();
10.4.3. 條件查詢(xún)(Criteria queries)
HQL極為強大,但是有些人希望能夠動(dòng)態(tài)的使用一種面向對象API創(chuàng )建查詢(xún),而非在他們的Java代碼中嵌入字符串。對于那部分人來(lái)說(shuō),Hibernate提供了直觀(guān)的Criteria查詢(xún)API。
Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
Criteria以及相關(guān)的樣例(Example)API將會(huì )再第 15 章 條件查詢(xún)(Criteria Queries) 中詳細討論。
10.4.4. 使用原生SQL的查詢(xún)
你可以使用createSQLQuery()方法,用SQL來(lái)描述查詢(xún),并由Hibernate將結果集轉換成對象。 請注意,你可以在任何時(shí)候調用session.connection()來(lái)獲得并使用JDBC Connection對象。 如果你選擇使用Hibernate的API, 你必須把SQL別名用大括號包圍起來(lái):
List cats = session.createSQLQuery(
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list();
List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list()
和Hibernate查詢(xún)一樣,SQL查詢(xún)也可以包含命名參數和占位參數。 可以在第 16 章 Native SQL查詢(xún)找到更多關(guān)于Hibernate中原生SQL(native SQL)的信息。
10.5. 修改持久對象
事務(wù)中的持久實(shí)例(就是通過(guò)session裝載、保存、創(chuàng )建或者查詢(xún)出的對象) 被應用程序操作所造成的任何修改都會(huì )在Session被刷出(flushed)的時(shí)候被持久化(本章后面會(huì )詳細討論)。 這里不需要調用某個(gè)特定的方法(比如update(),設計它的目的是不同的)將你的修改持久化。 所以最直接的更新一個(gè)對象的方法就是在Session處于打開(kāi)狀態(tài)時(shí)load()它,然后直接修改即可:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted
有時(shí)這種程序模型效率低下,因為它在同一Session里需要一條SQL SELECT語(yǔ)句(用于加載對象) 以及一條SQL UPDATE語(yǔ)句(持久化更新的狀態(tài))。 為此Hibernate提供了另一種途徑,使用脫管(detached)實(shí)例。
請注意Hibernate本身不提供直接執行UPDATE或DELETE語(yǔ)句的API。 Hibernate提供的是狀態(tài)管理(state management)服務(wù),你不必考慮要使用的語(yǔ)句(statements)。 JDBC是出色的執行SQL語(yǔ)句的API,任何時(shí)候調用session.connection()你都可以得到一個(gè)JDBC Connection對象。 此外,在聯(lián)機事務(wù)處理(OLTP)程序中,大量操作(mass operations)與對象/關(guān)系映射的觀(guān)點(diǎn)是相沖突的。 Hibernate的將來(lái)版本可能會(huì )提供專(zhuān)門(mén)的進(jìn)行大量操作(mass operation)的功能。 參考第 13 章 批量處理(Batch processing),尋找一些可用的批量(batch)操作技巧。
10.6. 修改脫管(Detached)對象
很多程序需要在某個(gè)事務(wù)中獲取對象,然后將對象發(fā)送到界面層去操作,最后在一個(gè)新的事務(wù)保存所做的修改。 在高并發(fā)訪(fǎng)問(wèn)的環(huán)境中使用這種方式,通常使用附帶版本信息的數據來(lái)保證這些?長(cháng)?工作單元之間的隔離。
Hibernate通過(guò)提供Session.update()或Session.merge() 重新關(guān)聯(lián)脫管實(shí)例的辦法來(lái)支持這種模型。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
cat.setMate(potentialMate);
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate
如果具有catId持久化標識的Cat之前已經(jīng)被另一Session(secondSession)裝載了, 應用程序進(jìn)行重關(guān)聯(lián)操作(reattach)的時(shí)候會(huì )拋出一個(gè)異常。
如果你確定當前session沒(méi)有包含與之具有相同持久化標識的持久實(shí)例,使用update()。 如果想隨時(shí)合并你的的改動(dòng)而不考慮session的狀態(tài),使用merge()。 換句話(huà)說(shuō),在一個(gè)新session中通常第一個(gè)調用的是update()方法,以便保證重新關(guān)聯(lián)脫管(detached)對象的操作首先被執行。
如果希望相關(guān)聯(lián)的脫管對象(通過(guò)引用?可到達?的脫管對象)的數據也要更新到數據庫時(shí)(并且也僅僅在這種情況), 可以對該相關(guān)聯(lián)的脫管對象單獨調用update() 當然這些可以自動(dòng)完成,即通過(guò)使用傳播性持久化(transitive persistence),請看第 10.11 節 ?傳播性持久化(transitive persistence)?。
lock()方法也允許程序重新關(guān)聯(lián)某個(gè)對象到一個(gè)新session上。不過(guò),該脫管(detached)的對象必須是沒(méi)有修改過(guò)的!
//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);
請注意,lock()可以搭配多種LockMode, 更多信息請閱讀API文檔以及關(guān)于事務(wù)處理(transaction handling)的章節。重新關(guān)聯(lián)不是lock()的唯一用途。
其他用于長(cháng)時(shí)間工作單元的模型會(huì )在第 11.3 節 ?樂(lè )觀(guān)并發(fā)控制(Optimistic concurrency control)?中討論。
10.7. 自動(dòng)狀態(tài)檢測
Hibernate的用戶(hù)曾要求一個(gè)既可自動(dòng)分配新持久化標識(identifier)保存瞬時(shí)(transient)對象,又可更新/重新關(guān)聯(lián)脫管(detached)實(shí)例的通用方法。 saveOrUpdate()方法實(shí)現了這個(gè)功能。
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
Cat mate = new Cat();
cat.setMate(mate);
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
saveOrUpdate()用途和語(yǔ)義可能會(huì )使新用戶(hù)感到迷惑。 首先,只要你沒(méi)有嘗試在某個(gè)session中使用來(lái)自另一session的實(shí)例,你就應該不需要使用update(), saveOrUpdate(),或merge()。有些程序從來(lái)不用這些方法。
通常下面的場(chǎng)景會(huì )使用update()或saveOrUpdate():
? 程序在第一個(gè)session中加載對象
? 該對象被傳遞到表現層
? 對象發(fā)生了一些改動(dòng)
? 該對象被返回到業(yè)務(wù)邏輯層
? 程序調用第二個(gè)session的update()方法持久這些改動(dòng)
saveOrUpdate()做下面的事:
? 如果對象已經(jīng)在本session中持久化了,不做任何事
? 如果另一個(gè)與本session關(guān)聯(lián)的對象擁有相同的持久化標識(identifier),拋出一個(gè)異常
? 如果對象沒(méi)有持久化標識(identifier)屬性,對其調用save()
? 如果對象的持久標識(identifier)表明其是一個(gè)新實(shí)例化的對象,對其調用save()
? 如果對象是附帶版本信息的(通過(guò)<version>或<timestamp>) 并且版本屬性的值表明其是一個(gè)新實(shí)例化的對象,save()它。
? 否則update() 這個(gè)對象
merge()可非常不同:
? 如果session中存在相同持久化標識(identifier)的實(shí)例,用用戶(hù)給出的對象的狀態(tài)覆蓋舊有的持久實(shí)例
? 如果session沒(méi)有相應的持久實(shí)例,則嘗試從數據庫中加載,或創(chuàng )建新的持久化實(shí)例
? 最后返回該持久實(shí)例
? 用戶(hù)給出的這個(gè)對象沒(méi)有被關(guān)聯(lián)到session上,它依舊是脫管的
10.8. 刪除持久對象
使用Session.delete()會(huì )把對象的狀態(tài)從數據庫中移除。 當然,你的應用程序可能仍然持有一個(gè)指向已刪除對象的引用。所以,最好這樣理解:delete()的用途是把一個(gè)持久實(shí)例變成瞬時(shí)(transient)實(shí)例。
sess.delete(cat);
你可以用你喜歡的任何順序刪除對象,不用擔心外鍵約束沖突。當然,如果你搞錯了順序,還是有可能引發(fā)在外鍵字段定義的NOT NULL約束沖突。 例如你刪除了父對象,但是忘記刪除孩子們。
10.9. 在兩個(gè)不同數據庫間復制對象
偶爾會(huì )用到不重新生成持久化標識(identifier),將持久實(shí)例以及其關(guān)聯(lián)的實(shí)例持久到不同的數據庫中的操作。
//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
ReplicationMode決定在和數據庫中已存在記錄由沖突時(shí),replicate()如何處理。
? ReplicationMode.IGNORE - 忽略它
? ReplicationMode.OVERWRITE - 覆蓋相同的行
? ReplicationMode.EXCEPTION - 拋出異常
? ReplicationMode.LATEST_VERSION - 如果當前的版本較新,則覆蓋,否則忽略
這個(gè)功能的用途包括使錄入的數據在不同數據庫中一致,產(chǎn)品升級時(shí)升級系統配置信息,回滾non-ACID事務(wù)中的修改等等。 (譯注,non-ACID,非ACID;ACID,Atomic,Consistent,Isolated and Durable的縮寫(xiě))
10.10. Session刷出(flush)
每間隔一段時(shí)間,Session會(huì )執行一些必需的SQL語(yǔ)句來(lái)把內存中的對象的狀態(tài)同步到JDBC連接中。這個(gè)過(guò)程被稱(chēng)為刷出(flush),默認會(huì )在下面的時(shí)間點(diǎn)執行:
? 在某些查詢(xún)執行之前
? 在調用org.hibernate.Transaction.commit()的時(shí)候
? 在調用Session.flush()的時(shí)候
涉及的SQL語(yǔ)句會(huì )按照下面的順序發(fā)出執行:
? 所有對實(shí)體進(jìn)行插入的語(yǔ)句,其順序按照對象執行Session.save()的時(shí)間順序
? 所有對實(shí)體進(jìn)行更新的語(yǔ)句
? 所有進(jìn)行集合刪除的語(yǔ)句
? 所有對集合元素進(jìn)行刪除,更新或者插入的語(yǔ)句
? 所有進(jìn)行集合插入的語(yǔ)句
? 所有對實(shí)體進(jìn)行刪除的語(yǔ)句,其順序按照對象執行Session.delete()的時(shí)間順序
(有一個(gè)例外是,如果對象使用native方式來(lái)生成ID(持久化標識)的話(huà),它們一執行save就會(huì )被插入。)
除非你明確地發(fā)出了flush()指令,關(guān)于Session何時(shí)會(huì )執行這些JDBC調用是完全無(wú)法保證的,只能保證它們執行的前后順序。 當然,Hibernate保證,Query.list(..)絕對不會(huì )返回已經(jīng)失效的數據,也不會(huì )返回錯誤數據。
也可以改變默認的設置,來(lái)讓刷出(flush)操作發(fā)生的不那么頻繁。 FlushMode類(lèi)定義了三種不同的方式。 僅在提交時(shí)刷出(僅當Hibernate的Transaction API被使用時(shí)有效), 按照剛才說(shuō)的方式刷出, 以及除非明確使用flush()否則從不刷出。 最后一種模式對于那些需要長(cháng)時(shí)間保持Session為打開(kāi)或者斷線(xiàn)狀態(tài)的長(cháng)時(shí)間運行的工作單元很有用。 (參見(jiàn) 第 11.3.2 節 ?擴展周期的session和自動(dòng)版本化?).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
izi.setName(iznizi);
sess.find("from Cat as cat left outer join cat.kittens kitten");
...
tx.commit(); // flush occurs
sess.close();
刷出(flush)期間,可能會(huì )拋出異常。(例如一個(gè)DML操作違反了約束) 異常處理涉及到對Hibernate事務(wù)性行為的理解,因此我們將在第 11 章 事務(wù)和并發(fā)中討論。
10.11. 傳播性持久化(transitive persistence)
對每一個(gè)對象都要執行保存,刪除或重關(guān)聯(lián)操作讓人感覺(jué)有點(diǎn)麻煩,尤其是在處理許多彼此關(guān)聯(lián)的對象的時(shí)候。 一個(gè)常見(jiàn)的例子是父子關(guān)系??紤]下面的例子:
如果一個(gè)父子關(guān)系中的子對象是值類(lèi)型(value typed)(例如,地址或字符串的集合)的,他們的生命周期會(huì )依賴(lài)于父對象,可以享受方便的級聯(lián)操作(Cascading),不需要額外的動(dòng)作。 父對象被保存時(shí),這些值類(lèi)型(value typed)子對象也將被保存;父對象被刪除時(shí),子對象也將被刪除。 這對將一個(gè)子對象從集合中移除是同樣有效:Hibernate會(huì )檢測到,并且因為值類(lèi)型(value typed)的對象不可能被其他對象引用,所以Hibernate會(huì )在數據庫中刪除這個(gè)子對象。
現在考慮同樣的場(chǎng)景,不過(guò)父子對象都是實(shí)體(entities)類(lèi)型,而非值類(lèi)型(value typed)(例如,類(lèi)別與個(gè)體,或母貓和小貓)。 實(shí)體有自己的生命期,允許共享對其的引用(因此從集合中移除一個(gè)實(shí)體,不意味著(zhù)它可以被刪除), 并且實(shí)體到其他關(guān)聯(lián)實(shí)體之間默認沒(méi)有級聯(lián)操作的設置。 Hibernate默認不實(shí)現所謂的可到達即持久化(persistence by reachability)的策略。
每個(gè)Hibernate session的基本操作 - 包括 persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() - 都有對應的級聯(lián)風(fēng)格(cascade style)。 這些級聯(lián)風(fēng)格(cascade style)風(fēng)格分別命名為 create, merge, save-update, delete, lock, refresh, evict, replicate。 如果你希望一個(gè)操作被順著(zhù)關(guān)聯(lián)關(guān)系級聯(lián)傳播,你必須在映射文件中指出這一點(diǎn)。例如:
<one-to-one name="person" cascade="persist"/>
級聯(lián)風(fēng)格(cascade style)是可組合的:
<one-to-one name="person" cascade="persist,delete,lock"/>
你可以使用cascade="all"來(lái)指定全部操作都順著(zhù)關(guān)聯(lián)關(guān)系級聯(lián)(cascaded)。 默認值是cascade="none",即任何操作都不會(huì )被級聯(lián)(cascaded)。
注意有一個(gè)特殊的級聯(lián)風(fēng)格(cascade style) delete-orphan,只應用于one-to-many關(guān)聯(lián),表明delete()操作 應該被應用于所有從關(guān)聯(lián)中刪除的對象。
建議:
? 通常在<many-to-one>或<many-to-many>關(guān)系中應用級聯(lián)(cascade)沒(méi)什么意義。 級聯(lián)(cascade)通常在 <one-to-one>和<one-to-many>關(guān)系中比較有用。
? 如果子對象的壽命限定在父親對象的壽命之內,可通過(guò)指定cascade="all,delete-orphan"將其變?yōu)樽詣?dòng)生命周期管理的對象(lifecycle object)。
? 其他情況,你可根本不需要級聯(lián)(cascade)。但是如果你認為你會(huì )經(jīng)常在某個(gè)事務(wù)中同時(shí)用到父對象與子對象,并且你希望少打點(diǎn)兒字,可以考慮使用cascade="persist,merge,save-update"。
可以使用cascade="all"將一個(gè)關(guān)聯(lián)關(guān)系(無(wú)論是對值對象的關(guān)聯(lián),或者對一個(gè)集合的關(guān)聯(lián))標記為父/子關(guān)系的關(guān)聯(lián)。 這樣對父對象進(jìn)行save/update/delete操作就會(huì )導致子對象也進(jìn)行save/update/delete操作。
此外,一個(gè)持久的父對象對子對象的淺引用(mere reference)會(huì )導致子對象被同步save/update。 不過(guò),這個(gè)隱喻(metaphor)的說(shuō)法并不完整。除非關(guān)聯(lián)是<one-to-many>關(guān)聯(lián)并且被標記為cascade="delete-orphan", 否則父對象失去對某個(gè)子對象的引用不會(huì )導致該子對象被自動(dòng)刪除。 父子關(guān)系的級聯(lián)(cascading)操作準確語(yǔ)義如下:
? 如果父對象被persist(),那么所有子對象也會(huì )被persist()
? 如果父對象被merge(),那么所有子對象也會(huì )被merge()
? 如果父對象被save(),update()或 saveOrUpdate(),那么所有子對象則會(huì )被saveOrUpdate()
? 如果某個(gè)持久的父對象引用了瞬時(shí)(transient)或者脫管(detached)的子對象,那么子對象將會(huì )被saveOrUpdate()
? 如果父對象被刪除,那么所有子對象也會(huì )被delete()
? 除非被標記為cascade="delete-orphan"(刪除?孤兒?模式,此時(shí)不被任何一個(gè)父對象引用的子對象會(huì )被刪除), 否則子對象失掉父對象對其的引用時(shí),什么事也不會(huì )發(fā)生。 如果有特殊需要,應用程序可通過(guò)顯式調用delete()刪除子對象。
最后,注意操作的級聯(lián)可能是在調用期(call time)或者寫(xiě)入期(flush time)作用到對象圖上的。所有的操作,如果允許,都在操作被執行的時(shí)候級聯(lián)到可觸及的關(guān)聯(lián)實(shí)體上。然而,save-upate和delete-orphan是在Session flush的時(shí)候才作用到所有可觸及的被關(guān)聯(lián)對象上的。
10.12. 使用元數據
Hibernate中有一個(gè)非常豐富的元級別(meta-level)的模型,含有所有的實(shí)體和值類(lèi)型數據的元數據。 有時(shí)這個(gè)模型對應用程序本身也會(huì )非常有用。 比如說(shuō),應用程序可能在實(shí)現一種?智能?的深度拷貝算法時(shí), 通過(guò)使用Hibernate的元數據來(lái)了解哪些對象應該被拷貝(比如,可變的值類(lèi)型數據), 那些不應該(不可變的值類(lèi)型數據,也許還有某些被關(guān)聯(lián)的實(shí)體)。
Hibernate提供了ClassMetadata接口,CollectionMetadata接口和Type層次體系來(lái)訪(fǎng)問(wèn)元數據。 可以通過(guò)SessionFactory獲取元數據接口的實(shí)例。
Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
namedValues.put( propertyNames[i], propertyValues[i] );
}
}
第 11 章 事務(wù)和并發(fā)
Hibernate的事務(wù)和并發(fā)控制很容易掌握。Hibernate直接使用JDBC連接和JTA資源,不添加任何附加鎖定 行為。我們強烈推薦你花點(diǎn)時(shí)間了解JDBC編程,ANSI SQL查詢(xún)語(yǔ)言和你使用 的數據庫系統的事務(wù)隔離規范。
Hibernate不鎖定內存中的對象。你的應用程序會(huì )按照你的數據庫事務(wù)的隔離級別規定的那樣運作。幸虧有了Session,使得Hibernate通過(guò)標識符查找,和實(shí)體查詢(xún)(不是返回標量值的報表查詢(xún))提供了可重復的讀?。≧epeatable reads)功能,Session同時(shí)也是事務(wù)范圍內的緩存(cache)。
除了對自動(dòng)樂(lè )觀(guān)并發(fā)控制提供版本管理,針對行級悲觀(guān)鎖定,Hibernate也提供了輔助的(較小的)API,它使用了 SELECT FOR UPDATE的SQL語(yǔ)法。本章后面會(huì )討論樂(lè )觀(guān)并發(fā)控制和這個(gè)API。
我們從Configuration層、SessionFactory層, 和 Session層開(kāi)始討論Hibernate的并行控制、數據庫事務(wù)和應用 程序的長(cháng)事務(wù)。
11.1. Session和事務(wù)范圍(transaction scope)
SessionFactory對象的創(chuàng )建代價(jià)很昂貴,它是線(xiàn)程安全的對象,它為所有的應用程序線(xiàn)程所共享。它只創(chuàng )建一次,通常是在應用程序啟動(dòng)的時(shí)候,由一個(gè)Configuraion的實(shí)例來(lái)創(chuàng )建。
Session對象的創(chuàng )建代價(jià)比較小,是非線(xiàn)程安全的,對于單個(gè)請求,單個(gè)會(huì )話(huà)、單個(gè)的 工作單元而言,它只被使用一次,然后就丟棄。只有在需要的時(shí)候,一個(gè)Session對象 才會(huì )獲取一個(gè)JDBC的Connection(或一個(gè)Datasource) 對象,因此假若不使用的時(shí)候它不消費任何資源。
此外我們還要考慮數據庫事務(wù)。數據庫事務(wù)應該盡可能的短,降低數據庫中的鎖爭用。 數據庫長(cháng)事務(wù)會(huì )阻止你的應用程序擴展到高的并發(fā)負載。因此,假若在用戶(hù)思考期間讓數據庫事務(wù)開(kāi)著(zhù),直到整個(gè)工作單元完成才關(guān)閉這個(gè)事務(wù),這絕不是一個(gè)好的設計。
一個(gè)操作單元(Unit of work)的范圍是多大?單個(gè)的Hibernate Session能跨越多個(gè) 數據庫事務(wù)嗎?還是一個(gè)Session的作用范圍對應一個(gè)數據庫事務(wù)的范圍?應該何時(shí)打開(kāi) Session,何時(shí)關(guān)閉Session?,你又如何劃分數據庫事務(wù)的邊界呢?
11.1.1. 操作單元(Unit of work)
首先,別用session-per-operation這種反模式了,也就是說(shuō),在單個(gè)線(xiàn)程中, 不要因為一次簡(jiǎn)單的數據庫調用,就打開(kāi)和關(guān)閉一次Session!數據庫事務(wù)也是如此。 應用程序中的數據庫調用是按照計劃好的次序,分組為原子的操作單元。(注意,這也意味著(zhù),應用程 序中,在單個(gè)的SQL語(yǔ)句發(fā)送之后,自動(dòng)事務(wù)提交(auto-commit)模式失效了。這種模式專(zhuān)門(mén)為SQL控制臺操作設計的。 Hibernate禁止立即自動(dòng)事務(wù)提交模式,或者期望應用服務(wù)器禁止立即自動(dòng)事務(wù)提交模式。)數據庫事務(wù)絕不是可有可無(wú)的,任何與數據庫之間的通訊都必須在某個(gè)事務(wù)中進(jìn)行,不管你是在讀還是在寫(xiě)數據。對讀數據而言,應該避免auto-commit行為,因為很多小的事務(wù)比一個(gè)清晰定義的工作單元性能差。后者也更容易維護和擴展。
在多用戶(hù)的client/server應用程序中,最常用的模式是 每個(gè)請求一個(gè)會(huì )話(huà)(session-per-request)。 在這種模式下,來(lái)自客戶(hù)端的請求被發(fā)送到服務(wù)器端(即Hibernate持久化層運行的地方),一 個(gè)新的Hibernate Session被打開(kāi),并且執行這個(gè)操作單元中所有的數據庫操作。 一旦操作完成(同時(shí)對客戶(hù)端的響應也準備就緒),session被同步,然后關(guān)閉。你也可以使用單 個(gè)數據庫事務(wù)來(lái)處理客戶(hù)端請求,在你打開(kāi)Session之后啟動(dòng)事務(wù),在你關(guān)閉 Session之前提交事務(wù)。會(huì )話(huà)和請求之間的關(guān)系是一對一的關(guān)系,這種模式對 于大多數應用程序來(lái)說(shuō)是很棒的。
實(shí)現才是真正的挑戰。Hibernate內置了對"當前session(current session)" 的管理,用于簡(jiǎn)化此模式。你要做的一切就是在服務(wù)器端要處理請求的時(shí)候,開(kāi)啟事務(wù),在響應發(fā)送給客戶(hù)之前結束事務(wù)。你可以用任何方式來(lái)完成這一操作,通常的方案有ServletFilter,在service方法中進(jìn)行pointcut的AOP攔截器,或者proxy/interception容器。EJB容器是實(shí)現橫切諸如EJB session bean上的事務(wù)分界,用CMT對事務(wù)進(jìn)行聲明等方面的標準手段。假若你決定使用編程式的事務(wù)分界,請參考本章后面講到的Hibernate Transaction API,這對易用性和代碼可移植性都有好處。
在任何時(shí)間,任何地方,你的應用代碼可以通過(guò)簡(jiǎn)單的調用sessionFactory.getCurrentSession()來(lái)訪(fǎng)問(wèn)"當前session",用于處理請求。你總是會(huì )得到當前數據庫事務(wù)范圍內的Session。在使用本地資源或JTA環(huán)境時(shí),必須配置它,請參見(jiàn)第 2.5 節 ?上下文相關(guān)的(Contextual)Session?。
有時(shí),將Session和數據庫事務(wù)的邊界延伸到"展示層被渲染后"會(huì )帶來(lái)便利。有些serlvet應用程序在對請求進(jìn)行處理后,有個(gè)單獨的渲染期,這種延伸對這種程序特別有用。假若你實(shí)現你自己的攔截器,把事務(wù)邊界延伸到展示層渲染結束后非常容易。然而,假若你依賴(lài)有容器管理事務(wù)的EJB,這就不太容易了,因為事務(wù)會(huì )在EJB方法返回后結束,而那是在任何展示層渲染開(kāi)始之前。請訪(fǎng)問(wèn)Hibernate網(wǎng)站和論壇,你可以找到Open Session in View這一模式的提示和示例。
11.1.2. 長(cháng)對話(huà)
session-per-request模式不僅僅是一個(gè)可以用來(lái)設計操作單元的有用概念。很多業(yè)務(wù)處理都需 要一系列完整的與用戶(hù)之間的交互,而這些用戶(hù)是指對數據庫有交叉訪(fǎng)問(wèn)的用戶(hù)。在基于web的應用和企業(yè) 應用中,跨用戶(hù)交互的數據庫事務(wù)是無(wú)法接受的??紤]下面的例子:
? 在界面的第一屏,打開(kāi)對話(huà)框,用戶(hù)所看到的數據是被一個(gè)特定的 Session 和數據 庫事務(wù)載入(load)的。用戶(hù)可以隨意修改對話(huà)框中的數據對象。
? 5分鐘后,用戶(hù)點(diǎn)擊?保存?,期望所做出的修改被持久化;同時(shí)他也期望自己是唯一修改這個(gè)信息的人,不會(huì )出現 修改沖突。
從用戶(hù)的角度來(lái)看,我們把這個(gè)操作單元稱(chēng)為長(cháng)時(shí)間運行的對話(huà)(conversation),或者(or 應用事務(wù),application transaction)。 在你的應用程序中,可以有很多種方法來(lái)實(shí)現它。
頭一個(gè)幼稚的做法是,在用戶(hù)思考的過(guò)程中,保持Session和數據庫事務(wù)是打開(kāi)的, 保持數據庫鎖定,以阻止并發(fā)修改,從而保證數據庫事務(wù)隔離級別和原子操作。這種方式當然是一個(gè)反模式, 因為鎖爭用會(huì )導致應用程序無(wú)法擴展并發(fā)用戶(hù)的數目。
很明顯,我們必須使用多個(gè)數據庫事務(wù)來(lái)實(shí)現這個(gè)對話(huà)。在這個(gè)例子中,維護業(yè)務(wù)處理的 事務(wù)隔離變成了應用程序層的部分責任。一個(gè)對話(huà)通??缭蕉鄠€(gè)數據庫事務(wù)。如果僅僅只有一 個(gè)數據庫事務(wù)(最后的那個(gè)事務(wù))保存更新過(guò)的數據,而所有其他事務(wù)只是單純的讀取數據(例如在一 個(gè)跨越多個(gè)請求/響應周期的向導風(fēng)格的對話(huà)框中),那么應用程序事務(wù)將保證其原子性。這種方式比聽(tīng) 起來(lái)還要容易實(shí)現,特別是當你使用了Hibernate的下述特性的時(shí)候:
? 自動(dòng)版本化 - Hibernate能夠自動(dòng)進(jìn)行樂(lè )觀(guān)并發(fā)控制 ,如果在用戶(hù)思考 的過(guò)程中發(fā)生并發(fā)修改,Hibernate能夠自動(dòng)檢測到。一般我們只在對話(huà)結束時(shí)才檢查。
? 脫管對象(Detached Objects)- 如果你決定采用前面已經(jīng)討論過(guò)的 session-per-request模式,所有載入的實(shí)例在用戶(hù)思考的過(guò)程 中都處于與Session脫離的狀態(tài)。Hibernate允許你把與Session脫離的對象重新關(guān)聯(lián)到Session 上,并且對修改進(jìn)行持久化,這種模式被稱(chēng)為 session-per-request-with-detached-objects。自動(dòng)版本化被用來(lái)隔離并發(fā)修改。
? Extended (or Long) Session - Hibernate 的Session 可以在數據庫事務(wù)提交之后和底層的JDBC連接斷開(kāi),當一個(gè)新的客戶(hù)端請求到來(lái)的時(shí)候,它又重新連接上底層的 JDBC連接。這種模式被稱(chēng)之為session-per-conversation,這種情況可 能會(huì )造成不必要的Session和JDBC連接的重新關(guān)聯(lián)。自動(dòng)版本化被用來(lái)隔離并發(fā)修改, Session通常不允許自動(dòng)flush,而是明確flush。
session-per-request-with-detached-objects 和 session-per-conversation 各有優(yōu)缺點(diǎn),我們在本章后面樂(lè )觀(guān)并發(fā) 控制那部分再進(jìn)行討論。
11.1.3. 關(guān)注對象標識(Considering object identity)
應用程序可能在兩個(gè)不同的Session中并發(fā)訪(fǎng)問(wèn)同一持久化狀態(tài),但是, 一個(gè)持久化類(lèi)的實(shí)例無(wú)法在兩個(gè) Session中共享。因此有兩種不同的標識語(yǔ)義:
數據庫標識
foo.getId().equals( bar.getId() )
JVM 標識
foo==bar
對于那些關(guān)聯(lián)到 特定Session (也就是在單個(gè)Session的范圍內)上的對象來(lái)說(shuō),這 兩種標識的語(yǔ)義是等價(jià)的,與數據庫標識對應的JVM標識是由Hibernate來(lái)保 證的。不過(guò),當應用程序在兩個(gè)不同的session中并發(fā)訪(fǎng)問(wèn)具有同一持久化標 識的業(yè)務(wù)對象實(shí)例的時(shí)候,這個(gè)業(yè)務(wù)對象的兩個(gè)實(shí)例事實(shí)上是不相同的(從 JVM識別來(lái)看)。這種沖突可以通過(guò)在同步和提交的時(shí)候使用自動(dòng)版本化和樂(lè ) 觀(guān)鎖定方法來(lái)解決。
這種方式把關(guān)于并發(fā)的頭疼問(wèn)題留給了Hibernate和數據庫;由于在單個(gè)線(xiàn)程內,操作單元中的對象識別不 需要代價(jià)昂貴的鎖定或其他意義上的同步,因此它同時(shí)可以提供最好的可伸縮性。只要在單個(gè)線(xiàn)程只持有一個(gè) Session,應用程序就不需要同步任何業(yè)務(wù)對象。在Session 的范圍內,應用程序可以放心的使用==進(jìn)行對象比較。
不過(guò),應用程序在Session的外面使用==進(jìn)行對象比較可能會(huì ) 導致無(wú)法預期的結果。在一些無(wú)法預料的場(chǎng)合,例如,如果你把兩個(gè)脫管對象實(shí)例放進(jìn)同一個(gè) Set的時(shí)候,就可能發(fā)生。這兩個(gè)對象實(shí)例可能有同一個(gè)數據庫標識(也就是說(shuō), 他們代表了表的同一行數據),從JVM標識的定義上來(lái)說(shuō),對脫管的對象而言,Hibernate無(wú)法保證他們 的的JVM標識一致。開(kāi)發(fā)人員必須覆蓋持久化類(lèi)的equals()方法和 hashCode() 方法,從而實(shí)現自定義的對象相等語(yǔ)義。警告:不要使用數據庫標識 來(lái)實(shí)現對象相等,應該使用業(yè)務(wù)鍵值,由唯一的,通常不變的屬性組成。當一個(gè)瞬時(shí)對象被持久化的時(shí) 候,它的數據庫標識會(huì )發(fā)生改變。如果一個(gè)瞬時(shí)對象(通常也包括脫管對象實(shí)例)被放入一 個(gè)Set,改變它的hashcode會(huì )導致與這個(gè)Set的關(guān)系中斷。雖 然業(yè)務(wù)鍵值的屬性不象數據庫主鍵那樣穩定不變,但是你只需要保證在同一個(gè)Set 中的對象屬性的穩定性就足夠了。請到Hibernate網(wǎng)站去尋求這個(gè)問(wèn)題更多的詳細的討論。請注意,這不是一 個(gè)有關(guān)Hibernate的問(wèn)題,而僅僅是一個(gè)關(guān)于Java對象標識和判等行為如何實(shí)現的問(wèn)題。
11.1.4. 常見(jiàn)問(wèn)題
決不要使用反模式session-per-user-session或者 session-per-application(當然,這個(gè)規定幾乎沒(méi)有例外)。請注意, 下述一些問(wèn)題可能也會(huì )出現在我們推薦的模式中,在你作出某個(gè)設計決定之前,請務(wù)必理解該模式的應用前提。
? Session 對象是非線(xiàn)程安全的。如果一個(gè)Session 實(shí)例允許共享的話(huà),那些支持并發(fā)運行的東東,例如HTTP request,session beans,或者是 Swing workers,將會(huì )導致出現資源爭用(race condition)。如果在HttpSession中有 Hibernate 的Session的話(huà)(稍后討論),你應該考慮同步訪(fǎng)問(wèn)你的Http session。 否則,只要用戶(hù)足夠快的點(diǎn)擊瀏覽器的?刷新?,就會(huì )導致兩個(gè)并發(fā)運行線(xiàn)程使用同一個(gè) Session。
? 一個(gè)由Hibernate拋出的異常意味著(zhù)你必須立即回滾數據庫事務(wù),并立即關(guān)閉Session (稍后會(huì )展開(kāi)討論)。如果你的Session綁定到一個(gè)應用程序上,你必 須停止該應用程序?;貪L數據庫事務(wù)并不會(huì )把你的業(yè)務(wù)對象退回到事務(wù)啟動(dòng)時(shí)候的狀態(tài)。這 意味著(zhù)數據庫狀態(tài)和業(yè)務(wù)對象狀態(tài)不同步。通常情況下,這不是什么問(wèn)題,因為異常是不可 恢復的,你必須在回滾之后重新開(kāi)始執行。
? Session 緩存了處于持久化狀態(tài)的每個(gè)對象(Hibernate會(huì )監視和檢查臟數據)。 這意味著(zhù),如果你讓Session打開(kāi)很長(cháng)一段時(shí)間,或是僅僅載入了過(guò)多的數據, Session占用的內存會(huì )一直增長(cháng),直到拋出OutOfMemoryException異常。這個(gè) 問(wèn)題的一個(gè)解決方法是調用clear() 和evict()來(lái)管理 Session的緩存,但是如果你需要大批量數據操作的話(huà),最好考慮 使用存儲過(guò)程。在第 13 章 批量處理(Batch processing)中有一些解決方案。在用戶(hù)會(huì )話(huà)期間一直保持 Session打開(kāi)也意味著(zhù)出現臟數據的可能性很高。
11.2. 數據庫事務(wù)聲明
數據庫(或者系統)事務(wù)的聲明總是必須的。在數據庫事務(wù)之外,就無(wú)法和數據庫通訊(這可能會(huì )讓那些習慣于 自動(dòng)提交事務(wù)模式的開(kāi)發(fā)人員感到迷惑)。永遠使用清晰的事務(wù)聲明,即使只讀操作也是如此。進(jìn)行 顯式的事務(wù)聲明并不總是需要的,這取決于你的事務(wù)隔離級別和數據庫的能力,但不管怎么說(shuō),聲明事務(wù)總歸有益無(wú)害。當然,一個(gè)單獨的數據庫事務(wù)總是比很多瑣碎的事務(wù)性能更好,即時(shí)對讀數據而言也是一樣。
一個(gè)Hibernate應用程序可以運行在非托管環(huán)境中(也就是獨立運行的應用程序,簡(jiǎn)單Web應用程序, 或者Swing圖形桌面應用程序),也可以運行在托管的J2EE環(huán)境中。在一個(gè)非托管環(huán)境中,Hibernate 通常自己負責管理數據庫連接池。應用程序開(kāi)發(fā)人員必須手工設置事務(wù)聲明,換句話(huà)說(shuō),就是手工啟 動(dòng),提交,或者回滾數據庫事務(wù)。一個(gè)托管的環(huán)境通常提供了容器管理事務(wù)(CMT),例如事務(wù)裝配通過(guò)可聲 明的方式定義在EJB session beans的部署描述符中??删幊淌绞聞?wù)聲明不再需要,即使是 Session 的同步也可以自動(dòng)完成。
讓持久層具備可移植性是人們的理想,這種移植發(fā)生在非托管的本地資源環(huán)境,與依賴(lài)JTA但是使用BMT而非CMT的系統之間。在兩種情況下你都可以使用編程式的事務(wù)管理。Hibernate提供了一套稱(chēng)為T(mén)ransaction的封裝API, 用來(lái)把你的部署環(huán)境中的本地事務(wù)管理系統轉換到Hibernate事務(wù)上。這個(gè)API是可選的,但是我們強烈 推薦你使用,除非你用CMT session bean。
通常情況下,結束 Session 包含了四個(gè)不同的階段:
? 同步session(flush,刷出到磁盤(pán))
? 提交事務(wù)
? 關(guān)閉session
? 處理異常
session的同步(flush,刷出)前面已經(jīng)討論過(guò)了,我們現在進(jìn)一步考察在托管和非托管環(huán)境下的事務(wù)聲明和異常處理。
11.2.1. 非托管環(huán)境
如果Hibernat持久層運行在一個(gè)非托管環(huán)境中,數據庫連接通常由Hibernate的簡(jiǎn)單(即非DataSource)連接池機制 來(lái)處理。session/transaction處理方式如下所示:
//Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
...
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
你不需要顯式flush() Session - 對commit()的調用會(huì )自動(dòng)觸發(fā)session的同步(取決于session的第 10.10 節 ?Session刷出(flush)?)。調用 close() 標志session的結束。close()方法重要的暗示是,session釋放了JDBC連接。這段Java代碼在非托管環(huán)境下和JTA環(huán)境下都可以運行。
更加靈活的方案是Hibernate內置的"current session"上下文管理,前文已經(jīng)講過(guò):
// Non-managed environment idiom with getCurrentSession()
try {
factory.getCurrentSession().beginTransaction();
...
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // or display error message
}
你很可能從未在一個(gè)通常的應用程序的業(yè)務(wù)代碼中見(jiàn)過(guò)這樣的代碼片斷:致命的(系統)異常應該總是 在應用程序?頂層?被捕獲。換句話(huà)說(shuō),執行Hibernate調用的代碼(在持久層)和處理 RuntimeException異常的代碼(通常只能清理和退出應用程序)應該在不同 的應用程序邏輯層。Hibernate的當前上下文管理可以極大地簡(jiǎn)化這一設計,你所有的一切就是SessionFactory。 異常處理將在本章稍后進(jìn)行討論。
請注意,你應該選擇 org.hibernate.transaction.JDBCTransactionFactory (這是默認選項),對第二個(gè)例子來(lái)說(shuō),hibernate.current_session_context_class應該是"thread"
11.2.2. 使用JTA
如果你的持久層運行在一個(gè)應用服務(wù)器中(例如,在EJB session beans的后面),Hibernate獲取 的每個(gè)數據源連接將自動(dòng)成為全局JTA事務(wù)的一部分。 你可以安裝一個(gè)獨立的JTA實(shí)現,使用它而不使用EJB。Hibernate提供了兩種策略進(jìn)行JTA集成。
如果你使用bean管理事務(wù)(BMT),可以通過(guò)使用Hibernate的 Transaction API來(lái)告訴 應用服務(wù)器啟動(dòng)和結束B(niǎo)MT事務(wù)。因此,事務(wù)管理代碼和在非托管環(huán)境下是一樣的。
// BMT idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
...
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
如果你希望使用與事務(wù)綁定的Session,也就是使用getCurrentSession()來(lái)簡(jiǎn)化上下文管理,你將不得不直接使用JTA UserTransactionAPI。
// BMT idiom with getCurrentSession()
try {
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or display error message
}
在CMT方式下,事務(wù)聲明是在session bean的部署描述符中,而不需要編程。 因此,代碼被簡(jiǎn)化為:
// CMT idiom
Session sess = factory.getCurrentSession();
...
注意,當你配置Hibernate的transaction factory的時(shí)候,在直接使用JTA的時(shí)候(BMT),你應該選擇org.hibernate.transaction.JTATransactionFactory,在CMT session bean中選擇org.hibernate.transaction.CMTTransactionFactory。記得也要設置hibernate.transaction.manager_lookup_class。還有,確認你的hibernate.current_session_context_class未設置(為了向下兼容),或者設置為"jta"。
getCurrentSession()在JTA環(huán)境中有一個(gè)弊端。對after_statement連接釋放方式有一個(gè)警告,這是被默認使用的。因為JTA規范的一個(gè)很愚蠢的限制,Hibernate不可能自動(dòng)清理任何未關(guān)閉的ScrollableResults 或者Iterator,它們是由scroll()或iterate()產(chǎn)生的。你must通過(guò)在finally塊中,顯式調用ScrollableResults.close()或者Hibernate.close(Iterator)方法來(lái)釋放底層數據庫游標。(當然,大部分程序完全可以很容易的避免在JTA或CMT代碼中出現scroll()或iterate()。)
11.2.3. 異常處理
如果 Session 拋出異常 (包括任何SQLException), 你應該立即回滾數據庫事務(wù),調用 Session.close() ,丟棄該 Session實(shí)例。Session的某些方法可能會(huì )導致session 處于不一致的狀態(tài)。所有由Hibernate拋出的異常都視為不可以恢復的。確保在 finally 代碼塊中調用close()方法,以關(guān)閉掉 Session。
HibernateException是一個(gè)非檢查期異常(這不同于Hibernate老的版本), 它封裝了Hibernate持久層可能出現的大多數錯誤。我們的觀(guān)點(diǎn)是,不應該強迫應用程序開(kāi)發(fā)人員 在底層捕獲無(wú)法恢復的異常。在大多數軟件系統中,非檢查期異常和致命異常都是在相應方法調用 的堆棧的頂層被處理的(也就是說(shuō),在軟件上面的邏輯層),并且提供一個(gè)錯誤信息給應用軟件的用戶(hù) (或者采取其他某些相應的操作)。請注意,Hibernate也有可能拋出其他并不屬于 HibernateException的非檢查期異常。這些異常同樣也是無(wú)法恢復的,應該 采取某些相應的操作去處理。
在和數據庫進(jìn)行交互時(shí),Hibernate把捕獲的SQLException封裝為Hibernate的 JDBCException。事實(shí)上,Hibernate嘗試把異常轉換為更有實(shí)際含義 的JDBCException異常的子類(lèi)。底層的SQLException可以 通過(guò)JDBCException.getCause()來(lái)得到。Hibernate通過(guò)使用關(guān)聯(lián)到 SessionFactory上的SQLExceptionConverter來(lái) 把SQLException轉換為一個(gè)對應的JDBCException 異常的子類(lèi)。默認情況下,SQLExceptionConverter可以通過(guò)配置dialect 選項指定;此外,也可以使用用戶(hù)自定義的實(shí)現類(lèi)(參考javadocs SQLExceptionConverterFactory類(lèi)來(lái)了解詳情)。標準的 JDBCException子類(lèi)型是:
? JDBCConnectionException - 指明底層的JDBC通訊出現錯誤
? SQLGrammarException - 指明發(fā)送的SQL語(yǔ)句的語(yǔ)法或者格式錯誤
? ConstraintViolationException - 指明某種類(lèi)型的約束違例錯誤
? LockAcquisitionException - 指明了在執行請求操作時(shí),獲取 所需的鎖級別時(shí)出現的錯誤。
? GenericJDBCException - 不屬于任何其他種類(lèi)的原生異常
11.2.4. 事務(wù)超時(shí)
EJB這樣的托管環(huán)境有一項極為重要的特性,而它從未在非托管環(huán)境中提供過(guò),那就是事務(wù)超時(shí)。在出現錯誤的事務(wù)行為的時(shí)候,超時(shí)可以確保不會(huì )無(wú)限掛起資源、對用戶(hù)沒(méi)有交代。在托管(JTA)環(huán)境之外,Hibernate無(wú)法完全提供這一功能。但是,Hiberante至少可以控制數據訪(fǎng)問(wèn),確保數據庫級別的死鎖,和返回巨大結果集的查詢(xún)被限定在一個(gè)規定的時(shí)間內。在托管環(huán)境中,Hibernate會(huì )把事務(wù)超時(shí)轉交給JTA。這一功能通過(guò)Hibernate Transaction對象進(jìn)行抽象。
Session sess = factory.openSession();
try {
//set transaction timeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
...
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}
注意setTimeout()不應該在CMT bean中調用,此時(shí)事務(wù)超時(shí)值應該是被聲明式定義的。
11.3. 樂(lè )觀(guān)并發(fā)控制(Optimistic concurrency control)
唯一能夠同時(shí)保持高并發(fā)和高可伸縮性的方法就是使用帶版本化的樂(lè )觀(guān)并發(fā)控制。版本檢查使用版本號、 或者時(shí)間戳來(lái)檢測更新沖突(并且防止更新丟失)。Hibernate為使用樂(lè )觀(guān)并發(fā)控制的代碼提供了三種可 能的方法,應用程序在編寫(xiě)這些代碼時(shí),可以采用它們。我們已經(jīng)在前面應用程序對話(huà)那部分展示了 樂(lè )觀(guān)并發(fā)控制的應用場(chǎng)景,此外,在單個(gè)數據庫事務(wù)范圍內,版本檢查也提供了防止更新丟失的好處。
11.3.1. 應用程序級別的版本檢查(Application version checking)
未能充分利用Hibernate功能的實(shí)現代碼中,每次和數據庫交互都需要一個(gè)新的 Session,而且開(kāi)發(fā)人員必須在顯示數據之前從數據庫中重 新載入所有的持久化對象實(shí)例。這種方式迫使應用程序自己實(shí)現版本檢查來(lái)確保 對話(huà)事務(wù)的隔離,從數據訪(fǎng)問(wèn)的角度來(lái)說(shuō)是最低效的。這種使用方式和 entity EJB最相似。
// foo is an instance loaded by a previous Session
session = factory.openSession();
Transaction t = session.beginTransaction();
session.load( foo, foo.getKey() ); // load the current state
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar");
session.close();
version 屬性使用 <version>來(lái)映射,如果對象 是臟數據,在同步的時(shí)候,Hibernate會(huì )自動(dòng)增加版本號。
當然,如果你的應用是在一個(gè)低數據并發(fā)環(huán)境下,并不需要版本檢查的話(huà),你照樣可以使用 這種方式,只不過(guò)跳過(guò)版本檢查就是了。在這種情況下,最晚提交生效 (last commit wins)就是你的長(cháng)對話(huà)的默認處理策略。 請記住這種策略可能會(huì )讓?xiě)密浖挠脩?hù)感到困惑,因為他們有可能會(huì )碰上更新丟失掉卻沒(méi) 有出錯信息,或者需要合并更改沖突的情況。
很明顯,手工進(jìn)行版本檢查只適合于某些軟件規模非常小的應用場(chǎng)景,對于大多數軟件應用場(chǎng)景 來(lái)說(shuō)并不現實(shí)。通常情況下,不僅是單個(gè)對象實(shí)例需要進(jìn)行版本檢查,整個(gè)被修改過(guò)的關(guān) 聯(lián)對象圖也都需要進(jìn)行版本檢查。作為標準設計范例,Hibernate使用擴展周期的 Session的方式,或者脫管對象實(shí)例的方式來(lái)提供自動(dòng)版本檢查。
11.3.2. 擴展周期的session和自動(dòng)版本化
單個(gè) Session實(shí)例和它所關(guān)聯(lián)的所有持久化對象實(shí)例都被用于整個(gè) 對話(huà),這被稱(chēng)為session-per-conversation。Hibernate在同步的時(shí)候進(jìn)行對象實(shí)例的版本檢查,如果檢測到并發(fā)修 改則拋出異常。由開(kāi)發(fā)人員來(lái)決定是否需要捕獲和處理這個(gè)異常(通常的抉擇是給用戶(hù) 提供一個(gè)合并更改,或者在無(wú)臟數據情況下重新進(jìn)行業(yè)務(wù)對話(huà)的機會(huì ))。
在等待用戶(hù)交互的時(shí)候, Session 斷開(kāi)底層的JDBC連接。這種方式 以數據庫訪(fǎng)問(wèn)的角度來(lái)說(shuō)是最高效的方式。應用程序不需要關(guān)心版本檢查或脫管對象實(shí)例 的重新關(guān)聯(lián),在每個(gè)數據庫事務(wù)中,應用程序也不需要載入讀取對象實(shí)例。
// foo is an instance loaded earlier by the old session
Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction
t.commit(); // Also return JDBC connection
session.close(); // Only for last transaction in conversation
foo對象知道它是在哪個(gè)Session中被裝入的。在一個(gè)舊session中開(kāi)啟一個(gè)新的數據庫事務(wù),會(huì )導致session獲取一個(gè)新的連接,并恢復session的功能。將數據庫事務(wù)提交,使得session從JDBC連接斷開(kāi),并將此連接交還給連接池。在重新連接之后,要強制對你沒(méi)有更新的數據進(jìn)行一次版本檢查,你可以對所有可能被其他事務(wù)修改過(guò)的對象,使用參數LockMode.READ來(lái)調用Session.lock()。你不用lock任何你正在更新的數據。一般你會(huì )在擴展的Session上設置FlushMode.NEVER,因此只有最后一個(gè)數據庫事務(wù)循環(huán)才會(huì )真正的吧整個(gè)對話(huà)中發(fā)生的修改發(fā)送到數據庫。因此,只有這最后一次數據庫事務(wù)才會(huì )包含flush()操作,然后在整個(gè)對話(huà)結束后,還要close()這個(gè)session。
如果在用戶(hù)思考的過(guò)程中,Session因為太大了而不能保存,那么這種模式是有 問(wèn)題的。舉例來(lái)說(shuō),一個(gè)HttpSession應該盡可能的小。由于 Session是一級緩存,并且保持了所有被載入過(guò)的對象,因此 我們只應該在那些少量的request/response情況下使用這種策略。你應該只把一個(gè)Session用于單個(gè)對話(huà),因為它很快就會(huì )出現臟數據。
(注意,早期的Hibernate版本需要明確的對Session進(jìn)行disconnec和reconnect。這些方法現在已經(jīng)過(guò)時(shí)了,打開(kāi)事務(wù)和關(guān)閉事務(wù)會(huì )起到同樣的效果。)
此外,也請注意,你應該讓與數據庫連接斷開(kāi)的Session對持久層保持 關(guān)閉狀態(tài)。換句話(huà)說(shuō),在三層環(huán)境中,使用有狀態(tài)的EJB session bean來(lái)持有Session, 而不要把它傳遞到web層(甚至把它序列化到一個(gè)單獨的層),保存在HttpSession中。
擴展session模式,或者被稱(chēng)為每次對話(huà)一個(gè)session(session-per-conversation), 在與自動(dòng)管理當前session上下文聯(lián)用的時(shí)候會(huì )更困難。你需要提供你自己的CurrentSessionContext實(shí)現。請參閱Hibernate Wiki以獲得示例。
11.3.3. 脫管對象(deatched object)和自動(dòng)版本化
這種方式下,與持久化存儲的每次交互都發(fā)生在一個(gè)新的Session中。 然而,同一持久化對象實(shí)例可以在多次與數據庫的交互中重用。應用程序操縱脫管對象實(shí)例 的狀態(tài),這個(gè)脫管對象實(shí)例最初是在另一個(gè)Session 中載入的,然后 調用 Session.update(),Session.saveOrUpdate(), 或者 Session.merge() 來(lái)重新關(guān)聯(lián)該對象實(shí)例。
// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
Transaction t = session.beginTransaction();
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
t.commit();
session.close();
Hibernate會(huì )再一次在同步的時(shí)候檢查對象實(shí)例的版本,如果發(fā)生更新沖突,就拋出異常。
如果你確信對象沒(méi)有被修改過(guò),你也可以調用lock() 來(lái)設置 LockMode.READ(繞過(guò)所有的緩存,執行版本檢查),從而取 代 update()操作。
11.3.4. 定制自動(dòng)版本化行為
對于特定的屬性和集合,通過(guò)為它們設置映射屬性optimistic-lock的值 為false,來(lái)禁止Hibernate的版本自動(dòng)增加。這樣的話(huà),如果該屬性 臟數據,Hibernate將不再增加版本號。
遺留系統的數據庫Schema通常是靜態(tài)的,不可修改的?;蛘?,其他應用程序也可能訪(fǎng)問(wèn)同一數據 庫,根本無(wú)法得知如何處理版本號,甚至時(shí)間戳。在以上的所有場(chǎng)景中,實(shí)現版本化不能依靠 數據庫表的某個(gè)特定列。在<class>的映射中設置 optimistic-lock="all"可以在沒(méi)有版本或者時(shí)間戳屬性映射的情況下實(shí)現 版本檢查,此時(shí)Hibernate將比較一行記錄的每個(gè)字段的狀態(tài)。請注意,只有當Hibernate能夠比 較新舊狀態(tài)的情況下,這種方式才能生效,也就是說(shuō), 你必須使用單個(gè)長(cháng)生命周期Session模式,而不能使用 session-per-request-with-detached-objects模式。
有些情況下,只要更改不發(fā)生交錯,并發(fā)修改也是允許的。當你在<class> 的映射中設置optimistic-lock="dirty",Hibernate在同步的時(shí)候將只比較有臟 數據的字段。
在以上所有場(chǎng)景中,不管是專(zhuān)門(mén)設置一個(gè)版本/時(shí)間戳列,還是進(jìn)行全部字段/臟數據字段比較, Hibernate都會(huì )針對每個(gè)實(shí)體對象發(fā)送一條UPDATE(帶有相應的 WHERE語(yǔ)句 )的SQL語(yǔ)句來(lái)執行版本檢查和數據更新。如果你對關(guān)聯(lián)實(shí)體 設置級聯(lián)關(guān)系使用傳播性持久化(transitive persistence),那么Hibernate可能會(huì )執行不必 要的update語(yǔ)句。這通常不是個(gè)問(wèn)題,但是數據庫里面對on update點(diǎn)火 的觸發(fā)器可能在脫管對象沒(méi)有任何更改的情況下被觸發(fā)。因此,你可以在 <class>的映射中,通過(guò)設置select-before-update="true" 來(lái)定制這一行為,強制Hibernate SELECT這個(gè)對象實(shí)例,從而保證, 在更新記錄之前,對象的確是被修改過(guò)。
11.4. 悲觀(guān)鎖定(Pessimistic Locking)
用戶(hù)其實(shí)并不需要花很多精力去擔心鎖定策略的問(wèn)題。通常情況下,只要為JDBC連接指定一下隔 離級別,然后讓數據庫去搞定一切就夠了。然而,高級用戶(hù)有時(shí)候希望進(jìn)行一個(gè)排它的悲觀(guān)鎖定, 或者在一個(gè)新的事務(wù)啟動(dòng)的時(shí)候,重新進(jìn)行鎖定。
Hibernate總是使用數據庫的鎖定機制,從不在內存中鎖定對象!
類(lèi)LockMode 定義了Hibernate所需的不同的鎖定級別。一個(gè)鎖定 可以通過(guò)以下的機制來(lái)設置:
? 當Hibernate更新或者插入一行記錄的時(shí)候,鎖定級別自動(dòng)設置為L(cháng)ockMode.WRITE。
? 當用戶(hù)顯式的使用數據庫支持的SQL格式SELECT ... FOR UPDATE 發(fā)送SQL的時(shí)候,鎖定級別設置為L(cháng)ockMode.UPGRADE
? 當用戶(hù)顯式的使用Oracle數據庫的SQL語(yǔ)句SELECT ... FOR UPDATE NOWAIT 的時(shí)候,鎖定級別設置LockMode.UPGRADE_NOWAIT
? 當Hibernate在?可重復讀?或者是?序列化?數據庫隔離級別下讀取數據的時(shí)候,鎖定模式 自動(dòng)設置為L(cháng)ockMode.READ。這種模式也可以通過(guò)用戶(hù)顯式指定進(jìn)行設置。
? LockMode.NONE 代表無(wú)需鎖定。在Transaction結束時(shí), 所有的對象都切換到該模式上來(lái)。與session相關(guān)聯(lián)的對象通過(guò)調用update() 或者saveOrUpdate()脫離該模式。
"顯式的用戶(hù)指定"可以通過(guò)以下幾種方式之一來(lái)表示:
? 調用 Session.load()的時(shí)候指定鎖定模式(LockMode)。
? 調用Session.lock()。
? 調用Query.setLockMode()。
如果在UPGRADE或者UPGRADE_NOWAIT鎖定模式下調 用Session.load(),并且要讀取的對象尚未被session載入過(guò),那么對象 通過(guò)SELECT ... FOR UPDATE這樣的SQL語(yǔ)句被載入。如果為一個(gè)對象調用 load()方法時(shí),該對象已經(jīng)在另一個(gè)較少限制的鎖定模式下被載入了,那 么Hibernate就對該對象調用lock() 方法。
如果指定的鎖定模式是READ, UPGRADE 或 UPGRADE_NOWAIT,那么Session.lock()就 執行版本號檢查。(在UPGRADE 或者UPGRADE_NOWAIT 鎖定模式下,執行SELECT ... FOR UPDATE這樣的SQL語(yǔ)句。)
如果數據庫不支持用戶(hù)設置的鎖定模式,Hibernate將使用適當的替代模式(而不是扔出異常)。 這一點(diǎn)可以確保應用程序的可移植性。
11.5. 連接釋放模式(Connection Release Modes)
Hibernate關(guān)于JDBC連接管理的舊(2.x)行為是,Session在第一次需要的時(shí)候獲取一個(gè)連接,在session關(guān)閉之前一直會(huì )持有這個(gè)連接。Hibernate引入了連接釋放的概念,來(lái)告訴session如何處理它的JDBC連接。注意,下面的討論只適用于采用配置ConnectionProvider來(lái)提供連接的情況,用戶(hù)自己提供的連接與這里的討論無(wú)關(guān)。通過(guò)org.hibernate.ConnectionReleaseMode的不同枚舉值來(lái)使用不用的釋放模式:
? ON_CLOSE - 基本上就是上面提到的老式行為。Hibernate session在第一次需要進(jìn)行JDBC操作的時(shí)候獲取連接,然后持有它,直到session關(guān)閉。
? AFTER_TRANSACTION - 在org.hibernate.Transaction結束后釋放連接。
? AFTER_STATEMENT (也被稱(chēng)做積極釋放) - 在每一條語(yǔ)句被執行后就釋放連接。但假若語(yǔ)句留下了與session相關(guān)的資源,那就不會(huì )被釋放。目前唯一的這種情形就是使用org.hibernate.ScrollableResults。
hibernate.connection.release_mode配置參數用來(lái)指定使用哪一種釋放模式??赡艿闹涤校?
? auto(默認) - 這一選擇把釋放模式委派給org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode()方法。對JTATransactionFactory來(lái)說(shuō),它會(huì )返回ConnectionReleaseMode.AFTER_STATEMENT;對JDBCTransactionFactory來(lái)說(shuō),則是ConnectionReleaseMode.AFTER_TRANSACTION。很少需要修改這一默認行為,因為假若設置不當,就會(huì )帶來(lái)bug,或者給用戶(hù)代碼帶來(lái)誤導。
? on_close - 使用 ConnectionReleaseMode.ON_CLOSE. 這種方式是為了向下兼容的,但是已經(jīng)完全不被鼓勵使用了。
? after_transaction - 使用ConnectionReleaseMode.AFTER_TRANSACTION。這一設置不應該在JTA環(huán)境下使用。也要注意,使用ConnectionReleaseMode.AFTER_TRANSACTION的時(shí)候,假若session 處于auto-commit狀態(tài),連接會(huì )像AFTER_STATEMENT那樣被釋放。
? after_statement - 使用ConnectionReleaseMode.AFTER_STATEMENT。除此之外,會(huì )查詢(xún)配置的ConnectionProvider,是否它支持這一設置((supportsAggressiveRelease()))。假若不支持,釋放模式會(huì )被設置為ConnectionReleaseMode.AFTER_TRANSACTION。只有在你每次調用ConnectionProvider.getConnection()獲取底層JDBC連接的時(shí)候,都可以確信獲得同一個(gè)連接的時(shí)候,這一設置才是安全的;或者在auto-commit環(huán)境中,你可以不管是否每次都獲得同一個(gè)連接的時(shí)候,這才是安全的。
第 12 章 攔截器與事件(Interceptors and events)
應用程序能夠響應Hibernate內部產(chǎn)生的特定事件是非常有用的。這樣就允許實(shí)現某些通用的功能 以及允許對Hibernate功能進(jìn)行擴展。
12.1. 攔截器(Interceptors)
Interceptor接口提供了從會(huì )話(huà)(session)回調(callback)應用程序(application)的機制, 這種回調機制可以允許應用程序在持久化對象被保存、更新、刪除或是加載之前,檢查并(或)修改其 屬性。一個(gè)可能的用途,就是用來(lái)跟蹤審核(auditing)信息。例如:下面的這個(gè)攔截器,會(huì )在一個(gè)實(shí)現了 Auditable接口的對象被創(chuàng )建時(shí)自動(dòng)地設置createTimestamp屬性,并在實(shí)現了 Auditable接口的對象被更新時(shí),同步更新lastUpdateTimestamp屬性。
你可以直接實(shí)現Interceptor接口,也可以(最好)繼承自EmptyInterceptor。
package org.hibernate.test;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
private int creates;
private int loads;
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// do nothing
}
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
updates++;
for ( int i=0; i < propertyNames.length; i++ ) {
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
currentState[i] = new Date();
return true;
}
}
}
return false;
}
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
loads++;
}
return false;
}
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
creates++;
for ( int i=0; i<propertyNames.length; i++ ) {
if ( "createTimestamp".equals( propertyNames[i] ) ) {
state[i] = new Date();
return true;
}
}
}
return false;
}
if ( tx.wasCommitted() ) {
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
}
updates=0;
creates=0;
loads=0;
}
攔截器可以有兩種:Session范圍內的,和SessionFactory范圍內的。
當使用某個(gè)重載的SessionFactory.openSession()使用Interceptor作為參數調用打開(kāi)一個(gè)session的時(shí)候,就指定了Session范圍內的攔截器。
Session session = sf.openSession( new AuditInterceptor() );
SessionFactory范圍內的攔截器要通過(guò)Configuration中注冊,而這必須在創(chuàng )建SessionFactory之前。在這種情況下,給出的攔截器會(huì )被這個(gè)SessionFactory所打開(kāi)的所有session使用了;除非session打開(kāi)時(shí)明確指明了使用的攔截器。SessionFactory范圍內的攔截器,必須是線(xiàn)程安全的,因為多個(gè)session可能并發(fā)使用這個(gè)攔截器,要因此小心不要保存與session相關(guān)的狀態(tài)。
new Configuration().setInterceptor( new AuditInterceptor() );
12.2. 事件系統(Event system)
如果需要響應持久層的某些特殊事件,你也可以使用Hibernate3的事件框架。 該事件系統可以用來(lái)替代攔截器,也可以作為攔截器的補充來(lái)使用。
基本上,Session接口的每個(gè)方法都有相對應的事件。比如 LoadEvent,FlushEvent,等等(查閱XML配置文件 的DTD,以及org.hibernate.event包來(lái)獲得所有已定義的事件的列表)。當某個(gè)方 法被調用時(shí),Hibernate Session會(huì )生成一個(gè)相對應的事件并激活所 有配置好的事件監聽(tīng)器。系統預設的監聽(tīng)器實(shí)現的處理過(guò)程就是被監聽(tīng)的方法要做的(被監聽(tīng)的方法所做的其實(shí)僅僅是激活監聽(tīng)器, ?實(shí)際?的工作是由監聽(tīng)器完成的)。不過(guò),你可以自由地選擇實(shí)現 一個(gè)自己定制的監聽(tīng)器(比如,實(shí)現并注冊用來(lái)處理處理LoadEvent的LoadEventListener接口), 來(lái)負責處理所有的調用Session的load()方法的請求。
監聽(tīng)器應該被看作是單例(singleton)對象,也就是說(shuō),所有同類(lèi)型的事件的處理共享同一個(gè)監聽(tīng)器實(shí)例,因此監聽(tīng)器 不應該保存任何狀態(tài)(也就是不應該使用成員變量)。
用戶(hù)定制的監聽(tīng)器應該實(shí)現與所要處理的事件相對應的接口,或者從一個(gè)合適的基類(lèi)繼承(甚至是從Hibernate自帶的默認事件監聽(tīng)器類(lèi)繼承, 為了方便你這樣做,這些類(lèi)都被聲明成non-final的了)。用戶(hù)定制的監聽(tīng)器可以通過(guò)編程使用Configuration對象 來(lái)注冊,也可以在Hibernate的XML格式的配置文件中進(jìn)行聲明(不支持在Properties格式的配置文件聲明監聽(tīng)器)。 下面是一個(gè)用戶(hù)定制的加載事件(load event)的監聽(tīng)器:
public class MyLoadListener implements LoadEventListener {
// this is the single method defined by the LoadEventListener interface
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
throw MySecurityException("Unauthorized access");
}
}
}
你還需要修改一處配置,來(lái)告訴Hibernate,除了默認的監聽(tīng)器,還要附加選定的監聽(tīng)器。
<hibernate-configuration>
<session-factory>
...
<event type="load">
<listener class="com.eg.MyLoadListener"/>
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
</event>
</session-factory>
</hibernate-configuration>
看看用另一種方式,通過(guò)編程的方式來(lái)注冊它。
Configuration cfg = new Configuration();
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);
通過(guò)在XML配置文件聲明而注冊的監聽(tīng)器不能共享實(shí)例。如果在多個(gè)<listener/>節點(diǎn)中使用 了相同的類(lèi)的名字,則每一個(gè)引用都將會(huì )產(chǎn)生一個(gè)獨立的實(shí)例。如果你需要在多個(gè)監聽(tīng)器類(lèi)型之間共享 監聽(tīng)器的實(shí)例,則你必須使用編程的方式來(lái)進(jìn)行注冊。
為什么我們實(shí)現了特定監聽(tīng)器的接口,在注冊的時(shí)候還要明確指出我們要注冊哪個(gè)事件的監聽(tīng)器呢? 這是因為一個(gè)類(lèi)可能實(shí)現多個(gè)監聽(tīng)器的接口。在注冊的時(shí)候明確指定要監聽(tīng)的事件,可以讓啟用或者禁用對某個(gè)事件的監聽(tīng)的配置工作簡(jiǎn)單些。
12.3. Hibernate的聲明式安全機制
通常,Hibernate應用程序的聲明式安全機制由會(huì )話(huà)外觀(guān)層(session facade)所管理。 現在,Hibernate3允許某些特定的行為由JACC進(jìn)行許可管理,由JAAS進(jìn)行授權管理。 本功能是一個(gè)建立在事件框架之上的可選的功能。
首先,你必須要配置適當的事件監聽(tīng)器(event listener),來(lái)激活使用JAAS管理授權的功能。
<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>
注意,<listener type="..." class="..."/>只是<event type="..."><listener class="..."/></event>的簡(jiǎn)寫(xiě),對每一個(gè)事件類(lèi)型都必須嚴格的有一個(gè)監聽(tīng)器與之對應。
接下來(lái),仍然在hibernate.cfg.xml文件中,綁定角色的權限:
<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>
這些角色的名字就是你的JACC provider所定義的角色的名字。
第 13 章 批量處理(Batch processing)
使用Hibernate將 100 000 條記錄插入到數據庫的一個(gè)很自然的做法可能是這樣的
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
}
tx.commit();
session.close();
這段程序大概運行到 50 000 條記錄左右會(huì )失敗并拋出 內存溢出異常(OutOfMemoryException) 。 這是因為 Hibernate 把所有新插入的 客戶(hù)(Customer)實(shí)例在 session級別的緩存區進(jìn)行了緩存的緣故。
我們會(huì )在本章告訴你如何避免此類(lèi)問(wèn)題。首先,如果你要執行批量處理并且想要達到一個(gè)理想的性能, 那么使用JDBC的批量(batching)功能是至關(guān)重要。將JDBC的批量抓取數量(batch size)參數設置到一個(gè)合適值 (比如,10-50之間):
hibernate.jdbc.batch_size 20
你也可能想在執行批量處理時(shí)關(guān)閉二級緩存:
hibernate.cache.use_second_level_cache false
但是,這不是絕對必須的,因為我們可以顯式設置CacheMode來(lái)關(guān)閉與二級緩存的交互。
13.1. 批量插入(Batch inserts)
如果要將很多對象持久化,你必須通過(guò)經(jīng)常的調用 flush() 以及稍后調用 clear() 來(lái)控制第一級緩存的大小。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size //20,與JDBC批量設置相同
//flush a batch of inserts and release memory:
//將本批插入的對象立即寫(xiě)入數據庫并釋放內存
session.flush();
session.clear();
}
}
tx.commit();
session.close();
13.2. 批量更新(Batch updates)
此方法同樣適用于檢索和更新數據。此外,在進(jìn)行會(huì )返回很多行數據的查詢(xún)時(shí), 你需要使用 scroll() 方法以便充分利用服務(wù)器端游標所帶來(lái)的好處。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
if ( ++count % 20 == 0 ) {
//flush a batch of updates and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
13.3. StatelessSession (無(wú)狀態(tài)session)接口
作為選擇,Hibernate提供了基于命令的API,可以用detached object的形式把數據以流的方法加入到數據庫,或從數據庫輸出。StatelessSession沒(méi)有持久化上下文,也不提供多少高層的生命周期語(yǔ)義。特別是,無(wú)狀態(tài)session不實(shí)現第一級cache,也不和第二級緩存,或者查詢(xún)緩存交互。它不實(shí)現事務(wù)化寫(xiě),也不實(shí)現臟數據檢查。用stateless session進(jìn)行的操作甚至不級聯(lián)到關(guān)聯(lián)實(shí)例。stateless session忽略集合類(lèi)(Collections)。通過(guò)stateless session進(jìn)行的操作不觸發(fā)Hibernate的事件模型和攔截器。無(wú)狀態(tài)session對數據的混淆現象免疫,因為它沒(méi)有第一級緩存。無(wú)狀態(tài)session是低層的抽象,和低層JDBC相當接近。
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();
注意在上面的例子中,查詢(xún)返回的Customer實(shí)例立即被脫管(detach)。它們與任何持久化上下文都沒(méi)有關(guān)系。
StatelessSession 接口定義的insert(), update() 和 delete()操作是直接的數據庫行級別操作,其結果是立刻執行一條INSERT, UPDATE 或 DELETE 語(yǔ)句。因此,它們的語(yǔ)義和Session 接口定義的save(), saveOrUpdate() 和delete() 操作有很大的不同。
13.4. DML(數據操作語(yǔ)言)風(fēng)格的操作(DML-style operations)
hence manipulating (using the SQL Data Manipulation Language (DML) statements: INSERT, UPDATE, DELETE) data directly in the database will not affect in-memory state. However, Hibernate provides methods for bulk SQL-style DML statement execution which are performed through the Hibernate Query Language (第 14 章 HQL: Hibernate查詢(xún)語(yǔ)言). 就像已經(jīng)討論的那樣,自動(dòng)和透明的 對象/關(guān)系 映射(object/relational mapping)關(guān)注于管理對象的狀態(tài)。 這就意味著(zhù)對象的狀態(tài)存在于內存,因此直接操作 (使用 SQL Data Manipulation Language(DML,數據操作語(yǔ)言)語(yǔ)句 :INSERT ,UPDATE 和 DELETE) 數據庫中的數據將不會(huì )影響內存中的對象狀態(tài)和對象數據。 不過(guò),Hibernate提供通過(guò)Hibernate查詢(xún)語(yǔ)言(第 14 章 HQL: Hibernate查詢(xún)語(yǔ)言)來(lái)執行大批 量SQL風(fēng)格的DML語(yǔ)句的方法。
UPDATE 和 DELETE語(yǔ)句的語(yǔ)法為: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)? 有幾點(diǎn)說(shuō)明:
? 在FROM子句(from-clause)中,FROM關(guān)鍵字是可選的
? 在FROM子句(from-clause)中只能有一個(gè)實(shí)體名,它可以是別名。如果實(shí)體名是別名,那么任何被引用的屬性都必須加上此別名的前綴;如果不是別名,那么任何有前綴的屬性引用都是非法的。
? 不能在大批量HQL語(yǔ)句中使用第 14.4 節 ?join 語(yǔ)法的形式?(顯式或者隱式的都不行)。不過(guò)在WHERE子句中可以使用子查詢(xún)??梢栽趙here子句中使用子查詢(xún),子查詢(xún)本身可以包含join。
? 整個(gè)WHERE子句是可選的。
舉個(gè)例子,使用Query.executeUpdate()方法執行一個(gè)HQL UPDATE語(yǔ)句(: (方法命名是來(lái)源于JDBC‘s PreparedStatement.executeUpdate()):
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
HQL UPDATE語(yǔ)句,默認不會(huì )影響更新實(shí)體的第 5.1.7 節 ?版本(version)(可選)?或者第 5.1.8 節 ?timestamp (可選)?屬性值。這和EJB3規范是一致的。但是,通過(guò)使用versioned update,你可以強制Hibernate正確的重置version或者timestamp屬性值。這通過(guò)在UPDATE關(guān)鍵字后面增加VERSIONED關(guān)鍵字來(lái)實(shí)現的。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
注意,自定義的版本類(lèi)型(org.hibernate.usertype.UserVersionType)不允許和update versioned語(yǔ)句聯(lián)用。
執行一個(gè)HQL DELETE,同樣使用 Query.executeUpdate() 方法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();
由Query.executeUpdate()方法返回的整型值表明了受此操作影響的記錄數量。 注意這個(gè)數值可能與數據庫中被(最后一條SQL語(yǔ)句)影響了的?行?數有關(guān),也可能沒(méi)有。一個(gè)大批量HQL操作可能導致多條實(shí)際的SQL語(yǔ)句被執行, 舉個(gè)例子,對joined-subclass映射方式的類(lèi)進(jìn)行的此類(lèi)操作。這個(gè)返回值代表了實(shí)際被語(yǔ)句影響了的記錄數量。在那個(gè)joined-subclass的例子中, 對一個(gè)子類(lèi)的刪除實(shí)際上可能不僅僅會(huì )刪除子類(lèi)映射到的表而且會(huì )影響?根?表,還有可能影響與之有繼承關(guān)系的joined-subclass映射方式的子類(lèi)的表。
INSERT語(yǔ)句的偽碼是: INSERT INTO EntityName properties_list select_statement. 要注意的是:
? 只支持INSERT INTO ... SELECT ...形式,不支持INSERT INTO ... VALUES ...形式.
properties_list和SQL INSERT語(yǔ)句中的字段定義(column speficiation)類(lèi)似。對參與繼承樹(shù)映射的實(shí)體而言,只有直接定義在給定的類(lèi)級別的屬性才能直接在properties_list中使用。超類(lèi)的屬性不被支持;子類(lèi)的屬性無(wú)意義。換句話(huà)說(shuō),INSERT天生不支持多態(tài)。
? select_statement可以是任何合法的HQL選擇查詢(xún),不過(guò)要保證返回類(lèi)型必須和要插入的類(lèi)型完全匹配。目前,這一檢查是在查詢(xún)編譯的時(shí)候進(jìn)行的,而不是把它交給數據庫。注意,在HibernateType間如果只是等價(jià)(equivalent)而非相等(equal),會(huì )導致問(wèn)題。定義為org.hibernate.type.DateType和org.hibernate.type.TimestampType的兩個(gè)屬性可能會(huì )產(chǎn)生類(lèi)型不匹配錯誤,雖然數據庫級可能不加區分或者可以處理這種轉換。
? 對id屬性來(lái)說(shuō),insert語(yǔ)句給你兩個(gè)選擇。你可以明確地在properties_list表中指定id屬性(這樣它的值是從對應的select表達式中獲得),或者在properties_list中省略它(此時(shí)使用生成指)。后一種選擇只有當使用在數據庫中生成值的id產(chǎn)生器時(shí)才能使用;如果是?內存?中計算的類(lèi)型生成器,在解析時(shí)會(huì )拋出一個(gè)異常。注意,為了說(shuō)明這一問(wèn)題,數據庫產(chǎn)生值的生成器是org.hibernate.id.SequenceGenerator(和它的子類(lèi)),以及任何org.hibernate.id.PostInsertIdentifierGenerator接口的實(shí)現。這兒最值得注意的意外是org.hibernate.id.TableHiLoGenerator,它不能在此使用,因為它沒(méi)有得到其值的途徑。
? 對映射為version 或 timestamp的屬性來(lái)說(shuō),insert語(yǔ)句也給你兩個(gè)選擇,你可以在properties_list表中指定(此時(shí)其值從對應的select表達式中獲得),或者在properties_list中省略它(此時(shí),使用在org.hibernate.type.VersionType 中定義的seed value(種子值))。
執行HQL INSERT語(yǔ)句的例子如下:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();
第 14 章 HQL: Hibernate查詢(xún)語(yǔ)言
Hibernate配備了一種非常強大的查詢(xún)語(yǔ)言,這種語(yǔ)言看上去很像SQL。但是不要被語(yǔ)法結構 上的相似所迷惑,HQL是非常有意識的被設計為完全面向對象的查詢(xún),它可以理解如繼承、多態(tài) 和關(guān)聯(lián)之類(lèi)的概念。
14.1. 大小寫(xiě)敏感性問(wèn)題
除了Java類(lèi)與屬性的名稱(chēng)外,查詢(xún)語(yǔ)句對大小寫(xiě)并不敏感。 所以 SeLeCT 與 sELEct 以及 SELECT 是相同的,但是 org.hibernate.eg.FOO 并不等價(jià)于 org.hibernate.eg.Foo 并且 foo.barSet 也不等價(jià)于 foo.BARSET。
本手冊中的HQL關(guān)鍵字將使用小寫(xiě)字母. 很多用戶(hù)發(fā)現使用完全大寫(xiě)的關(guān)鍵字會(huì )使查詢(xún)語(yǔ)句 的可讀性更強, 但我們發(fā)現,當把查詢(xún)語(yǔ)句嵌入到Java語(yǔ)句中的時(shí)候使用大寫(xiě)關(guān)鍵字比較難看。
14.2. from子句
Hibernate中最簡(jiǎn)單的查詢(xún)語(yǔ)句的形式如下:
from eg.Cat
該子句簡(jiǎn)單的返回eg.Cat類(lèi)的所有實(shí)例。 通常我們不需要使用類(lèi)的全限定名, 因為 auto-import(自動(dòng)引入) 是缺省的情況。 所以我們幾乎只使用如下的簡(jiǎn)單寫(xiě)法:
from Cat
大多數情況下, 你需要指定一個(gè)別名, 原因是你可能需要 在查詢(xún)語(yǔ)句的其它部分引用到Cat
from Cat as cat
這個(gè)語(yǔ)句把別名cat指定給類(lèi)Cat 的實(shí)例, 這樣我們就可以在隨后的查詢(xún)中使用此別名了。 關(guān)鍵字as 是可選的,我們也可以這樣寫(xiě):
from Cat cat
子句中可以同時(shí)出現多個(gè)類(lèi), 其查詢(xún)結果是產(chǎn)生一個(gè)笛卡兒積或產(chǎn)生跨表的連接。
from Formula, Parameter
from Formula as form, Parameter as param
查詢(xún)語(yǔ)句中別名的開(kāi)頭部分小寫(xiě)被認為是實(shí)踐中的好習慣, 這樣做與Java變量的命名標準保持了一致 (比如,domesticCat)。
14.3. 關(guān)聯(lián)(Association)與連接(Join)
我們也可以為相關(guān)聯(lián)的實(shí)體甚至是對一個(gè)集合中的全部元素指定一個(gè)別名, 這時(shí)要使用關(guān)鍵字join。
from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten
from Cat as cat left join cat.mate.kittens as kittens
from Formula form full join form.parameter param
受支持的連接類(lèi)型是從ANSI SQL中借鑒來(lái)的。
? inner join(內連接)
? left outer join(左外連接)
? right outer join(右外連接)
? full join (全連接,并不常用)
語(yǔ)句inner join, left outer join 以及 right outer join 可以簡(jiǎn)寫(xiě)。
from Cat as cat
join cat.mate as mate
left join cat.kittens as kitten
通過(guò)HQL的with關(guān)鍵字,你可以提供額外的join條件。
from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0
還有,一個(gè)"fetch"連接允許僅僅使用一個(gè)選擇語(yǔ)句就將相關(guān)聯(lián)的對象或一組值的集合隨著(zhù)他們的父對象的初始化而被初始化,這種方法在使用到集合的情況下尤其有用,對于關(guān)聯(lián)和集合來(lái)說(shuō),它有效的代替了映射文件中的外聯(lián)接 與延遲聲明(lazy declarations). 查看 第 19.1 節 ? 抓取策略(Fetching strategies) ? 以獲得等多的信息。
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
一個(gè)fetch連接通常不需要被指定別名, 因為相關(guān)聯(lián)的對象不應當被用在 where 子句 (或其它任何子句)中。同時(shí),相關(guān)聯(lián)的對象 并不在查詢(xún)的結果中直接返回,但可以通過(guò)他們的父對象來(lái)訪(fǎng)問(wèn)到他們。
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
假若使用iterate()來(lái)調用查詢(xún),請注意fetch構造是不能使用的(scroll() 可以使用)。fetch也不應該與setMaxResults() 或setFirstResult()共用,這是因為這些操作是基于結果集的,而在預先抓取集合類(lèi)時(shí)可能包含重復的數據,也就是說(shuō)無(wú)法預先知道精確的行數。fetch還不能與獨立的 with條件一起使用。通過(guò)在一次查詢(xún)中fetch多個(gè)集合,可以制造出笛卡爾積,因此請多加注意。對bag映射來(lái)說(shuō),同時(shí)join fetch多個(gè)集合角色可能在某些情況下給出并非預期的結果,也請小心。最后注意,使用full join fetch 與 right join fetch是沒(méi)有意義的。
如果你使用屬性級別的延遲獲?。╨azy fetching)(這是通過(guò)重新編寫(xiě)字節碼實(shí)現的),可以使用 fetch all properties 來(lái)強制Hibernate立即取得那些原本需要延遲加載的屬性(在第一個(gè)查詢(xún)中)。
from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like ‘%cats%‘
14.4. join 語(yǔ)法的形式
HQL支持兩種關(guān)聯(lián)join的形式:implicit(隱式) 與explicit(顯式)。
上一節中給出的查詢(xún)都是使用explicit(顯式)形式的,其中form子句中明確給出了join關(guān)鍵字。這是建議使用的方式。
implicit(隱式)形式不使用join關(guān)鍵字。關(guān)聯(lián)使用"點(diǎn)號"來(lái)進(jìn)行?引用?。implicit join可以在任何HQL子句中出現.implicit join在最終的SQL語(yǔ)句中以inner join的方式出現。
from Cat as cat where cat.mate.name like ‘%s%‘
14.5. select子句
select 子句選擇將哪些對象與屬性返 回到查詢(xún)結果集中. 考慮如下情況:
select mate
from Cat as cat
inner join cat.mate as mate
該語(yǔ)句將選擇mates of other Cats。(其他貓的配偶) 實(shí)際上, 你可以更簡(jiǎn)潔的用以下的查詢(xún)語(yǔ)句表達相同的含義:
select cat.mate from Cat cat
查詢(xún)語(yǔ)句可以返回值為任何類(lèi)型的屬性,包括返回類(lèi)型為某種組件(Component)的屬性:
select cat.name from DomesticCat cat
where cat.name like ‘fri%‘
select cust.name.firstName from Customer as cust
查詢(xún)語(yǔ)句可以返回多個(gè)對象和(或)屬性,存放在 Object[]隊列中,
select mother, offspr, mate.name
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr
或存放在一個(gè)List對象中,
select new list(mother, offspr, mate.name)
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr
也可能直接返回一個(gè)實(shí)際的類(lèi)型安全的Java對象,
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
假設類(lèi)Family有一個(gè)合適的構造函數.
你可以使用關(guān)鍵字as給?被選擇了的表達式?指派別名:
select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n
from Cat cat
這種做法在與子句select new map一起使用時(shí)最有用:
select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
from Cat cat
該查詢(xún)返回了一個(gè)Map的對象,內容是別名與被選擇的值組成的名-值映射。
14.6. 聚集函數
HQL查詢(xún)甚至可以返回作用于屬性之上的聚集函數的計算結果:
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from Cat cat
受支持的聚集函數如下:
? avg(...), sum(...), min(...), max(...)
? count(*)
? count(...), count(distinct ...), count(all...)
你可以在選擇子句中使用數學(xué)操作符、連接以及經(jīng)過(guò)驗證的SQL函數:
select cat.weight + sum(kitten.weight)
from Cat cat
join cat.kittens kitten
group by cat.id, cat.weight
select firstName||‘ ‘||initial||‘ ‘||upper(lastName) from Person
關(guān)鍵字distinct與all 也可以使用,它們具有與SQL相同的語(yǔ)義.
select distinct cat.name from Cat cat
14.7. 多態(tài)查詢(xún)
一個(gè)如下的查詢(xún)語(yǔ)句:
from Cat as cat
不僅返回Cat類(lèi)的實(shí)例, 也同時(shí)返回子類(lèi) DomesticCat的實(shí)例. Hibernate 可以在from子句中指定任何 Java 類(lèi)或接口. 查詢(xún)會(huì )返回繼承了該類(lèi)的所有持久化子類(lèi) 的實(shí)例或返回聲明了該接口的所有持久化類(lèi)的實(shí)例。下面的查詢(xún)語(yǔ)句返回所有的被持久化的對象:
from java.lang.Object o
接口Named 可能被各種各樣的持久化類(lèi)聲明:
from Named n, Named m where n.name = m.name
注意,最后的兩個(gè)查詢(xún)將需要超過(guò)一個(gè)的SQL SELECT.這表明order by子句 沒(méi)有對整個(gè)結果集進(jìn)行正確的排序. (這也說(shuō)明你不能對這樣的查詢(xún)使用Query.scroll()方法.)
14.8. where子句
where子句允許你將返回的實(shí)例列表的范圍縮小. 如果沒(méi)有指定別名,你可以使用屬性名來(lái)直接引用屬性:
from Cat where name=‘Fritz‘
如果指派了別名,需要使用完整的屬性名:
from Cat as cat where cat.name=‘Fritz‘
返回名為(屬性name等于)‘Fritz‘的Cat類(lèi)的實(shí)例。
select foo
from Foo foo, Bar bar
where foo.startDate = bar.date
將返回所有滿(mǎn)足下面條件的Foo類(lèi)的實(shí)例: 存在如下的bar的一個(gè)實(shí)例,其date屬性等于 Foo的startDate屬性。 復合路徑表達式使得where子句非常的強大,考慮如下情況:
from Cat cat where cat.mate.name is not null
該查詢(xún)將被翻譯成為一個(gè)含有表連接(內連接)的SQL查詢(xún)。如果你打算寫(xiě)像這樣的查詢(xún)語(yǔ)句
from Foo foo
where foo.bar.baz.customer.address.city is not null
在SQL中,你為達此目的將需要進(jìn)行一個(gè)四表連接的查詢(xún)。
=運算符不僅可以被用來(lái)比較屬性的值,也可以用來(lái)比較實(shí)例:
from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate
from Cat cat, Cat mate
where cat.mate = mate
特殊屬性(小寫(xiě))id可以用來(lái)表示一個(gè)對象的唯一的標識符。(你也可以使用該對象的屬性名。)
from Cat as cat where cat.id = 123
第二個(gè)查詢(xún)是有效的。此時(shí)不需要進(jìn)行表連接!
同樣也可以使用復合標識符。比如Person類(lèi)有一個(gè)復合標識符,它由country屬性 與medicareNumber屬性組成。
from bank.Person person
where person.id.country = ‘AU‘
and person.id.medicareNumber = 123456
from bank.Account account
where account.owner.id.country = ‘AU‘
and account.owner.id.medicareNumber = 123456
第二個(gè)查詢(xún)也不需要進(jìn)行表連接。
同樣的,特殊屬性class在進(jìn)行多態(tài)持久化的情況下被用來(lái)存取一個(gè)實(shí)例的鑒別值(discriminator value)。 一個(gè)嵌入到where子句中的Java類(lèi)的名字將被轉換為該類(lèi)的鑒別值。
from Cat cat where cat.class = DomesticCat
你也可以聲明一個(gè)屬性的類(lèi)型是組件或者復合用戶(hù)類(lèi)型(以及由組件構成的組件等等)。永遠不要嘗試使用以組件類(lèi)型來(lái)結尾的路徑表達式(path-expression) (與此相反,你應當使用組件的一個(gè)屬性來(lái)結尾)。 舉例來(lái)說(shuō),如果store.owner含有一個(gè)包含了組件的實(shí)體address
store.owner.address.city // 正確
store.owner.address // 錯誤!
一個(gè)?任意?類(lèi)型有兩個(gè)特殊的屬性id和class, 來(lái)允許我們按照下面的方式表達一個(gè)連接(AuditLog.item 是一個(gè)屬性,該屬性被映射為<any>)。
from AuditLog log, Payment payment
where log.item.class = ‘Payment‘ and log.item.id = payment.id
注意,在上面的查詢(xún)與句中,log.item.class 和 payment.class 將涉及到完全不同的數據庫中的列。
14.9. 表達式
在where子句中允許使用的表達式包括 大多數你可以在SQL使用的表達式種類(lèi):
? 數學(xué)運算符+, -, *, /
? 二進(jìn)制比較運算符=, >=, <=, <>, !=, like
? 邏輯運算符and, or, not
? in, not in, between, is null, is not null, is empty, is not empty, member of and not member of
? "簡(jiǎn)單的" case, case ... when ... then ... else ... end,和 "搜索" case, case when ... then ... else ... end
? 字符串連接符...||... or concat(...,...)
? current_date(), current_time(), current_timestamp()
? second(...), minute(...), hour(...), day(...), month(...), year(...),
? EJB-QL 3.0定義的任何函數或操作:substring(), trim(), lower(), upper(), length(), locate(), abs(), sqrt(), bit_length(), mod()
? coalesce() 和 nullif()
? str() 把數字或者時(shí)間值轉換為可讀的字符串
? cast(... as ...), 其第二個(gè)參數是某Hibernate類(lèi)型的名字,以及extract(... from ...),只要ANSI cast() 和 extract() 被底層數據庫支持
? HQL index() 函數,作用于join的有序集合的別名。
? HQL函數,把集合作為參數:size(), minelement(), maxelement(), minindex(), maxindex(),還有特別的elements() 和indices函數,可以與數量詞加以限定:some, all, exists, any, in。
? 任何數據庫支持的SQL標量函數,比如sign(), trunc(), rtrim(), sin()
? JDBC風(fēng)格的參數傳入 ?
? 命名參數:name, :start_date, :x1
? SQL 直接常量 ‘foo‘, 69, 6.66E+2, ‘1970-01-01 10:00:01.0‘
? Java public static final 類(lèi)型的常量 eg.Color.TABBY
關(guān)鍵字in與between可按如下方法使用:
from DomesticCat cat where cat.name between ‘A‘ and ‘B‘
from DomesticCat cat where cat.name in ( ‘Foo‘, ‘Bar‘, ‘Baz‘ )
而且否定的格式也可以如下書(shū)寫(xiě):
from DomesticCat cat where cat.name not between ‘A‘ and ‘B‘
from DomesticCat cat where cat.name not in ( ‘Foo‘, ‘Bar‘, ‘Baz‘ )
同樣, 子句is null與is not null可以被用來(lái)測試空值(null).
在Hibernate配置文件中聲明HQL?查詢(xún)替代(query substitutions)?之后, 布爾表達式(Booleans)可以在其他表達式中輕松的使用:
<property name="hibernate.query.substitutions">true 1, false 0</property>
系統將該HQL轉換為SQL語(yǔ)句時(shí),該設置表明將用字符 1 和 0 來(lái) 取代關(guān)鍵字true 和 false:
from Cat cat where cat.alive = true
你可以用特殊屬性size, 或是特殊函數size()測試一個(gè)集合的大小。
from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
對于索引了(有序)的集合,你可以使用minindex 與 maxindex函數來(lái)引用到最小與最大的索引序數。 同理,你可以使用minelement 與 maxelement函數來(lái) 引用到一個(gè)基本數據類(lèi)型的集合中最小與最大的元素。
from Calendar cal where maxelement(cal.holidays) > current_date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
在傳遞一個(gè)集合的索引集或者是元素集(elements與indices 函數) 或者傳遞一個(gè)子查詢(xún)的結果的時(shí)候,可以使用SQL函數any, some, all, exists, in
select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where ‘fizard‘ in indices(show.acts)
注意,在Hibernate3種,這些結構變量- size, elements, indices, minindex, maxindex, minelement, maxelement - 只能在where子句中使用。
一個(gè)被索引過(guò)的(有序的)集合的元素(arrays, lists, maps)可以在其他索引中被引用(只能在where子句中):
from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays[‘national day‘] = person.birthDay
and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
在[]中的表達式甚至可以是一個(gè)算數表達式。
select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
對于一個(gè)一對多的關(guān)聯(lián)(one-to-many association)或是值的集合中的元素, HQL也提供內建的index()函數,
select item, index(item) from Order order
join order.items item
where index(item) < 5
如果底層數據庫支持標量的SQL函數,它們也可以被使用
from DomesticCat cat where upper(cat.name) like ‘FRI%‘
如果你還不能對所有的這些深信不疑,想想下面的查詢(xún)。如果使用SQL,語(yǔ)句長(cháng)度會(huì )增長(cháng)多少,可讀性會(huì )下降多少:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = ‘widget‘
and store.location.name in ( ‘Melbourne‘, ‘Sydney‘ )
and prod = all elements(cust.currentOrder.lineItems)
提示: 會(huì )像如下的語(yǔ)句
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = ‘widget‘
AND store.loc_id = loc.id
AND loc.name IN ( ‘Melbourne‘, ‘Sydney‘ )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)
14.10. order by子句
查詢(xún)返回的列表(list)可以按照一個(gè)返回的類(lèi)或組件(components)中的任何屬性(property)進(jìn)行排序:
from DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate
可選的asc或desc關(guān)鍵字指明了按照升序或降序進(jìn)行排序.
14.11. group by子句
一個(gè)返回聚集值(aggregate values)的查詢(xún)可以按照一個(gè)返回的類(lèi)或組件(components)中的任何屬性(property)進(jìn)行分組:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id
having子句在這里也允許使用.
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
如果底層的數據庫支持的話(huà)(例如不能在MySQL中使用),SQL的一般函數與聚集函數也可以出現 在having與order by 子句中。
select cat
from Cat cat
join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
注意group by子句與 order by子句中都不能包含算術(shù)表達式(arithmetic expressions).
14.12. 子查詢(xún)
對于支持子查詢(xún)的數據庫,Hibernate支持在查詢(xún)中使用子查詢(xún)。一個(gè)子查詢(xún)必須被圓括號包圍起來(lái)(經(jīng)常是SQL聚集函數的圓括號)。 甚至相互關(guān)聯(lián)的子查詢(xún)(引用到外部查詢(xún)中的別名的子查詢(xún))也是允許的。
from Cat as fatcat
where fatcat.weight > (
select avg(cat.weight) from DomesticCat cat
)
from DomesticCat as cat
where cat.name = some (
select name.nickName from Name as name
)
from Cat as cat
where not exists (
from Cat as mate where mate.mate = cat
)
from DomesticCat as cat
where cat.name not in (
select name.nickName from Name as name
)
select cat.id, (select max(kit.weight) from cat.kitten kit)
from Cat as cat
注意,HQL自查詢(xún)只可以在select或者where子句中出現。
在select列表中包含一個(gè)表達式以上的子查詢(xún),你可以使用一個(gè)元組構造符(tuple constructors):
from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)
注意在某些數據庫中(不包括Oracle與HSQL),你也可以在其他語(yǔ)境中使用元組構造符, 比如查詢(xún)用戶(hù)類(lèi)型的組件與組合:
from Person where name = (‘Gavin‘, ‘A‘, ‘King‘)
該查詢(xún)等價(jià)于更復雜的:
from Person where name.first = ‘Gavin‘ and name.initial = ‘A‘ and name.last = ‘King‘)
有兩個(gè)很好的理由使你不應當作這樣的事情:首先,它不完全適用于各個(gè)數據庫平臺;其次,查詢(xún)現在依賴(lài)于映射文件中屬性的順序。
14.13. HQL示例
Hibernate查詢(xún)可以非常的強大與復雜。實(shí)際上,Hibernate的一個(gè)主要賣(mài)點(diǎn)就是查詢(xún)語(yǔ)句的威力。這里有一些例子,它們與我在最近的 一個(gè)項目中使用的查詢(xún)非常相似。注意你能用到的大多數查詢(xún)比這些要簡(jiǎn)單的多!
下面的查詢(xún)對于某個(gè)特定的客戶(hù)的所有未支付的賬單,在給定給最小總價(jià)值的情況下,返回訂單的id,條目的數量和總價(jià)值, 返回值按照總價(jià)值的結果進(jìn)行排序。為了決定價(jià)格,查詢(xún)使用了當前目錄。作為轉換結果的SQL查詢(xún),使用了ORDER, ORDER_LINE, PRODUCT, CATALOG 和PRICE 庫表。
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog.effectiveDate < sysdate
and catalog.effectiveDate >= all (
select cat.effectiveDate
from Catalog as cat
where cat.effectiveDate < sysdate
)
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc
這簡(jiǎn)直是一個(gè)怪物!實(shí)際上,在現實(shí)生活中,我并不熱衷于子查詢(xún),所以我的查詢(xún)語(yǔ)句看起來(lái)更像這個(gè):
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog = :currentCatalog
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc
下面一個(gè)查詢(xún)計算每一種狀態(tài)下的支付的數目,除去所有處于A(yíng)WAITING_APPROVAL狀態(tài)的支付,因為在該狀態(tài)下 當前的用戶(hù)作出了狀態(tài)的最新改變。該查詢(xún)被轉換成含有兩個(gè)內連接以及一個(gè)相關(guān)聯(lián)的子選擇的SQL查詢(xún),該查詢(xún)使用了表 PAYMENT, PAYMENT_STATUS 以及 PAYMENT_STATUS_CHANGE。
select count(payment), status.name
from Payment as payment
join payment.currentStatus as status
join payment.statusChanges as statusChange
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
or (
statusChange.timeStamp = (
select max(change.timeStamp)
from PaymentStatusChange change
where change.payment = payment
)
and statusChange.user <> :currentUser
)
group by status.name, status.sortOrder
order by status.sortOrder
如果我把statusChanges實(shí)例集映射為一個(gè)列表(list)而不是一個(gè)集合(set), 書(shū)寫(xiě)查詢(xún)語(yǔ)句將更加簡(jiǎn)單.
select count(payment), status.name
from Payment as payment
join payment.currentStatus as status
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser
group by status.name, status.sortOrder
order by status.sortOrder
下面一個(gè)查詢(xún)使用了MS SQL Server的 isNull()函數用以返回當前用戶(hù)所屬組織的組織賬號及組織未支付的賬。 它被轉換成一個(gè)對表ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION 以及 ORG_USER進(jìn)行的三個(gè)內連接, 一個(gè)外連接和一個(gè)子選擇的SQL查詢(xún)。
select account, payment
from Account as account
left outer join account.payments as payment
where :currentUser in elements(account.holder.users)
and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate
對于一些數據庫,我們需要棄用(相關(guān)的)子選擇。
select account, payment
from Account as account
join account.holder.users as user
left outer join account.payments as payment
where :currentUser = user
and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate
14.14. 批量的UPDATE和DELETE
HQL現在支持 update, delete 和 insert ... select ...語(yǔ)句. 查閱 第 13.4 節 ?DML(數據操作語(yǔ)言)風(fēng)格的操作(DML-style operations)? 以獲得更多信息。
14.15. 小技巧 & 小竅門(mén)
你可以統計查詢(xún)結果的數目而不必實(shí)際的返回他們:
( (Integer) session.iterate("select count(*) from ....").next() ).intValue()
若想根據一個(gè)集合的大小來(lái)進(jìn)行排序,可以使用如下的語(yǔ)句:
select usr.id, usr.name
from User as usr
left join usr.messages as msg
group by usr.id, usr.name
order by count(msg)
如果你的數據庫支持子選擇,你可以在你的查詢(xún)的where子句中為選擇的大?。╯election size)指定一個(gè)條件:
from User usr where size(usr.messages) >= 1
如果你的數據庫不支持子選擇語(yǔ)句,使用下面的查詢(xún):
select usr.id, usr.name
from User usr.name
join usr.messages msg
group by usr.id, usr.name
having count(msg) >= 1
因為內連接(inner join)的原因,這個(gè)解決方案不能返回含有零個(gè)信息的User 類(lèi)的實(shí)例, 所以這種情況下使用下面的格式將是有幫助的:
select usr.id, usr.name
from User as usr
left join usr.messages as msg
group by usr.id, usr.name
having count(msg) = 0
JavaBean的屬性可以被綁定到一個(gè)命名查詢(xún)(named query)的參數上:
Query q = s.createQuery("from foo Foo as foo where foo.name=:name and foo.size=:size");
q.setProperties(fooBean); // fooBean包含方法getName()與getSize()
List foos = q.list();
通過(guò)將接口Query與一個(gè)過(guò)濾器(filter)一起使用,集合(Collections)是可以分頁(yè)的:
Query q = s.createFilter( collection, "" ); // 一個(gè)簡(jiǎn)單的過(guò)濾器
q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE * pageNumber);
List page = q.list();
通過(guò)使用查詢(xún)過(guò)濾器(query filter)可以將集合(Collection)的原素分組或排序:
Collection orderedCollection = s.filter( collection, "order by this.amount" );
Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );
不用通過(guò)初始化,你就可以知道一個(gè)集合(Collection)的大?。?
( (Integer) session.iterate("select count(*) from ....").next() ).intValue();
第 15 章 條件查詢(xún)(Criteria Queries)
具有一個(gè)直觀(guān)的、可擴展的條件查詢(xún)API是Hibernate的特色。
15.1. 創(chuàng )建一個(gè)Criteria 實(shí)例
org.hibernate.Criteria接口表示特定持久類(lèi)的一個(gè)查詢(xún)。Session是 Criteria實(shí)例的工廠(chǎng)。
Criteria crit = sess.createCriteria(Cat.class);
crit.setMaxResults(50);
List cats = crit.list();
15.2. 限制結果集內容
一個(gè)單獨的查詢(xún)條件是org.hibernate.criterion.Criterion 接口的一個(gè)實(shí)例。org.hibernate.criterion.Restrictions類(lèi) 定義了獲得某些內置Criterion類(lèi)型的工廠(chǎng)方法。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.between("weight", minWeight, maxWeight) )
.list();
約束可以按邏輯分組。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.or(
Restrictions.eq( "age", new Integer(0) ),
Restrictions.isNull("age")
) )
.list();
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
.add( Restrictions.disjunction()
.add( Restrictions.isNull("age") )
.add( Restrictions.eq("age", new Integer(0) ) )
.add( Restrictions.eq("age", new Integer(1) ) )
.add( Restrictions.eq("age", new Integer(2) ) )
) )
.list();
Hibernate提供了相當多的內置criterion類(lèi)型(Restrictions 子類(lèi)), 但是尤其有用的是可以允許你直接使用SQL。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
.list();
{alias}占位符應當被替換為被查詢(xún)實(shí)體的列別名。
Property實(shí)例是獲得一個(gè)條件的另外一種途徑。你可以通過(guò)調用Property.forName() 創(chuàng )建一個(gè)Property。
Property age = Property.forName("age");
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.disjunction()
.add( age.isNull() )
.add( age.eq( new Integer(0) ) )
.add( age.eq( new Integer(1) ) )
.add( age.eq( new Integer(2) ) )
) )
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
.list();
15.3. 結果集排序
你可以使用org.hibernate.criterion.Order來(lái)為查詢(xún)結果排序。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%")
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50)
.list();
List cats = sess.createCriteria(Cat.class)
.add( Property.forName("name").like("F%") )
.addOrder( Property.forName("name").asc() )
.addOrder( Property.forName("age").desc() )
.setMaxResults(50)
.list();
15.4. 關(guān)聯(lián)
你可以使用createCriteria()非常容易的在互相關(guān)聯(lián)的實(shí)體間建立 約束。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%") )
.createCriteria("kittens")
.add( Restrictions.like("name", "F%") )
.list();
注意第二個(gè) createCriteria()返回一個(gè)新的 Criteria實(shí)例,該實(shí)例引用kittens 集合中的元素。
接下來(lái),替換形態(tài)在某些情況下也是很有用的。
List cats = sess.createCriteria(Cat.class)
.createAlias("kittens", "kt")
.createAlias("mate", "mt")
.add( Restrictions.eqProperty("kt.name", "mt.name") )
.list();
(createAlias()并不創(chuàng )建一個(gè)新的 Criteria實(shí)例。)
Cat實(shí)例所保存的之前兩次查詢(xún)所返回的kittens集合是 沒(méi)有被條件預過(guò)濾的。如果你希望只獲得符合條件的kittens, 你必須使用ResultTransformer。
List cats = sess.createCriteria(Cat.class)
.createCriteria("kittens", "kt")
.add( Restrictions.eq("name", "F%") )
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
.list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
Cat kitten = (Cat) map.get("kt");
}
15.5. 動(dòng)態(tài)關(guān)聯(lián)抓取
你可以使用setFetchMode()在運行時(shí)定義動(dòng)態(tài)關(guān)聯(lián)抓取的語(yǔ)義。
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.setFetchMode("mate", FetchMode.EAGER)
.setFetchMode("kittens", FetchMode.EAGER)
.list();
這個(gè)查詢(xún)可以通過(guò)外連接抓取mate和kittens。 查看第 19.1 節 ? 抓取策略(Fetching strategies) ?可以獲得更多信息。
15.6. 查詢(xún)示例
org.hibernate.criterion.Example類(lèi)允許你通過(guò)一個(gè)給定實(shí)例 構建一個(gè)條件查詢(xún)。
Cat cat = new Cat();
cat.setSex(‘F‘);
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.list();
版本屬性、標識符和關(guān)聯(lián)被忽略。默認情況下值為null的屬性將被排除。
你可以自行調整Example使之更實(shí)用。
Example example = Example.create(cat)
.excludeZeroes() //exclude zero valued properties
.excludeProperty("color") //exclude the property named "color"
.ignoreCase() //perform case insensitive string comparisons
.enableLike(); //use like for string comparisons
List results = session.createCriteria(Cat.class)
.add(example)
.list();
你甚至可以使用examples在關(guān)聯(lián)對象上放置條件。
List results = session.createCriteria(Cat.class)
.add( Example.create(cat) )
.createCriteria("mate")
.add( Example.create( cat.getMate() ) )
.list();
15.7. 投影(Projections)、聚合(aggregation)和分組(grouping)
org.hibernate.criterion.Projections是 Projection 的實(shí)例工廠(chǎng)。我們通過(guò)調用 setProjection()應用投影到一個(gè)查詢(xún)。
List results = session.createCriteria(Cat.class)
.setProjection( Projections.rowCount() )
.add( Restrictions.eq("color", Color.BLACK) )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount() )
.add( Projections.avg("weight") )
.add( Projections.max("weight") )
.add( Projections.groupProperty("color") )
)
.list();
在一個(gè)條件查詢(xún)中沒(méi)有必要顯式的使用 "group by" 。某些投影類(lèi)型就是被定義為 分組投影,他們也出現在SQL的group by子句中。
你可以選擇把一個(gè)別名指派給一個(gè)投影,這樣可以使投影值被約束或排序所引用。下面是兩種不同的實(shí)現方式:
List results = session.createCriteria(Cat.class)
.setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
.addOrder( Order.asc("colr") )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.groupProperty("color").as("colr") )
.addOrder( Order.asc("colr") )
.list();
alias()和as()方法簡(jiǎn)便的將一個(gè)投影實(shí)例包裝到另外一個(gè) 別名的Projection實(shí)例中。簡(jiǎn)而言之,當你添加一個(gè)投影到一個(gè)投影列表中時(shí) 你可以為它指定一個(gè)別名:
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount(), "catCountByColor" )
.add( Projections.avg("weight"), "avgWeight" )
.add( Projections.max("weight"), "maxWeight" )
.add( Projections.groupProperty("color"), "color" )
)
.addOrder( Order.desc("catCountByColor") )
.addOrder( Order.desc("avgWeight") )
.list();
List results = session.createCriteria(Domestic.class, "cat")
.createAlias("kittens", "kit")
.setProjection( Projections.projectionList()
.add( Projections.property("cat.name"), "catName" )
.add( Projections.property("kit.name"), "kitName" )
)
.addOrder( Order.asc("catName") )
.addOrder( Order.asc("kitName") )
.list();
你也可以使用Property.forName()來(lái)表示投影:
List results = session.createCriteria(Cat.class)
.setProjection( Property.forName("name") )
.add( Property.forName("color").eq(Color.BLACK) )
.list();
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount().as("catCountByColor") )
.add( Property.forName("weight").avg().as("avgWeight") )
.add( Property.forName("weight").max().as("maxWeight") )
.add( Property.forName("color").group().as("color" )
)
.addOrder( Order.desc("catCountByColor") )
.addOrder( Order.desc("avgWeight") )
.list();
15.8. 離線(xiàn)(detached)查詢(xún)和子查詢(xún)
DetachedCriteria類(lèi)使你在一個(gè)session范圍之外創(chuàng )建一個(gè)查詢(xún),并且可以使用任意的 Session來(lái)執行它。
DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
.add( Property.forName("sex").eq(‘F‘) );
Session session = ....;
Transaction txn = session.beginTransaction();
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
txn.commit();
session.close();
DetachedCriteria也可以用以表示子查詢(xún)。條件實(shí)例包含子查詢(xún)可以通過(guò) Subqueries或者Property獲得。
DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
.setProjection( Property.forName("weight").avg() );
session.createCriteria(Cat.class)
.add( Property.forName("weight).gt(avgWeight) )
.list();
DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
.setProjection( Property.forName("weight") );
session.createCriteria(Cat.class)
.add( Subqueries.geAll("weight", weights) )
.list();
甚至相互關(guān)聯(lián)的子查詢(xún)也是有可能的:
DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
.setProjection( Property.forName("weight").avg() )
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
session.createCriteria(Cat.class, "cat")
.add( Property.forName("weight).gt(avgWeightForSex) )
.list();
15.9. 根據自然標識查詢(xún)(Queries by natural identifier)
對大多數查詢(xún),包括條件查詢(xún)而言,因為查詢(xún)緩存的失效(invalidation)發(fā)生得太頻繁,查詢(xún)緩存不是非常高效。然而,有一種特別的查詢(xún),可以通過(guò)不變的自然鍵優(yōu)化緩存的失效算法。在某些應用中,這種類(lèi)型的查詢(xún)比較常見(jiàn)。條件查詢(xún)API對這種用例提供了特別規約。
首先,你應該對你的entity使用<natural-id>來(lái)映射自然鍵,然后打開(kāi)第二級緩存。
<class name="User">
<cache usage="read-write"/>
<id name="id">
<generator class="increment"/>
</id>
<natural-id>
<property name="name"/>
<property name="org"/>
</natural-id>
<property name="password"/>
</class>
注意,此功能對具有mutable自然鍵的entity并不適用。
然后,打開(kāi)Hibernate 查詢(xún)緩存。
現在,我們可以用Restrictions.naturalId()來(lái)使用更加高效的緩存算法。
session.createCriteria(User.class)
.add( Restrictions.naturalId()
.set("name", "gavin")
.set("org", "hb")
).setCacheable(true)
.uniqueResult();
第 16 章 Native SQL查詢(xún)
你也可以使用你的數據庫的Native SQL語(yǔ)言來(lái)查詢(xún)數據。這對你在要使用數據庫的某些特性的時(shí)候(比如說(shuō)在查詢(xún)提示或者Oracle中的 CONNECT關(guān)鍵字),這是非常有用的。這就能夠掃清你把原來(lái)直接使用SQL/JDBC 的程序遷移到基于 Hibernate應用的道路上的障礙。
Hibernate3允許你使用手寫(xiě)的sql來(lái)完成所有的create,update,delete,和load操作(包括存儲過(guò)程)
16.1. 使用SQLQuery
對原生SQL查詢(xún)執行的控制是通過(guò)SQLQuery接口進(jìn)行的,通過(guò)執行Session.createSQLQuery()獲取這個(gè)接口。最簡(jiǎn)單的情況下,我們可以采用以下形式:
List cats = sess.createSQLQuery("select * from cats")
.addEntity(Cat.class)
.list();
這個(gè)查詢(xún)指定了:
? SQL查詢(xún)字符串
? 查詢(xún)返回的實(shí)體
這里,結果集字段名被假設為與映射文件中指明的字段名相同。對于連接了多個(gè)表的查詢(xún),這就可能造成問(wèn)題,因為可能在多個(gè)表中出現同樣名字的字段。下面的方法就可以避免字段名重復的問(wèn)題:
List cats = sess.createSQLQuery("select {cat.*} from cats cat")
.addEntity("cat", Cat.class)
.list();
這個(gè)查詢(xún)指定了:
? SQL查詢(xún)語(yǔ)句,它帶一個(gè)占位符,可以讓Hibernate使用字段的別名.
? 查詢(xún)返回的實(shí)體,和它的SQL表的別名.
addEntity()方法將SQL表的別名和實(shí)體類(lèi)聯(lián)系起來(lái),并且確定查詢(xún)結果集的形態(tài)。
addJoin()方法可以被用于載入其他的實(shí)體和集合的關(guān)聯(lián).
List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.addEntity("cat", Cat.class)
.addJoin("kitten", "cat.kittens")
.list();
原生的SQL查詢(xún)可能返回一個(gè)簡(jiǎn)單的標量值或者一個(gè)標量和實(shí)體的結合體。
Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();
除此之外,你還可以在你的hbm文件中描述結果集映射信息,在查詢(xún)中使用。
List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();
16.2. 別名和屬性引用
上面使用的{cat.*}標記是 "所有屬性" 的簡(jiǎn)寫(xiě).你可以顯式地列出需要的字段,但是你必須讓Hibernate 為每一個(gè)屬性注入字段的別名.這些字段的站位符是以字段別名為前導,再加上屬性名.在下面的例子里,我們從一個(gè)其他的表(cat_log) 中獲取Cat對象,而非Cat對象原本在映射元數據中聲明的表.注意我們甚至在where子句中也可以使用屬性別名. 對于命名查詢(xún),{}語(yǔ)法并不是必需的.你可以在第 16.3 節 ?命名SQL查詢(xún)?得到更多的細節.
String sql = "select cat.originalId as {cat.id}, " +
"cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +
"cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +
"from cat_log cat where {cat.mate} = :catId"
List loggedCats = sess.createSQLQuery(sql)
.addEntity("cat", Cat.class)
.setLong("catId", catId)
.list();
注意:如果你明確地列出了每個(gè)屬性,你必須包含這個(gè)類(lèi)和它的子類(lèi)的屬性!
下表列出了使用別名注射參數的不同可能性。注意:下面結果中的別名只是示例,實(shí)用時(shí)每個(gè)別名需要唯一并且不同的名字。
表 16.1. 別名注射(alias injection names)
描述 語(yǔ)法 示例
簡(jiǎn)單屬性 {[aliasname].[propertyname] A_NAME as {item.name}
復合屬性 {[aliasname].[componentname].[propertyname]} CURRENCY as {item.amount.currency}, VALUE as {item.amount.value}
實(shí)體辨別器(Discriminator of an entity) {[aliasname].class} DISC as {item.class}
實(shí)體的所有屬性 {[aliasname].*} {item.*}
集合鍵(collection key) {[aliasname].key} ORGID as {coll.key}
集合id {[aliasname].id} EMPID as {coll.id}
集合元素 {[aliasname].element} XID as {coll.element}
集合元素的屬性 {[aliasname].element.[propertyname]} NAME as {coll.element.name}
集合元素的所有屬性 {[aliasname].element.*} {coll.element.*}
集合的所有屬性 {[aliasname].*} {coll.*}
16.3. 命名SQL查詢(xún)
可以在映射文檔中定義查詢(xún)的名字,然后就可以象調用一個(gè)命名的HQL查詢(xún)一樣直接調用命名SQL查詢(xún).在這種情況下,我們不 需要調用addEntity()方法.
<sql-query name="persons">
<return alias="person" class="eg.Person"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex}
FROM PERSON person
WHERE person.NAME LIKE :namePattern
</sql-query>
List people = sess.getNamedQuery("persons")
.setString("namePattern", namePattern)
.setMaxResults(50)
.list();
<return-join>和 <load-collection> 元素是用來(lái)連接關(guān)聯(lián)以及將查詢(xún)定義為預先初始化各個(gè)集合的。
<sql-query name="personsWith">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
adddress.STREET AS {address.street},
adddress.CITY AS {address.city},
adddress.STATE AS {address.state},
adddress.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS adddress
ON person.ID = address.PERSON_ID AND address.TYPE=‘MAILING‘
WHERE person.NAME LIKE :namePattern
</sql-query>
一個(gè)命名查詢(xún)可能會(huì )返回一個(gè)標量值.你必須使用<return-scalar>元素來(lái)指定字段的別名和 Hibernate類(lèi)型
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string"/>
<return-scalar column="age" type="long"/>
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE ‘Hiber%‘
</sql-query>
你可以把結果集映射的信息放在外部的<resultset>元素中,這樣就可以在多個(gè)命名查詢(xún)間,或者通過(guò)setResultSetMapping()API來(lái)訪(fǎng)問(wèn)。(此處原文即存疑。原文為:You can externalize the resultset mapping informations in a <resultset> element to either reuse them accross several named queries or through the setResultSetMapping() API.)
<resultset name="personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex},
adddress.STREET AS {address.street},
adddress.CITY AS {address.city},
adddress.STATE AS {address.state},
adddress.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS adddress
ON person.ID = address.PERSON_ID AND address.TYPE=‘MAILING‘
WHERE person.NAME LIKE :namePattern
</sql-query>
16.3.1. 使用return-property來(lái)明確地指定字段/別名
使用<return-property>你可以明確的告訴Hibernate使用哪些字段別名,這取代了使用{}-語(yǔ)法 來(lái)讓Hibernate注入它自己的別名.
<sql-query name="mySqlQuery">
<return alias="person" class="eg.Person">
<return-property name="name" column="myName"/>
<return-property name="age" column="myAge"/>
<return-property name="sex" column="mySex"/>
</return>
SELECT person.NAME AS myName,
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>
<return-property>也可用于多個(gè)字段,它解決了使用{}-語(yǔ)法不能細粒度控制多個(gè)字段的限制
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment">
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
<return-property name="endDate" column="myEndDate"/>
</return>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
FROM EMPLOYMENT
WHERE EMPLOYER = :id AND ENDDATE IS NULL
ORDER BY STARTDATE ASC
</sql-query>
注意在這個(gè)例子中,我們使用了<return-property>結合{}的注入語(yǔ)法. 允許用戶(hù)來(lái)選擇如何引用字段以及屬性.
如果你映射一個(gè)識別器(discriminator),你必須使用<return-discriminator> 來(lái)指定識別器字段
16.3.2. 使用存儲過(guò)程來(lái)查詢(xún)
Hibernate 3引入了對存儲過(guò)程查詢(xún)(stored procedure)和函數(function)的支持.以下的說(shuō)明中,這二者一般都適用。 存儲過(guò)程/函數必須返回一個(gè)結果集,作為Hibernate能夠使用的第一個(gè)外部參數. 下面是一個(gè)Oracle9和更高版本的存儲過(guò)程例子.
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
在Hibernate里要要使用這個(gè)查詢(xún),你需要通過(guò)命名查詢(xún)來(lái)映射它.
<sql-query name="selectAllEmployees_SP" callable="true">
<return alias="emp" class="Employment">
<return-property name="employee" column="EMPLOYEE"/>
<return-property name="employer" column="EMPLOYER"/>
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EID"/>
<return-property name="salary">
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
注意存儲過(guò)程當前僅僅返回標量和實(shí)體.現在不支持<return-join>和<load-collection>
16.3.2.1. 使用存儲過(guò)程的規則和限制
為了在Hibernate中使用存儲過(guò)程,你必須遵循一些規則.不遵循這些規則的存儲過(guò)程將不可用.如果你仍然想要使用他們, 你必須通過(guò)session.connection()來(lái)執行他們.這些規則針對于不同的數據庫.因為數據庫 提供商有各種不同的存儲過(guò)程語(yǔ)法和語(yǔ)義.
對存儲過(guò)程進(jìn)行的查詢(xún)無(wú)法使用setFirstResult()/setMaxResults()進(jìn)行分頁(yè)。
建議采用的調用方式是標準SQL92: { ? = call functionName(<parameters>) } 或者 { ? = call procedureName(<parameters>}.原生調用語(yǔ)法不被支持。
對于Oracle有如下規則:
? 函數必須返回一個(gè)結果集。存儲過(guò)程的第一個(gè)參數必須是OUT,它返回一個(gè)結果集。這是通過(guò)Oracle 9或10的SYS_REFCURSOR類(lèi)型來(lái)完成的。在Oracle中你需要定義一個(gè)REF CURSOR類(lèi)型,參見(jiàn)Oracle的手冊。
對于Sybase或者M(jìn)S SQL server有如下規則:
? 存儲過(guò)程必須返回一個(gè)結果集。.注意這些servers可能返回多個(gè)結果集以及更新的數目.Hibernate將取出第一條結果集作為它的返回值, 其他將被丟棄。
? 如果你能夠在存儲過(guò)程里設定SET NOCOUNT ON,這可能會(huì )效率更高,但這不是必需的。
16.4. 定制SQL用來(lái)create,update和delete
Hibernate3能夠使用定制的SQL語(yǔ)句來(lái)執行create,update和delete操作。在Hibernate中,持久化的類(lèi)和集合已經(jīng) 包含了一套配置期產(chǎn)生的語(yǔ)句(insertsql, deletesql, updatesql等等),這些映射標記 <sql-insert>, <sql-delete>, and <sql-update>重載了 這些語(yǔ)句。
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
這些SQL直接在你的數據庫里執行,所以你可以自由的使用你喜歡的任意語(yǔ)法。但如果你使用數據庫特定的語(yǔ)法, 這當然會(huì )降低你映射的可移植性。
如果設定callable,則能夠支持存儲過(guò)程了。
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
</class>
參數的位置順序是非常重要的,他們必須和Hibernate所期待的順序相同。
你能夠通過(guò)設定日志調試級別為org.hiberante.persister.entity,來(lái)查看Hibernate所期待的順序。在這個(gè)級別下, Hibernate將會(huì )打印出create,update和delete實(shí)體的靜態(tài)SQL。(如果想看到預計的順序。記得不要將定制SQL包含在映射文件里, 因為他們會(huì )重載Hibernate生成的靜態(tài)SQL。)
在大多數情況下(最好這么做),存儲過(guò)程需要返回插入/更新/刪除的行數,因為Hibernate對語(yǔ)句的成功執行有些運行時(shí)的檢查。 Hibernate常會(huì )把進(jìn)行CUD操作的語(yǔ)句的第一個(gè)參數注冊為一個(gè)數值型輸出參數。
CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
RETURN NUMBER IS
BEGIN
set
NAME = uname,
where
ID = uid;
16.5. 定制裝載SQL
你可能需要聲明你自己的SQL(或HQL)來(lái)裝載實(shí)體
<sql-query name="person">
<return alias="pers" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>
這只是一個(gè)前面討論過(guò)的命名查詢(xún)聲明,你可以在類(lèi)映射里引用這個(gè)命名查詢(xún)。
<class name="Person">
<id name="id">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<loader query-ref="person"/>
</class>
這也可以用于存儲過(guò)程
你甚至可以定一個(gè)用于集合裝載的查詢(xún):
<set name="employments" inverse="true">
<key/>
<one-to-many class="Employment"/>
<loader query-ref="employments"/>
</set>
<sql-query name="employments">
<load-collection alias="emp" role="Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
你甚至還可以定義一個(gè)實(shí)體裝載器,它通過(guò)連接抓取裝載一個(gè)集合:
<sql-query name="person">
<return alias="pers" class="Person"/>
<return-join alias="emp" property="pers.employments"/>
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
</sql-query>
第 17 章 過(guò)濾數據
Hibernate3 提供了一種創(chuàng )新的方式來(lái)處理具有?顯性(visibility)?規則的數據,那就是使用Hibernate filter。 Hibernate filter是全局有效的、具有名字、可以帶參數的過(guò)濾器, 對于某個(gè)特定的Hibernate session您可以選擇是否啟用(或禁用)某個(gè)過(guò)濾器。
17.1. Hibernate 過(guò)濾器(filters)
Hibernate3新增了對某個(gè)類(lèi)或者集合使用預先定義的過(guò)濾器條件(filter criteria)的功能。過(guò)濾器條件相當于定義一個(gè) 非常類(lèi)似于類(lèi)和各種集合上的?where?屬性的約束子句,但是過(guò)濾器條件可以帶參數。 應用程序可以在運行時(shí)決定是否啟用給定的過(guò)濾器,以及使用什么樣的參數值。 過(guò)濾器的用法很像數據庫視圖,只不過(guò)是在應用程序中確定使用什么樣的參數的。
要使用過(guò)濾器,必須首先在相應的映射節點(diǎn)中定義。而定義一個(gè)過(guò)濾器,要用到位于<hibernate-mapping/> 節點(diǎn)之內的<filter-def/>節點(diǎn):
<filter-def name="myFilter">
定義好之后,就可以在某個(gè)類(lèi)中使用這個(gè)過(guò)濾器:
<class name="myClass" ...>
也可以在某個(gè)集合使用它:
<set ...>
可以在多個(gè)類(lèi)或集合中使用某個(gè)過(guò)濾器;某個(gè)類(lèi)或者集合中也可以使用多個(gè)過(guò)濾器。
Session對象中會(huì )用到的方法有:enableFilter(String filterName), getEnabledFilter(String filterName), 和 disableFilter(String filterName). Session中默認是不啟用過(guò)濾器的,必須通過(guò)Session.enabledFilter()方法顯式的啟用。 該方法返回被啟用的Filter的實(shí)例。以上文定義的過(guò)濾器為例:
session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");
注意,org.hibernate.Filter的方法允許鏈式方法調用。(類(lèi)似上面例子中啟用Filter之后設定Filter參數這個(gè)?方法鏈?) Hibernate的其他部分也大多有這個(gè)特性。
下面是一個(gè)比較完整的例子,使用了記錄生效日期模式過(guò)濾有時(shí)效的數據:
<filter-def name="effectiveDate">
定義好后,如果想要保證取回的都是目前處于生效期的記錄,只需在獲取雇員數據的操作之前先開(kāi)啟過(guò)濾器即可:
Session session = ...;
注意:如果你打算在使用外連接(或者通過(guò)HQL或load fetching)的同時(shí)使用過(guò)濾器,要注意條件表達式的方向(左還是右)。 最安全的方式是使用左外連接(left outer joining)。并且通常來(lái)說(shuō),先寫(xiě)參數, 然后是操作符,最后寫(xiě)數據庫字段名。
第 18 章 XML映射
注意這是Hibernate 3.0的一個(gè)實(shí)驗性的特性。這一特性仍在積極開(kāi)發(fā)中。
18.1. 用XML數據進(jìn)行工作
Hibernate使得你可以用XML數據來(lái)進(jìn)行工作,恰如你用持久化的POJO進(jìn)行工作那樣。解析過(guò)的XML樹(shù) 可以被認為是代替POJO的另外一種在對象層面上表示關(guān)系型數據的途徑.
Hibernate支持采用dom4j作為操作XML樹(shù)的API。你可以寫(xiě)一些查詢(xún)從數據庫中檢索出 dom4j樹(shù),隨后你對這顆樹(shù)做的任何修改都將自動(dòng)同步回數據庫。你甚至可以用dom4j解析 一篇XML文檔,然后使用Hibernate的任一基本操作將它寫(xiě)入數據庫: persist(), saveOrUpdate(), merge(), delete(), replicate() (合并操作merge()目前還不支持)。
這一特性可以應用在很多場(chǎng)合,包括數據導入導出,通過(guò)JMS或SOAP具體化實(shí)體數據以及 基于XSLT的報表。
一個(gè)單一的映射就可以將類(lèi)的屬性和XML文檔的節點(diǎn)同時(shí)映射到數據庫。如果不需要映射類(lèi), 它也可以用來(lái)只映射XML文檔。
18.1.1. 指定同時(shí)映射XML和類(lèi)
這是一個(gè)同時(shí)映射POJO和XML的例子:
<class name="Account"
table="ACCOUNTS"
node="account">
<id name="accountId"
column="ACCOUNT_ID"
node="@id"/>
<many-to-one name="customer"
column="CUSTOMER_ID"
node=" embed-xml="false"/>
<property name="balance"
column="BALANCE"
node="balance"/>
...
</class>
18.1.2. 只定義XML映射
這是一個(gè)不映射POJO的例子:
<class entity-name="Account"
table="ACCOUNTS"
node="account">
<id name="id"
column="ACCOUNT_ID"
node="@id"
type="string"/>
<many-to-one name="customerId"
column="CUSTOMER_ID"
node=" embed-xml="false"
entity-name="Customer"/>
<property name="balance"
column="BALANCE"
node="balance"
type="big_decimal"/>
...
</class>
這個(gè)映射使得你既可以把數據作為一棵dom4j樹(shù)那樣訪(fǎng)問(wèn),又可以作為由屬性鍵值對(java Maps) 組成的圖那樣訪(fǎng)問(wèn)。屬性名字純粹是邏輯上的結構,你可以在HQL查詢(xún)中引用它。
18.2. XML映射元數據
許多Hibernate映射元素具有node屬性。這使你可以指定用來(lái)保存 屬性或實(shí)體數據的XML屬性或元素。node屬性必須是下列格式之一:
? "element-name" - 映射為指定的XML元素
? "@attribute-name" - 映射為指定的XML屬性
? "." - 映射為父元素
? "對于集合和單值的關(guān)聯(lián),有一個(gè)額外的embed-xml屬性可用。 這個(gè)屬性的缺省值是真(embed-xml="true")。如果embed-xml="true", 則對應于被關(guān)聯(lián)實(shí)體或值類(lèi)型的集合的XML樹(shù)將直接嵌入擁有這些關(guān)聯(lián)的實(shí)體的XML樹(shù)中。 否則,如果embed-xml="false",那么對于單值的關(guān)聯(lián),僅被引用的實(shí)體的標識符出現在 XML樹(shù)中(被引用實(shí)體本身不出現),而集合則根本不出現。
你應該小心,不要讓太多關(guān)聯(lián)的embed-xml屬性為真(embed-xml="true"),因為XML不能很好地處理 循環(huán)引用!
<class name="Customer"
table="CUSTOMER"
node="customer">
<id name="id"
column="CUST_ID"
node="@id"/>
<map name="accounts"
node="."
embed-xml="true">
<key column="CUSTOMER_ID"
not-null="true"/>
<map-key column="SHORT_DESC"
node="@short-desc"
type="string"/>
<one-to-many entity-name="Account"
embed-xml="false"
node="account"/>
</map>
<component name="name"
node="name">
<property name="firstName"
node="first-name"/>
<property name="initial"
node="initial"/>
<property name="lastName"
node="last-name"/>
</component>
...
</class>
在這個(gè)例子中,我們決定嵌入帳目號碼(account id)的集合,但不嵌入實(shí)際的帳目數據。下面的HQL查詢(xún):
from Customer c left join fetch c.accounts where c.lastName like :lastName
返回的數據集將是這樣:
<customer id="123456789">
<account id="987632567" short-desc="Savings"/>
<account id="985612323" short-desc="Credit Card"/>
<name>
<first-name>Gavin</first-name>
<initial>A</initial>
<last-name>King</last-name>
</name>
...
</customer>
如果你把一對多映射<one-to-many>的embed-xml屬性置為真(embed-xml="true"), 則數據看上去就像這樣:
<customer id="123456789">
<account id="987632567" short-desc="Savings">
<customer id="123456789"/>
<balance>100.29</balance>
</account>
<account id="985612323" short-desc="Credit Card">
<customer id="123456789"/>
<balance>-2370.34</balance>
</account>
<name>
<first-name>Gavin</first-name>
<initial>A</initial>
<last-name>King</last-name>
</name>
...
</customer>
18.3. 操作XML數據
讓我們來(lái)讀入和更新應用程序中的XML文檔。通過(guò)獲取一個(gè)dom4j會(huì )話(huà)可以做到這一點(diǎn):
Document doc = ....;
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();
.createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
.list();
for ( int i=0; i<results.size(); i++ ) {
//add the customer data to the XML document
Element customer = (Element) results.get(i);
doc.add(customer);
}
session.close();
Session session = factory.openSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Transaction tx = session.beginTransaction();
for ( int i=0; i<results.size(); i++ ) {
Element customer = (Element) results.get(i);
//change the customer name in the XML and database
Element name = customer.element("name");
name.element("first-name").setText(firstName);
name.element("initial").setText(initial);
name.element("last-name").setText(lastName);
}
session.close();
將這一特色與Hibernate的replicate()操作結合起來(lái)對于實(shí)現的基于XML的數據導入/導出將非常有用.
第 19 章 提升性能
19.1. 抓取策略(Fetching strategies)
抓取策略(fetching strategy) 是指:當應用程序需要在(Hibernate實(shí)體對象圖的)關(guān)聯(lián)關(guān)系間進(jìn)行導航的時(shí)候, Hibernate如何獲取關(guān)聯(lián)對象的策略。抓取策略可以在O/R映射的元數據中聲明,也可以在特定的HQL 或條件查詢(xún)(Criteria Query)中重載聲明。
Hibernate3 定義了如下幾種抓取策略:
? 連接抓?。↗oin fetching) - Hibernate通過(guò) 在SELECT語(yǔ)句使用OUTER JOIN(外連接)來(lái) 獲得對象的關(guān)聯(lián)實(shí)例或者關(guān)聯(lián)集合。
? 查詢(xún)抓?。⊿elect fetching) - 另外發(fā)送一條 SELECT 語(yǔ)句抓取當前對象的關(guān)聯(lián)實(shí)體或集合。除非你顯式的指定lazy="false"禁止 延遲抓?。╨azy fetching),否則只有當你真正訪(fǎng)問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì )執行第二條select語(yǔ)句。
? 子查詢(xún)抓?。⊿ubselect fetching) - 另外發(fā)送一條SELECT 語(yǔ)句抓取在前面查詢(xún)到(或者抓取到)的所有實(shí)體對象的關(guān)聯(lián)集合。除非你顯式的指定lazy="false" 禁止延遲抓?。╨azy fetching),否則只有當你真正訪(fǎng)問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì )執行第二條select語(yǔ)句。
? 批量抓?。˙atch fetching) - 對查詢(xún)抓取的優(yōu)化方案, 通過(guò)指定一個(gè)主鍵或外鍵列表,Hibernate使用單條SELECT語(yǔ)句獲取一批對象實(shí)例或集合。
Hibernate會(huì )區分下列各種情況:
? Immediate fetching,立即抓取 - 當宿主被加載時(shí),關(guān)聯(lián)、集合或屬性被立即抓取。
? Lazy collection fetching,延遲集合抓取- 直到應用程序對集合進(jìn)行了一次操作時(shí),集合才被抓取。(對集合而言這是默認行為。)
? "Extra-lazy" collection fetching,"Extra-lazy"集合抓取 -對集合類(lèi)中的每個(gè)元素而言,都是直到需要時(shí)才去訪(fǎng)問(wèn)數據庫。除非絕對必要,Hibernate不會(huì )試圖去把整個(gè)集合都抓取到內存里來(lái)(適用于非常大的集合)。
? Proxy fetching,代理抓取 - 對返回單值的關(guān)聯(lián)而言,當其某個(gè)方法被調用,而非對其關(guān)鍵字進(jìn)行g(shù)et操作時(shí)才抓取。
? "No-proxy" fetching,非代理抓取 - 對返回單值的關(guān)聯(lián)而言,當實(shí)例變量被訪(fǎng)問(wèn)的時(shí)候進(jìn)行抓取。與上面的代理抓取相比,這種方法沒(méi)有那么?延遲?得厲害(就算只訪(fǎng)問(wèn)標識符,也會(huì )導致關(guān)聯(lián)抓取)但是更加透明,因為對應用程序來(lái)說(shuō),不再看到proxy。這種方法需要在編譯期間進(jìn)行字節碼增強操作,因此很少需要用到。
? Lazy attribute fetching,屬性延遲加載 - 對屬性或返回單值的關(guān)聯(lián)而言,當其實(shí)例變量被訪(fǎng)問(wèn)的時(shí)候進(jìn)行抓取。需要編譯期字節碼強化,因此這一方法很少是必要的。
這里有兩個(gè)正交的概念:關(guān)聯(lián)何時(shí)被抓取,以及被如何抓?。〞?huì )采用什么樣的SQL語(yǔ)句)。不要混淆它們!我們使用抓取來(lái)改善性能。我們使用延遲來(lái)定義一些契約,對某特定類(lèi)的某個(gè)脫管的實(shí)例,知道有哪些數據是可以使用的。
19.1.1. 操作延遲加載的關(guān)聯(lián)
默認情況下,Hibernate 3對集合使用延遲select抓取,對返回單值的關(guān)聯(lián)使用延遲代理抓取。對幾乎是所有的應用而言,其絕大多數的關(guān)聯(lián),這種策略都是有效的。
注意:假若你設置了hibernate.default_batch_fetch_size,Hibernate會(huì )對延遲加載采取批量抓取優(yōu)化措施(這種優(yōu)化也可能會(huì )在更細化的級別打開(kāi))。
然而,你必須了解延遲抓取帶來(lái)的一個(gè)問(wèn)題。在一個(gè)打開(kāi)的Hibernate session上下文之外調用延遲集合會(huì )導致一次意外。比如:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
s.close();
在Session關(guān)閉后,permessions集合將是未實(shí)例化的、不再可用,因此無(wú)法正常載入其狀態(tài)。 Hibernate對脫管對象不支持延遲實(shí)例化. 這里的修改方法是:將permissions讀取數據的代碼 移到tx.commit()之前。
除此之外,通過(guò)對關(guān)聯(lián)映射指定lazy="false",我們也可以使用非延遲的集合或關(guān)聯(lián)。但是, 對絕大部分集合來(lái)說(shuō),更推薦使用延遲方式抓取數據。如果在你的對象模型中定義了太多的非延遲關(guān)聯(lián),Hibernate最終幾乎需要在每個(gè)事務(wù)中載入整個(gè)數據庫到內存中!
但是,另一方面,在一些特殊的事務(wù)中,我們也經(jīng)常需要使用到連接抓?。ㄋ旧砩暇褪欠茄舆t的),以代替查詢(xún)抓取。 下面我們將會(huì )很快明白如何具體的定制Hibernate中的抓取策略。在Hibernate3中,具體選擇哪種抓取策略的機制是和選擇 單值關(guān)聯(lián)或集合關(guān)聯(lián)相一致的。
19.1.2. 調整抓取策略(Tuning fetch strategies)
查詢(xún)抓?。J的)在N+1查詢(xún)的情況下是極其脆弱的,因此我們可能會(huì )要求在映射文檔中定義使用連接抓?。?
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
在映射文檔中定義的抓取策略將會(huì )對以下列表條目產(chǎn)生影響:
? 通過(guò)get()或load()方法取得數據。
? 只有在關(guān)聯(lián)之間進(jìn)行導航時(shí),才會(huì )隱式的取得數據。
? 條件查詢(xún)
? 使用了subselect抓取的HQL查詢(xún)
不管你使用哪種抓取策略,定義為非延遲的類(lèi)圖會(huì )被保證一定裝載入內存。注意這可能意味著(zhù)在一條HQL查詢(xún)后緊跟著(zhù)一系列的查詢(xún)。
通常情況下,我們并不使用映射文檔進(jìn)行抓取策略的定制。更多的是,保持其默認值,然后在特定的事務(wù)中, 使用HQL的左連接抓?。╨eft join fetch) 對其進(jìn)行重載。這將通知 Hibernate在第一次查詢(xún)中使用外部關(guān)聯(lián)(outer join),直接得到其關(guān)聯(lián)數據。 在條件查詢(xún) API中,應該調用 setFetchMode(FetchMode.JOIN)語(yǔ)句。
也許你喜歡僅僅通過(guò)條件查詢(xún),就可以改變get() 或 load()語(yǔ)句中的數據抓取策略。例如:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
(這就是其他ORM解決方案的?抓取計劃(fetch plan)?在Hibernate中的等價(jià)物。)
截然不同的一種避免N+1次查詢(xún)的方法是,使用二級緩存。
19.1.3. 單端關(guān)聯(lián)代理(Single-ended association proxies)
在Hinerbate中,對集合的延遲抓取的采用了自己的實(shí)現方法。但是,對于單端關(guān)聯(lián)的延遲抓取,則需要采用 其他不同的機制。單端關(guān)聯(lián)的目標實(shí)體必須使用代理,Hihernate在運行期二進(jìn)制級(通過(guò)優(yōu)異的CGLIB庫), 為持久對象實(shí)現了延遲載入代理。
默認的,Hibernate3將會(huì )為所有的持久對象產(chǎn)生代理(在啟動(dòng)階段),然后使用他們實(shí)現 多對一(many-to-one)關(guān)聯(lián)和一對一(one-to-one) 關(guān)聯(lián)的延遲抓取。
在映射文件中,可以通過(guò)設置proxy屬性為目標class聲明一個(gè)接口供代理接口使用。 默認的,Hibernate將會(huì )使用該類(lèi)的一個(gè)子類(lèi)。 注意:被代理的類(lèi)必須實(shí)現一個(gè)至少包可見(jiàn)的默認構造函數,我們建議所有的持久類(lèi)都應擁有這樣的構造函數
在如此方式定義一個(gè)多態(tài)類(lèi)的時(shí)候,有許多值得注意的常見(jiàn)性的問(wèn)題,例如:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
首先,Cat實(shí)例永遠不可以被強制轉換為DomesticCat, 即使它本身就是DomesticCat實(shí)例。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
其次,代理的?==?可能不再成立。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
雖然如此,但實(shí)際情況并沒(méi)有看上去那么糟糕。雖然我們現在有兩個(gè)不同的引用,分別指向這兩個(gè)不同的代理對象, 但實(shí)際上,其底層應該是同一個(gè)實(shí)例對象:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
第三,你不能對?final類(lèi)?或?具有final方法的類(lèi)?使用CGLIB代理。
最后,如果你的持久化對象在實(shí)例化時(shí)需要某些資源(例如,在實(shí)例化方法、默認構造方法中), 那么代理對象也同樣需要使用這些資源。實(shí)際上,代理類(lèi)是持久化類(lèi)的子類(lèi)。
這些問(wèn)題都源于Java的單根繼承模型的天生限制。如果你希望避免這些問(wèn)題,那么你的每個(gè)持久化類(lèi)必須實(shí)現一個(gè)接口, 在此接口中已經(jīng)聲明了其業(yè)務(wù)方法。然后,你需要在映射文檔中再指定這些接口。例如:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
這里CatImpl實(shí)現了Cat接口, DomesticCatImpl實(shí)現DomesticCat接口。 在load()、iterate()方法中就會(huì )返回 Cat和DomesticCat的代理對象。 (注意list()并不會(huì )返回代理對象。)
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name=‘fritz‘");
Cat fritz = (Cat) iter.next();
這里,對象之間的關(guān)系也將被延遲載入。這就意味著(zhù),你應該將屬性聲明為Cat,而不是CatImpl。
但是,在有些方法中是不需要使用代理的。例如:
? equals()方法,如果持久類(lèi)沒(méi)有重載equals()方法。
? hashCode()方法,如果持久類(lèi)沒(méi)有重載hashCode()方法。
? 標志符的getter方法。
Hibernate將會(huì )識別出那些重載了equals()、或hashCode()方法的持久化類(lèi)。
若選擇lazy="no-proxy"而非默認的lazy="proxy",我們可以避免類(lèi)型轉換帶來(lái)的問(wèn)題。然而,這樣我們就需要編譯期字節碼增強,并且所有的操作都會(huì )導致立刻進(jìn)行代理初始化。
19.1.4. 實(shí)例化集合和代理(Initializing collections and proxies)
在Session范圍之外訪(fǎng)問(wèn)未初始化的集合或代理,Hibernate將會(huì )拋出LazyInitializationException異常。 也就是說(shuō),在分離狀態(tài)下,訪(fǎng)問(wèn)一個(gè)實(shí)體所擁有的集合,或者訪(fǎng)問(wèn)其指向代理的屬性時(shí),會(huì )引發(fā)此異常。
有時(shí)候我們需要保證某個(gè)代理或者集合在Session關(guān)閉前就已經(jīng)被初始化了。 當然,我們可以通過(guò)強行調用cat.getSex()或者cat.getKittens().size()之類(lèi)的方法來(lái)確保這一點(diǎn)。 但是這樣的程序會(huì )造成讀者的疑惑,也不符合通常的代碼規范。
靜態(tài)方法Hibernate.initialized() 為你的應用程序提供了一個(gè)便捷的途徑來(lái)延遲加載集合或代理。 只要它的Session處于open狀態(tài),Hibernate.initialize(cat) 將會(huì )為cat強制對代理實(shí)例化。 同樣,Hibernate.initialize( cat.getKittens() ) 對kittens的集合具有同樣的功能。
還有另外一種選擇,就是保持Session一直處于open狀態(tài),直到所有需要的集合或代理都被載入。 在某些應用架構中,特別是對于那些使用Hibernate進(jìn)行數據訪(fǎng)問(wèn)的代碼,以及那些在不同應用層和不同物理進(jìn)程中使用Hibernate的代碼。 在集合實(shí)例化時(shí),如何保證Session處于open狀態(tài)經(jīng)常會(huì )是一個(gè)問(wèn)題。有兩種方法可以解決此問(wèn)題:
? 在一個(gè)基于Web的應用中,可以利用servlet過(guò)濾器(filter),在用戶(hù)請求(request)結束、頁(yè)面生成 結束時(shí)關(guān)閉Session(這里使用了在展示層保持打開(kāi)Session模式(Open Session in View)), 當然,這將依賴(lài)于應用框架中異常需要被正確的處理。在返回界面給用戶(hù)之前,乃至在生成界面過(guò)程中發(fā)生異常的情況下, 正確關(guān)閉Session和結束事務(wù)將是非常重要的, 請參見(jiàn)Hibernate wiki上的"Open Session in View"模式,你可以找到示例。
? 在一個(gè)擁有單獨業(yè)務(wù)層的應用中,業(yè)務(wù)層必須在返回之前,為web層?準備?好其所需的數據集合。這就意味著(zhù) 業(yè)務(wù)層應該載入所有表現層/web層所需的數據,并將這些已實(shí)例化完畢的數據返回。通常,應用程序應該 為web層所需的每個(gè)集合調用Hibernate.initialize()(這個(gè)調用必須發(fā)生咱session關(guān)閉之前); 或者使用帶有FETCH從句,或FetchMode.JOIN的Hibernate查詢(xún), 事先取得所有的數據集合。如果你在應用中使用了Command模式,代替Session Facade , 那么這項任務(wù)將會(huì )變得簡(jiǎn)單的多。
? 你也可以通過(guò)merge()或lock()方法,在訪(fǎng)問(wèn)未實(shí)例化的集合(或代理)之前, 為先前載入的對象綁定一個(gè)新的Session。 顯然,Hibernate將不會(huì ),也不應該自動(dòng)完成這些任務(wù),因為這將引入一個(gè)特殊的事務(wù)語(yǔ)義。
有時(shí)候,你并不需要完全實(shí)例化整個(gè)大的集合,僅需要了解它的部分信息(例如其大?。?、或者集合的部分內容。
你可以使用集合過(guò)濾器得到其集合的大小,而不必實(shí)例化整個(gè)集合:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
這里的createFilter()方法也可以被用來(lái)有效的抓取集合的部分內容,而無(wú)需實(shí)例化整個(gè)集合:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
19.1.5. 使用批量抓?。║sing batch fetching)
Hibernate可以充分有效的使用批量抓取,也就是說(shuō),如果僅一個(gè)訪(fǎng)問(wèn)代理(或集合),那么Hibernate將不載入其他未實(shí)例化的代理。 批量抓取是延遲查詢(xún)抓取的優(yōu)化方案,你可以在兩種批量抓取方案之間進(jìn)行選擇:在類(lèi)級別和集合級別。
類(lèi)/實(shí)體級別的批量抓取很容易理解。假設你在運行時(shí)將需要面對下面的問(wèn)題:你在一個(gè)Session中載入了25個(gè) Cat實(shí)例,每個(gè)Cat實(shí)例都擁有一個(gè)引用成員owner, 其指向Person,而Person類(lèi)是代理,同時(shí)lazy="true"。 如果你必須遍歷整個(gè)cats集合,對每個(gè)元素調用getOwner()方法,Hibernate將會(huì )默認的執行25次SELECT查詢(xún), 得到其owner的代理對象。這時(shí),你可以通過(guò)在映射文件的Person屬性,顯式聲明batch-size,改變其行為:
<class name="Person" batch-size="10">...</class>
隨之,Hibernate將只需要執行三次查詢(xún),分別為10、10、 5。
你也可以在集合級別定義批量抓取。例如,如果每個(gè)Person都擁有一個(gè)延遲載入的Cats集合, 現在,Sesssion中載入了10個(gè)person對象,遍歷person集合將會(huì )引起10次SELECT查詢(xún), 每次查詢(xún)都會(huì )調用getCats()方法。如果你在Person的映射定義部分,允許對cats批量抓取, 那么,Hibernate將可以預先抓取整個(gè)集合。請看例子:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
如果整個(gè)的batch-size是3(筆誤?),那么Hibernate將會(huì )分四次執行SELECT查詢(xún), 按照3、3、3、1的大小分別載入數據。這里的每次載入的數據量還具體依賴(lài)于當前Session中未實(shí)例化集合的個(gè)數。
如果你的模型中有嵌套的樹(shù)狀結構,例如典型的帳單-原料結構(bill-of-materials pattern),集合的批量抓取是非常有用的。 (盡管在更多情況下對樹(shù)進(jìn)行讀取時(shí),嵌套集合(nested set)或原料路徑(materialized path)(××) 是更好的解決方法。)
19.1.6. 使用子查詢(xún)抓?。║sing subselect fetching)
假若一個(gè)延遲集合或單值代理需要抓取,Hibernate會(huì )使用一個(gè)subselect重新運行原來(lái)的查詢(xún),一次性讀入所有的實(shí)例。這和批量抓取的實(shí)現方法是一樣的,不會(huì )有破碎的加載。
19.1.7. 使用延遲屬性抓?。║sing lazy property fetching)
Hibernate3對單獨的屬性支持延遲抓取,這項優(yōu)化技術(shù)也被稱(chēng)為組抓?。╢etch groups)。 請注意,該技術(shù)更多的屬于市場(chǎng)特性。在實(shí)際應用中,優(yōu)化行讀取比優(yōu)化列讀取更重要。但是,僅載入類(lèi)的部分屬性在某些特定情況下會(huì )有用,例如在原有表中擁有幾百列數據、數據模型無(wú)法改動(dòng)的情況下。
可以在映射文件中對特定的屬性設置lazy,定義該屬性為延遲載入。
<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
屬性的延遲載入要求在其代碼構建時(shí)加入二進(jìn)制指示指令(bytecode instrumentation),如果你的持久類(lèi)代碼中未含有這些指令, Hibernate將會(huì )忽略這些屬性的延遲設置,仍然將其直接載入。
你可以在A(yíng)nt的Task中,進(jìn)行如下定義,對持久類(lèi)代碼加入?二進(jìn)制指令。?
<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
還有一種可以?xún)?yōu)化的方法,它使用HQL或條件查詢(xún)的投影(projection)特性,可以避免讀取非必要的列, 這一點(diǎn)至少對只讀事務(wù)是非常有用的。它無(wú)需在代碼構建時(shí)?二進(jìn)制指令?處理,因此是一個(gè)更加值得選擇的解決方法。
有時(shí)你需要在HQL中通過(guò)抓取所有屬性,強行抓取所有內容。
19.2. 二級緩存(The Second Level Cache)
Hibernate的Session在事務(wù)級別進(jìn)行持久化數據的緩存操作。 當然,也有可能分別為每個(gè)類(lèi)(或集合),配置集群、或JVM級別(SessionFactory級別)的緩存。 你甚至可以為之插入一個(gè)集群的緩存。注意,緩存永遠不知道其他應用程序對持久化倉庫(數據庫)可能進(jìn)行的修改 (即使可以將緩存數據設定為定期失效)。
默認情況下,Hibernate使用EHCache進(jìn)行JVM級別的緩存(目前,Hibernate已經(jīng)廢棄了對JCS的支持,未來(lái)版本中將會(huì )去掉它)。 你可以通過(guò)設置hibernate.cache.provider_class屬性,指定其他的緩存策略, 該緩存策略必須實(shí)現org.hibernate.cache.CacheProvider接口。
表 19.1. 緩存策略提供商(Cache Providers)
Cache Provider class Type Cluster Safe Query Cache Supported
Hashtable (not intended for production use) org.hibernate.cache.HashtableCacheProvider memory yes
EHCache org.hibernate.cache.EhCacheProvider memory, disk yes
OSCache org.hibernate.cache.OSCacheProvider memory, disk yes
SwarmCache org.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)
JBoss TreeCache org.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication) yes (clock sync req.)
19.2.1. 緩存映射(Cache mappings)
類(lèi)或者集合映射的?<cache>元素?可以有下列形式:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
(1) usage(必須)說(shuō)明了緩存的策略: transactional、 read-write、 nonstrict-read-write或 read-only。
(2) region (可選, 默認為類(lèi)或者集合的名字(class or collection role name)) 指定第二級緩存的區域名(name of the second level cache region)
(3) include (可選,默認為 all) non-lazy 當屬性級延遲抓取打開(kāi)時(shí), 標記為lazy="true"的實(shí)體的屬性可能無(wú)法被緩存
另外(首選?), 你可以在hibernate.cfg.xml中指定<class-cache>和 <collection-cache> 元素。
這里的usage 屬性指明了緩存并發(fā)策略(cache concurrency strategy)。
19.2.2. 策略:只讀緩存(Strategy: read only)
如果你的應用程序只需讀取一個(gè)持久化類(lèi)的實(shí)例,而無(wú)需對其修改, 那么就可以對其進(jìn)行只讀 緩存。這是最簡(jiǎn)單,也是實(shí)用性最好的方法。甚至在集群中,它也能完美地運作。
<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>
19.2.3. 策略:讀/寫(xiě)緩存(Strategy: read/write)
如果應用程序需要更新數據,那么使用讀/寫(xiě)緩存 比較合適。 如果應用程序要求?序列化事務(wù)?的隔離級別(serializable transaction isolation level),那么就決不能使用這種緩存策略。 如果在JTA環(huán)境中使用緩存,你必須指定hibernate.transaction.manager_lookup_class屬性的值, 通過(guò)它,Hibernate才能知道該應用程序中JTA的TransactionManager的具體策略。 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調用前, 整個(gè)事務(wù)已經(jīng)結束。 如果你想在集群環(huán)境中使用此策略,你必須保證底層的緩存實(shí)現支持鎖定(locking)。Hibernate內置的緩存策略并不支持鎖定功能。
<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>
19.2.4. 策略:非嚴格讀/寫(xiě)緩存(Strategy: nonstrict read/write)
如果應用程序只偶爾需要更新數據(也就是說(shuō),兩個(gè)事務(wù)同時(shí)更新同一記錄的情況很不常見(jiàn)),也不需要十分嚴格的事務(wù)隔離, 那么比較適合使用非嚴格讀/寫(xiě)緩存策略。如果在JTA環(huán)境中使用該策略, 你必須為其指定hibernate.transaction.manager_lookup_class屬性的值, 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調用前, 整個(gè)事務(wù)已經(jīng)結束。
19.2.5. 策略:事務(wù)緩存(transactional)
Hibernate的事務(wù)緩存策略提供了全事務(wù)的緩存支持, 例如對JBoss TreeCache的支持。這樣的緩存只能用于JTA環(huán)境中,你必須指定 為其hibernate.transaction.manager_lookup_class屬性。
沒(méi)有一種緩存提供商能夠支持上列的所有緩存并發(fā)策略。下表中列出了各種提供器、及其各自適用的并發(fā)策略。
表 19.2. 各種緩存提供商對緩存并發(fā)策略的支持情況(Cache Concurrency Strategy Support)
Cache read-only nonstrict-read-write read-write transactional
Hashtable (not intended for production use) yes yes yes
EHCache yes yes yes
OSCache yes yes yes
SwarmCache yes yes
JBoss TreeCache yes yes
19.3. 管理緩存(Managing the caches)
無(wú)論何時(shí),當你給save()、update()或 saveOrUpdate()方法傳遞一個(gè)對象時(shí),或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個(gè)對象時(shí), 該對象都將被加入到Session的內部緩存中。
當隨后flush()方法被調用時(shí),對象的狀態(tài)會(huì )和數據庫取得同步。 如果你不希望此同步操作發(fā)生,或者你正處理大量對象、需要對有效管理內存時(shí),你可以調用evict() 方法,從一級緩存中去掉這些對象及其集合。
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
Session還提供了一個(gè)contains()方法,用來(lái)判斷某個(gè)實(shí)例是否處于當前session的緩存中。
如若要把所有的對象從session緩存中徹底清除,則需要調用Session.clear()。
對于二級緩存來(lái)說(shuō),在SessionFactory中定義了許多方法, 清除緩存中實(shí)例、整個(gè)類(lèi)、集合實(shí)例或者整個(gè)集合。
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
CacheMode參數用于控制具體的Session如何與二級緩存進(jìn)行交互。
? CacheMode.NORMAL - 從二級緩存中讀、寫(xiě)數據。
? CacheMode.GET - 從二級緩存中讀取數據,僅在數據更新時(shí)對二級緩存寫(xiě)數據。
? CacheMode.PUT - 僅向二級緩存寫(xiě)數據,但不從二級緩存中讀數據。
? CacheMode.REFRESH - 僅向二級緩存寫(xiě)數據,但不從二級緩存中讀數據。通過(guò) hibernate.cache.use_minimal_puts的設置,強制二級緩存從數據庫中讀取數據,刷新緩存內容。
如若需要查看二級緩存或查詢(xún)緩存區域的內容,你可以使用統計(Statistics) API。
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
此時(shí),你必須手工打開(kāi)統計選項??蛇x的,你可以讓Hibernate更人工可讀的方式維護緩存內容。
hibernate.generate_statistics true
hibernate.cache.use_structured_entries true
19.4. 查詢(xún)緩存(The Query Cache)
查詢(xún)的結果集也可以被緩存。只有當經(jīng)常使用同樣的參數進(jìn)行查詢(xún)時(shí),這才會(huì )有些用處。 要使用查詢(xún)緩存,首先你必須打開(kāi)它:
hibernate.cache.use_query_cache true
該設置將會(huì )創(chuàng )建兩個(gè)緩存區域 - 一個(gè)用于保存查詢(xún)結果集(org.hibernate.cache.StandardQueryCache); 另一個(gè)則用于保存最近查詢(xún)的一系列表的時(shí)間戳(org.hibernate.cache.UpdateTimestampsCache)。 請注意:在查詢(xún)緩存中,它并不緩存結果集中所包含的實(shí)體的確切狀態(tài);它只緩存這些實(shí)體的標識符屬性的值、以及各值類(lèi)型的結果。 所以查詢(xún)緩存通常會(huì )和二級緩存一起使用。
絕大多數的查詢(xún)并不能從查詢(xún)緩存中受益,所以Hibernate默認是不進(jìn)行查詢(xún)緩存的。如若需要進(jìn)行緩存,請調用 Query.setCacheable(true)方法。這個(gè)調用會(huì )讓查詢(xún)在執行過(guò)程中時(shí)先從緩存中查找結果, 并將自己的結果集放到緩存中去。
如果你要對查詢(xún)緩存的失效政策進(jìn)行精確的控制,你必須調用Query.setCacheRegion()方法, 為每個(gè)查詢(xún)指定其命名的緩存區域。
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
如果查詢(xún)需要強行刷新其查詢(xún)緩存區域,那么你應該調用Query.setCacheMode(CacheMode.REFRESH)方法。 這對在其他進(jìn)程中修改底層數據(例如,不通過(guò)Hibernate修改數據),或對那些需要選擇性更新特定查詢(xún)結果集的情況特別有用。 這是對SessionFactory.evictQueries()的更為有效的替代方案,同樣可以清除查詢(xún)緩存區域。
19.5. 理解集合性能(Understanding Collection performance)
前面我們已經(jīng)對集合進(jìn)行了足夠的討論。本段中,我們將著(zhù)重講述集合在運行時(shí)的事宜。
19.5.1. 分類(lèi)(Taxonomy)
Hibernate定義了三種基本類(lèi)型的集合:
? 值數據集合
? 一對多關(guān)聯(lián)
? 多對多關(guān)聯(lián)
這個(gè)分類(lèi)是區分了不同的表和外鍵關(guān)系類(lèi)型,但是它沒(méi)有告訴我們關(guān)系模型的所有內容。 要完全理解他們的關(guān)系結構和性能特點(diǎn),我們必須同時(shí)考慮?用于Hibernate更新或刪除集合行數據的主鍵的結構?。 因此得到了如下的分類(lèi):
? 有序集合類(lèi)
? 集合(sets)
? 包(bags)
所有的有序集合類(lèi)(maps, lists, arrays)都擁有一個(gè)由<key>和 <index>組成的主鍵。 這種情況下集合類(lèi)的更新是非常高效的——主鍵已經(jīng)被有效的索引,因此當Hibernate試圖更新或刪除一行時(shí),可以迅速找到該行數據。
集合(sets)的主鍵由<key>和其他元素字段構成。 對于有些元素類(lèi)型來(lái)說(shuō),這很低效,特別是組合元素或者大文本、大二進(jìn)制字段; 數據庫可能無(wú)法有效的對復雜的主鍵進(jìn)行索引。 另一方面,對于一對多、多對多關(guān)聯(lián),特別是合成的標識符來(lái)說(shuō),集合也可以達到同樣的高效性能。( 附注:如果你希望SchemaExport為你的<set>創(chuàng )建主鍵, 你必須把所有的字段都聲明為not-null="true"。)
<idbag>映射定義了代理鍵,因此它總是可以很高效的被更新。事實(shí)上, <idbag>擁有著(zhù)最好的性能表現。
Bag是最差的。因為bag允許重復的元素值,也沒(méi)有索引字段,因此不可能定義主鍵。 Hibernate無(wú)法判斷出重復的行。當這種集合被更改時(shí),Hibernate將會(huì )先完整地移除 (通過(guò)一個(gè)(in a single DELETE))整個(gè)集合,然后再重新創(chuàng )建整個(gè)集合。 因此Bag是非常低效的。
請注意:對于一對多關(guān)聯(lián)來(lái)說(shuō),?主鍵?很可能并不是數據庫表的物理主鍵。 但就算在此情況下,上面的分類(lèi)仍然是有用的。(它仍然反映了Hibernate在集合的各數據行中是如何進(jìn)行?定位?的。)
19.5.2. Lists, maps 和sets用于更新效率最高
根據我們上面的討論,顯然有序集合類(lèi)型和大多數set都可以在增加、刪除、修改元素中擁有最好的性能。
可論證的是對于多對多關(guān)聯(lián)、值數據集合而言,有序集合類(lèi)比集合(set)有一個(gè)好處。因為Set的內在結構, 如果?改變?了一個(gè)元素,Hibernate并不會(huì )更新(UPDATE)這一行。 對于Set來(lái)說(shuō),只有在插入(INSERT)和刪除(DELETE) 操作時(shí)?改變?才有效。再次強調:這段討論對?一對多關(guān)聯(lián)?并不適用。
注意到數組無(wú)法延遲載入,我們可以得出結論,list, map和idbags是最高效的(非反向)集合類(lèi)型,set則緊隨其后。 在Hibernate中,set應該時(shí)最通用的集合類(lèi)型,這時(shí)因為?set?的語(yǔ)義在關(guān)系模型中是最自然的。
但是,在設計良好的Hibernate領(lǐng)域模型中,我們通??梢钥吹礁嗟募鲜聦?shí)上是帶有inverse="true" 的一對多的關(guān)聯(lián)。對于這些關(guān)聯(lián),更新操作將會(huì )在多對一的這一端進(jìn)行處理。因此對于此類(lèi)情況,無(wú)需考慮其集合的更新性能。
19.5.3. Bag和list是反向集合類(lèi)中效率最高的
在把bag扔進(jìn)水溝之前,你必須了解,在一種情況下,bag的性能(包括list)要比set高得多: 對于指明了inverse="true"的集合類(lèi)(比如說(shuō),標準的雙向的一對多關(guān)聯(lián)), 我們可以在未初始化(fetch)包元素的情況下直接向bag或list添加新元素! 這是因為Collection.add())或者Collection.addAll() 方法 對bag或者List總是返回true(這點(diǎn)與與Set不同)。因此對于下面的相同代碼來(lái)說(shuō),速度會(huì )快得多。
Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();
19.5.4. 一次性刪除(One shot delete)
偶爾的,逐個(gè)刪除集合類(lèi)中的元素是相當低效的。Hibernate并沒(méi)那么笨, 如果你想要把整個(gè)集合都刪除(比如說(shuō)調用list.clear()),Hibernate只需要一個(gè)DELETE就搞定了。
假設我們在一個(gè)長(cháng)度為20的集合類(lèi)中新增加了一個(gè)元素,然后再刪除兩個(gè)。 Hibernate會(huì )安排一條INSERT語(yǔ)句和兩條DELETE語(yǔ)句(除非集合類(lèi)是一個(gè)bag)。 這當然是顯而易見(jiàn)的。
但是,假設我們刪除了18個(gè)數據,只剩下2個(gè),然后新增3個(gè)。則有兩種處理方式:
? 逐一的刪除這18個(gè)數據,再新增三個(gè);
? 刪除整個(gè)集合類(lèi)(只用一句DELETE語(yǔ)句),然后增加5個(gè)數據。
Hibernate還沒(méi)那么聰明,知道第二種選擇可能會(huì )比較快。 (也許讓Hibernate不這么聰明也是好事,否則可能會(huì )引發(fā)意外的?數據庫觸發(fā)器?之類(lèi)的問(wèn)題。)
幸運的是,你可以強制使用第二種策略。你需要取消原來(lái)的整個(gè)集合類(lèi)(解除其引用), 然后再返回一個(gè)新的實(shí)例化的集合類(lèi),只包含需要的元素。有些時(shí)候這是非常有用的。
顯然,一次性刪除并不適用于被映射為inverse="true"的集合。
19.6. 監測性能(Monitoring performance)
沒(méi)有監測和性能參數而進(jìn)行優(yōu)化是毫無(wú)意義的。Hibernate為其內部操作提供了一系列的示意圖,因此可以從 每個(gè)SessionFactory抓取其統計數據。
19.6.1. 監測SessionFactory
你可以有兩種方式訪(fǎng)問(wèn)SessionFactory的數據記錄,第一種就是自己直接調用 sessionFactory.getStatistics()方法讀取、顯示統計數據。
此外,如果你打開(kāi)StatisticsService MBean選項,那么Hibernate則可以使用JMX技術(shù) 發(fā)布其數據記錄。你可以讓?xiě)弥兴械腟essionFactory同時(shí)共享一個(gè)MBean,也可以每個(gè) SessionFactory分配一個(gè)MBean。下面的代碼即是其演示代碼:
// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory‘s
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
server.registerMBean(stats, on); // Register the MBean on the server
TODO:仍需要說(shuō)明的是:在第一個(gè)例子中,我們直接得到和使用MBean;而在第二個(gè)例子中,在使用MBean之前 我們則需要給出SessionFactory的JNDI名,使用hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") 得到SessionFactory,然后將MBean保存于其中。
你可以通過(guò)以下方法打開(kāi)或關(guān)閉SessionFactory的監測功能:
? 在配置期間,將hibernate.generate_statistics設置為true或false;
? 在運行期間,則可以可以通過(guò)sf.getStatistics().setStatisticsEnabled(true) 或hibernateStatsBean.setStatisticsEnabled(true)
你也可以在程序中調用clear()方法重置統計數據,調用logSummary() 在日志中記錄(info級別)其總結。
19.6.2. 數據記錄(Metrics)
Hibernate提供了一系列數據記錄,其記錄的內容包括從最基本的信息到與具體場(chǎng)景的特殊信息。所有的測量值都可以由 Statistics接口進(jìn)行訪(fǎng)問(wèn),主要分為三類(lèi):
? 使用Session的普通數據記錄,例如打開(kāi)的Session的個(gè)數、取得的JDBC的連接數等;
? 實(shí)體、集合、查詢(xún)、緩存等內容的統一數據記錄
? 和具體實(shí)體、集合、查詢(xún)、緩存相關(guān)的詳細數據記錄
例如:你可以檢查緩存的命中成功次數,緩存的命中失敗次數,實(shí)體、集合和查詢(xún)的使用概率,查詢(xún)的平均時(shí)間等。請注意 Java中時(shí)間的近似精度是毫秒。Hibernate的數據精度和具體的JVM有關(guān),在有些平臺上其精度甚至只能精確到10秒。
你可以直接使用getter方法得到全局數據記錄(例如,和具體的實(shí)體、集合、緩存區無(wú)關(guān)的數據),你也可以在具體查詢(xún)中通過(guò)標記實(shí)體名、 或HQL、SQL語(yǔ)句得到某實(shí)體的數據記錄。請參考Statistics、EntityStatistics、 CollectionStatistics、SecondLevelCacheStatistics、 和QueryStatistics的API文檔以抓取更多信息。下面的代碼則是個(gè)簡(jiǎn)單的例子:
Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
如果你想得到所有實(shí)體、集合、查詢(xún)和緩存區的數據,你可以通過(guò)以下方法獲得實(shí)體、集合、查詢(xún)和緩存區列表: getQueries()、getEntityNames()、 getCollectionRoleNames()和 getSecondLevelCacheRegionNames()。
第 20 章 工具箱指南
可以通過(guò)一系列Eclipse插件、命令行工具和Ant任務(wù)來(lái)進(jìn)行與Hibernate關(guān)聯(lián)的轉換。
除了Ant任務(wù)外,當前的Hibernate Tools也包含了Eclipse IDE的插件,用于與現存數據庫的逆向工程。
? Mapping Editor: Hibernate XML映射文件的編輯器,支持自動(dòng)完成和語(yǔ)法高亮。它也支持對類(lèi)名和屬性/字段名的語(yǔ)義自動(dòng)完成,比通常的XML編輯器方便得多。
? Console: Console是Eclipse的一個(gè)新視圖。除了對你的console配置的樹(shù)狀概覽,你還可以獲得對你持久化類(lèi)及其關(guān)聯(lián)的交互式視圖。Console允許你對數據庫執行HQL查詢(xún),并直接在Eclipse中瀏覽結果。
? Development Wizards: 在Hibernate Eclipse tools中還提供了幾個(gè)向導;你可以用向導快速生成Hibernate 配置文件(cfg.xml),你甚至還可以同現存的數據庫schema中反向工程出POJO源代碼與Hibernate 映射文件。反向工程支持可定制的模版。
? Ant Tasks:
要得到更多信息,請查閱 Hibernate Tools 包及其文檔。
同時(shí),Hibernate主發(fā)行包還附帶了一個(gè)集成的工具(它甚至可以在Hibernate?內部?快速運行)SchemaExport ,也就是 hbm2ddl。
20.1. Schema自動(dòng)生成(Automatic schema generation)
可以從你的映射文件使用一個(gè)Hibernate工具生成DDL。 生成的schema包含有對實(shí)體和集合類(lèi)表的完整性引用約束(主鍵和外鍵)。涉及到的標示符生成器所需的表和sequence也會(huì )同時(shí)生成。
在使用這個(gè)工具的時(shí)候,你必須 通過(guò)hibernate.dialet屬性指定一個(gè)SQL方言(Dialet),因為DDL是與供應商高度相關(guān)的。
首先,要定制你的映射文件,來(lái)改善生成的schema。
20.1.1. 對schema定制化(Customizing the schema)
很多Hibernate映射元素定義了可選的length、precision 或者 scale屬性。你可以通過(guò)這個(gè)屬性設置字段的長(cháng)度、精度、小數點(diǎn)位數。
<property name="zip" length="5"/>
<property name="balance" precision="12" scale="2"/>
有些tag還接受not-null屬性(用來(lái)在表字段上生成NOT NULL約束)和unique屬性(用來(lái)在表字段上生成UNIQUE約束)。
<many-to-one name="bar" column="barId" not-null="true"/>
<element column="serialNumber" type="long" not-null="true" unique="true"/>
unique-key屬性可以對成組的字段指定一個(gè)唯一鍵約束(unique key constraint)。目前,unique-key屬性指定的值在生成DDL時(shí)并不會(huì )被當作這個(gè)約束的名字,它們只是在用來(lái)在映射文件內部用作區分的。
<many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/>
<property name="employeeId" unique-key="OrgEmployee"/>
index屬性會(huì )用對應的字段(一個(gè)或多個(gè))生成一個(gè)index,它指出了這個(gè)index的名字。如果多個(gè)字段對應的index名字相同,就會(huì )生成包含這些字段的index。
<property name="lastName" index="CustName"/>
<property name="firstName" index="CustName"/>
foreign-key屬性可以用來(lái)覆蓋任何生成的外鍵約束的名字。
<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>
很多映射元素還接受<column>子元素。這在定義跨越多字段的類(lèi)型時(shí)特別有用。
<property name="name" type="my.customtypes.Name"/>
<column name="last" not-null="true" index="bar_idx" length="30"/>
<column name="first" not-null="true" index="bar_idx" length="20"/>
<column name="initial"/>
</property>
default屬性為字段指定一個(gè)默認值 (在保存被映射的類(lèi)的新實(shí)例之前,你應該將同樣的值賦于對應的屬性)。
<property name="credits" type="integer" insert="false">
<column name="credits" default="10"/>
</property>
<version name="version" type="integer" insert="false">
<column name="version" default="0"/>
</property>
sql-type屬性允許用戶(hù)覆蓋默認的Hibernate類(lèi)型到SQL數據類(lèi)型的映射。
<property name="balance" type="float">
<column name="balance" sql-type="decimal(13,3)"/>
</property>
check屬性允許用戶(hù)指定一個(gè)約束檢查。
<property name="foo" type="integer">
<column name="foo" check="foo > 10"/>
</property>
<class name="Foo" table="foos" check="bar < 100.0">
...
<property name="bar" type="float"/>
</class>
表 20.1. Summary
屬性(Attribute) 值(Values) 解釋?zhuān)↖nterpretation)
length 數字 字段長(cháng)度
precision 數字 精度(decimal precision)
scale 數字 小數點(diǎn)位數(decimal scale)
not-null true|false 指明字段是否應該是非空的
unique true|false 指明是否該字段具有惟一約束
index index_name 指明一個(gè)(多字段)的索引(index)的名字
unique-key unique_key_name 指明多字段惟一約束的名字(參見(jiàn)上面的說(shuō)明)
foreign-key foreign_key_name specifies the name of the foreign key constraint generated for an association, for a <one-to-one>, <many-to-one>, <key>, or <many-to-many> mapping element. Note that inverse="true" sides will not be considered by SchemaExport. 指明一個(gè)外鍵的名字,它是為關(guān)聯(lián)生成的,或者<one-to-one>,<many-to-one>, <key>, 或者<many-to-many>映射元素。注意inverse="true"在SchemaExport時(shí)會(huì )被忽略。
sql-type SQL 字段類(lèi)型 覆蓋默認的字段類(lèi)型(只能用于<column>屬性)
default SQL表達式 為字段指定默認值
check SQL 表達式 對字段或表加入SQL約束檢查
<comment>元素可以讓你在生成的schema中加入注釋。
<class name="Customer" table="CurCust">
<comment>Current customers only</comment>
...
</class>
<property name="balance">
<column name="bal">
<comment>Balance in USD</comment>
</column>
</property>
結果是在生成的DDL中包含comment on table 或者 comment on column語(yǔ)句(假若支持的話(huà))。
20.1.2. 運行該工具
SchemaExport工具把DDL腳本寫(xiě)到標準輸出,同時(shí)/或者執行DDL語(yǔ)句。
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaExport options mapping_files
表 20.2. SchemaExport命令行選項
選項 說(shuō)明
--quiet 不要把腳本輸出到stdout
--drop 只進(jìn)行drop tables的步驟
--create 只創(chuàng )建表
--text 不執行在數據庫中運行的步驟
--output=my_schema.ddl 把輸出的ddl腳本輸出到一個(gè)文件
--naming=eg.MyNamingStrategy 選擇一個(gè)命名策略(NamingStrategy)
--config=hibernate.cfg.xml 從XML文件讀入Hibernate配置
--properties=hibernate.properties 從文件讀入數據庫屬性
--format 把腳本中的SQL語(yǔ)句對齊和美化
--delimiter=; 為腳本設置行結束符
你甚至可以在你的應用程序中嵌入SchemaExport工具:
Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);
20.1.3. 屬性(Properties)
可以通過(guò)如下方式指定數據庫屬性:
? 通過(guò)-D<property>系統參數
? 在hibernate.properties文件中
? 位于一個(gè)其它名字的properties文件中,然后用 --properties參數指定
所需的參數包括:
表 20.3. SchemaExport 連接屬性
屬性名 說(shuō)明
hibernate.connection.driver_class jdbc driver class
hibernate.connection.url jdbc url
hibernate.connection.username database user
hibernate.connection.password user password
hibernate.dialect 方言(dialect)
20.1.4. 使用Ant(Using Ant)
你可以在你的Ant build腳本中調用SchemaExport:
<target name="schemaexport">
<taskdef name="schemaexport"
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="class.path"/>
<schemaexport
properties="hibernate.properties"
quiet="no"
text="no"
drop="no"
delimiter=";"
output="schema-export.sql">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>
</target>
20.1.5. 對schema的增量更新(Incremental schema updates)
SchemaUpdate工具對已存在的schema采用"增量"方式進(jìn)行更新。注意SchemaUpdate嚴重依賴(lài)于JDBC metadata API,所以它并非對所有JDBC驅動(dòng)都有效。
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files
表 20.4. SchemaUpdate命令行選項
選項 說(shuō)明
--quiet 不要把腳本輸出到stdout
--text 不把腳本輸出到數據庫
--naming=eg.MyNamingStrategy 選擇一個(gè)命名策略 (NamingStrategy)
--properties=hibernate.properties 從指定文件讀入數據庫屬性
--config=hibernate.cfg.xml 指定一個(gè) .cfg.xml文件
你可以在你的應用程序中嵌入SchemaUpdate工具:
Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);
20.1.6. 用Ant來(lái)增量更新schema(Using Ant for incremental schema updates)
你可以在A(yíng)nt腳本中調用SchemaUpdate:
<target name="schemaupdate">
<taskdef name="schemaupdate"
classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask"
classpathref="class.path"/>
<schemaupdate
properties="hibernate.properties"
quiet="no">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaupdate>
</target>
20.1.7. Schema 校驗
SchemaValidator工具會(huì )比較數據庫現狀是否與映射文檔?匹配?。注意,SchemaValidator 嚴重依賴(lài)于JDBC的metadata API,因此不是對所有的JDBC驅動(dòng)都適用。這一工具在測試的時(shí)候特別有用。
java -cp hibernate_classpaths org.hibernate.tool.hbm2ddl.SchemaValidator options mapping_files
表 20.5. SchemaValidator命令行參數
選項 描述
--naming=eg.MyNamingStrategy 選擇一個(gè)命名策略 (NamingStrategy)
--properties=hibernate.properties 從文件中讀取數據庫屬性
--config=hibernate.cfg.xml 指定一個(gè).cfg.xml文件
你可以在你的應用程序中嵌入SchemaValidator:
Configuration cfg = ....;
new SchemaValidator(cfg).validate();
20.1.8. 使用Ant進(jìn)行schema校驗
你可以在A(yíng)nt腳本中調用SchemaValidator:
<target name="schemavalidate">
<taskdef name="schemavalidator"
classname="org.hibernate.tool.hbm2ddl.SchemaValidatorTask"
classpathref="class.path"/>
<schemavalidator
properties="hibernate.properties">
<fileset dir="src">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaupdate>
</target>
第 21 章 示例:父子關(guān)系(Parent Child Relationships)
剛剛接觸Hibernate的人大多是從父子關(guān)系(parent / child type relationship)的建模入手的。父子關(guān)系的建模有兩種方法。由于種種原因,最方便的方法是把Parent和Child都建模成實(shí)體類(lèi),并創(chuàng )建一個(gè)從Parent指向Child的<one-to-many>關(guān)聯(lián),對新手來(lái)說(shuō)尤其如此。還有一種方法,就是將Child聲明為一個(gè)<composite-element>(組合元素)。 事實(shí)上在Hibernate中one to many關(guān)聯(lián)的默認語(yǔ)義遠沒(méi)有composite element貼近parent / child關(guān)系的通常語(yǔ)義。下面我們會(huì )闡述如何使用帶有級聯(lián)的雙向一對多關(guān)聯(lián)(bidirectional one to many association with cascades)去建立有效、優(yōu)美的parent / child關(guān)系。這一點(diǎn)也不難!
21.1. 關(guān)于collections需要注意的一點(diǎn)
Hibernate collections被當作其所屬實(shí)體而不是其包含實(shí)體的一個(gè)邏輯部分。這非常重要!它主要體現為以下幾點(diǎn):
? 當刪除或增加collection中對象的時(shí)候,collection所屬者的版本值會(huì )遞增。
? 如果一個(gè)從collection中移除的對象是一個(gè)值類(lèi)型(value type)的實(shí)例,比如composite element,那么這個(gè)對象的持久化狀態(tài)將會(huì )終止,其在數據庫中對應的記錄會(huì )被刪除。同樣的,向collection增加一個(gè)value type的實(shí)例將會(huì )使之立即被持久化。
? 另一方面,如果從一對多或多對多關(guān)聯(lián)的collection中移除一個(gè)實(shí)體,在缺省情況下這個(gè)對象并不會(huì )被刪除。這個(gè)行為是完全合乎邏輯的--改變一個(gè)實(shí)體的內部狀態(tài)不應該使與它關(guān)聯(lián)的實(shí)體消失掉!同樣的,向collection增加一個(gè)實(shí)體不會(huì )使之被持久化。
實(shí)際上,向Collection增加一個(gè)實(shí)體的缺省動(dòng)作只是在兩個(gè)實(shí)體之間創(chuàng )建一個(gè)連接而已,同樣移除的時(shí)候也只是刪除連接。這種處理對于所有的情況都是合適的。對于父子關(guān)系則是完全不適合的,在這種關(guān)系下,子對象的生存綁定于父對象的生存周期。
21.2. 雙向的一對多關(guān)系(Bidirectional one-to-many)
假設我們要實(shí)現一個(gè)簡(jiǎn)單的從Parent到Child的<one-to-many>關(guān)聯(lián)。
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
如果我們運行下面的代碼
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();
Hibernate會(huì )產(chǎn)生兩條SQL語(yǔ)句:
? 一條INSERT語(yǔ)句,為c創(chuàng )建一條記錄
? 一條UPDATE語(yǔ)句,創(chuàng )建從p到c的連接
這樣做不僅效率低,而且違反了列parent_id非空的限制。我們可以通過(guò)在集合類(lèi)映射上指定not-null="true"來(lái)解決違反非空約束的問(wèn)題:
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
然而,這并非是推薦的解決方法。
這種現象的根本原因是從p到c的連接(外鍵parent_id)沒(méi)有被當作Child對象狀態(tài)的一部分,因而沒(méi)有在INSERT語(yǔ)句中被創(chuàng )建。因此解決的辦法就是把這個(gè)連接添加到Child的映射中。
<many-to-one name="parent" column="parent_id" not-null="true"/>
(我們還需要為類(lèi)Child添加parent屬性)
現在實(shí)體Child在管理連接的狀態(tài),為了使collection不更新連接,我們使用inverse屬性。
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
下面的代碼是用來(lái)添加一個(gè)新的Child
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
現在,只會(huì )有一條INSERT語(yǔ)句被執行!
為了讓事情變得井井有條,可以為Parent加一個(gè)addChild()方法。
public void addChild(Child c) {
c.setParent(this);
children.add(c);
}
現在,添加Child的代碼就是這樣
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();
21.3. 級聯(lián)生命周期(Cascading lifecycle)
需要顯式調用save()仍然很麻煩,我們可以用級聯(lián)來(lái)解決這個(gè)問(wèn)題。
<set name="children" inverse="true" cascade="all">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
這樣上面的代碼可以簡(jiǎn)化為:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();
同樣的,保存或刪除Parent對象的時(shí)候并不需要遍歷其子對象。 下面的代碼會(huì )刪除對象p及其所有子對象對應的數據庫記錄。
Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();
然而,這段代碼
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();
不會(huì )從數據庫刪除c;它只會(huì )刪除與p之間的連接(并且會(huì )導致違反NOT NULL約束,在這個(gè)例子中)。你需要顯式調用delete()來(lái)刪除Child。
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
在我們的例子中,如果沒(méi)有父對象,子對象就不應該存在,如果將子對象從collection中移除,實(shí)際上我們是想刪除它。要實(shí)現這種要求,就必須使用cascade="all-delete-orphan"。
<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
注意:即使在collection一方的映射中指定inverse="true",級聯(lián)仍然是通過(guò)遍歷collection中的元素來(lái)處理的。如果你想要通過(guò)級聯(lián)進(jìn)行子對象的插入、刪除、更新操作,就必須把它加到collection中,只調用setParent()是不夠的。
21.4. 級聯(lián)與未保存值(Cascades and unsaved-value)
假設我們從Session中裝入了一個(gè)Parent對象,用戶(hù)界面對其進(jìn)行了修改,然后希望在一個(gè)新的Session里面調用update()來(lái)保存這些修改。對象Parent包含了子對象的集合,由于打開(kāi)了級聯(lián)更新,Hibernate需要知道哪些Child對象是新實(shí)例化的,哪些代表數據庫中已經(jīng)存在的記錄。我們假設Parent和Child對象的標識屬性都是自動(dòng)生成的,類(lèi)型為java.lang.Long。Hibernate會(huì )使用標識屬性的值,和version 或 timestamp 屬性,來(lái)判斷哪些子對象是新的。(參見(jiàn)第 10.7 節 ?自動(dòng)狀態(tài)檢測?.) 在 Hibernate3 中,顯式指定unsaved-value不再是必須的了。
下面的代碼會(huì )更新parent和child對象,并且插入newChild對象。
//parent and child were both loaded in a previous session
parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();
Well, that‘s all very well for the case of a generated identifier, but what about assigned identifiers and composite identifiers? This is more difficult, since Hibernate can‘t use the identifier property to distinguish between a newly instantiated object (with an identifier assigned by the user) and an object loaded in a previous session. In this case, Hibernate will either use the timestamp or version property, or will actually query the second-level cache or, worst case, the database, to see if the row exists.
這對于自動(dòng)生成標識的情況是非常好的,但是自分配的標識和復合標識怎么辦呢?這是有點(diǎn)麻煩,因為Hibernate沒(méi)有辦法區分新實(shí)例化的對象(標識被用戶(hù)指定了)和前一個(gè)Session裝入的對象。在這種情況下,Hibernate會(huì )使用timestamp或version屬性,或者查詢(xún)第二級緩存,或者最壞的情況,查詢(xún)數據庫,來(lái)確認是否此行存在。
21.5. 結論
這里有不少東西需要融會(huì )貫通,可能會(huì )讓新手感到迷惑。但是在實(shí)踐中它們都工作地非常好。大部分Hibernate應用程序都會(huì )經(jīng)常用到父子對象模式。
在第一段中我們曾經(jīng)提到另一個(gè)方案。上面的這些問(wèn)題都不會(huì )出現在<composite-element>映射中,它準確地表達了父子關(guān)系的語(yǔ)義。很不幸復合元素還有兩個(gè)重大限制:復合元素不能擁有collections,并且,除了用于惟一的父對象外,它們不能再作為其它任何實(shí)體的子對象。
第 22 章 示例:Weblog 應用程序
22.1. 持久化類(lèi)
下面的持久化類(lèi)表示一個(gè)weblog和在其中張貼的一個(gè)貼子。他們是標準的父/子關(guān)系模型,但是我們會(huì )用一個(gè)有序包(ordered bag)而非集合(set)。
package eg;
private Long _id;
private String _name;
private List _items;
return _id;
}
public List getItems() {
return _items;
}
public String getName() {
return _name;
}
public void setId(Long long1) {
_id = long1;
}
public void setItems(List list) {
_items = list;
}
public void setName(String string) {
_name = string;
}
}
package eg;
import java.util.Calendar;
private Long _id;
private Calendar _datetime;
private String _text;
private String _title;
private Blog _blog;
return _blog;
}
public Calendar getDatetime() {
return _datetime;
}
public Long getId() {
return _id;
}
public String getText() {
return _text;
}
public String getTitle() {
return _title;
}
public void setBlog(Blog blog) {
_blog = blog;
}
public void setDatetime(Calendar calendar) {
_datetime = calendar;
}
public void setId(Long long1) {
_id = long1;
}
public void setText(String string) {
_text = string;
}
public void setTitle(String string) {
_title = string;
}
}
22.2. Hibernate 映射
下列的XML映射應該是很直白的。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"
聯(lián)系客服