Updated against 3.1.1 English version

git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@9087 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Xiaogang Cao 2006-01-18 13:42:45 +00:00
parent 7c307eb39a
commit 0d69a1e670
22 changed files with 2156 additions and 687 deletions

View File

@ -2,7 +2,6 @@
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN" <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN"
"../support/docbook-dtd/docbookx.dtd" "../support/docbook-dtd/docbookx.dtd"
[ [
<!ENTITY quickstart SYSTEM "modules/quickstart.xml">
<!ENTITY tutorial SYSTEM "modules/tutorial.xml"> <!ENTITY tutorial SYSTEM "modules/tutorial.xml">
<!ENTITY architecture SYSTEM "modules/architecture.xml"> <!ENTITY architecture SYSTEM "modules/architecture.xml">
<!ENTITY configuration SYSTEM "modules/configuration.xml"> <!ENTITY configuration SYSTEM "modules/configuration.xml">
@ -36,7 +35,7 @@
<title>HIBERNATE - 符合Java习惯的关系数据库持久化</title> <title>HIBERNATE - 符合Java习惯的关系数据库持久化</title>
<subtitle>Hibernate参考文档</subtitle> <subtitle>Hibernate参考文档</subtitle>
<releaseinfo>3.0.4</releaseinfo> <releaseinfo>3.1.1</releaseinfo>
</bookinfo> </bookinfo>
<toc/> <toc/>
@ -76,13 +75,7 @@
<orderedlist> <orderedlist>
<listitem> <listitem>
<para> <para>
阅读这个30分钟就可以结束的<xref linkend="quickstart"/>它使用Tomcat。 阅读<xref linkend="tutorial"/>,这是一篇包含详细的逐步指导的指南。本指南的源代码包含在发行包中,你可以在<literal>doc/reference/tutorial/</literal>目录下找到。 </para>
</para>
</listitem>
<listitem>
<para>
阅读<xref linkend="tutorial"/>,这是一篇较长的指南,包含详细的逐步指导。
</para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
@ -169,7 +162,7 @@
<row> <row>
<entry>#1</entry> <entry>#1</entry>
<entry>Quickstart with Tomcat</entry> <entry>Quickstart with Tomcat</entry>
<entry>在Tomcat中快速上手</entry> <entry>在Tomcat中快速上手(3.1版本中取消)</entry>
<entry>曹晓钢</entry> <entry>曹晓钢</entry>
<entry>zoujm</entry> <entry>zoujm</entry>
</row> </row>
@ -384,6 +377,13 @@
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>致谢</term>
<listitem>
<para>还有一些朋友给我们发来了勘误在此致谢Kurapica 。
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</sect1> </sect1>
@ -402,8 +402,6 @@
</preface> </preface>
&quickstart;
&tutorial; &tutorial;
&architecture; &architecture;

View File

@ -235,6 +235,44 @@
请注意Hibernate对JCA的支持仍处于实验性质。 请注意Hibernate对JCA的支持仍处于实验性质。
</para> </para>
</sect1> </sect1>
<sect1 id="architecture-current-session" revision="1">
<title>上下文范围内相关的Session</title>
<para>
使用Hibernate的大多数应用需要某种形式的“上下文范围内有效的”session,特定的session仅在特定的上下文中生效并且自始至终一直生效。然而对不同类型的应用程序而言要为这种“上下文”下一个定义通常是困难的不同的上下文对“当前”这个概念的定义范围也有所不同。在3.0之前使用Hibernate的程序要么采用自行编写的基于<literal>ThreadLocal</literal>的上下文session,要么采用<literal>HibernateUtil</literal>这样的辅助类要么采用第三方框架比如Spring或Pico),它们提供基于代理(proxy)或者拦截器(interception)的上下文session。
</para>
<para>
从3.0.1开始Hibernate增加了<literal>SessionFactory.getCurrentSession()</literal>方法。一开始,它假定是和<literal>JTA</literal>事务联用的,<literal>JTA</literal>事务定义了当前session的范围和上下文(scope and context)。Hibernate开发团队理解到因为有好几个独立的<literal>JTA TransactionManager</literal>实现稳定可用,不论是否被部署到一个<literal>J2EE</literal>容器中,大多数(假若不是所有的)应用程序都应该采用<literal>JTA</literal>事务管理。基于这一点,采用<literal>JTA</literal>的上下文相关session可以满足你一切需要。
</para>
<para>
更好的是从3.1开始,<literal>SessionFactory.getCurrentSession()</literal>的后台实现是可拔插的。因此,我们引入了新的扩展接口(<literal>org.hibernate.context.CurrentSessionContext</literal>)和新的配置参数(<literal>hibernate.current_session_context_class</literal>)以便对什么是“当前session”的范围和上下文(scope and context)的定义进行拔插。
</para>
<para>
请参阅<literal>org.hibernate.context.CurrentSessionContext</literal>接口的Javadoc,那里有关于它的契约的详细讨论。它定义了一个方法,<literal>currentSession()</literal>特定的实现用它来负责跟踪当前的上下文session。Hibernate内置了此接口的两种实现。
</para>
<itemizedlist>
<listitem>
<para>
<literal>org.hibernate.context.JTASessionContext</literal> - 当前session根据<literal>JTA</literal>来跟踪和界定。这和以前的仅支持JTA的方法是完全一样的。详情请参阅Javadoc。
</para>
</listitem>
<listitem>
<para>
<literal>org.hibernate.context.ThreadLocalSessionContext</literal> - 当前session通过当前执行的线程来跟踪和界定。详情也请参阅Javadoc。
</para>
</listitem>
</itemizedlist>
<para>
这两种实现都提供了“每数据库事务对应一个session”的编程模型也称作<emphasis>每次请求一个session</emphasis>。Hibernate session的起始和终结由数据库事务的生存来控制。假若你采用自行编写代码来管理事务比如在纯粹的J2SE,或者JTA/UserTransaction/BMT建议使用Hibernate <literal>Transaction</literal> API来把底层事务实现从你的代码中隐藏掉。如果你在支持CMT的EJB容器中执行事务边界是声明式定义的你不需要在代码中进行任何事务或session管理操作。请参阅<xref linkend="transactions"/>一节来阅读更多的内容和示例代码。
</para>
<para>
<literal>hibernate.current_session_context_class</literal>配置参数定义了应该采用哪个<literal>org.hibernate.context.CurrentSessionContext</literal>实现。注意,为了向下兼容,如果未配置此参数,但是存在<literal>org.hibernate.transaction.TransactionManagerLookup</literal>的配置Hibernate会采用<literal>org.hibernate.context.JTASessionContext</literal>。一般而言,此参数的值是要使用的实现类的全名,但那两个内置的实现可以使用简写值,它们是"jta"和"thread"。
</para>
</sect1>
</chapter> </chapter>

View File

@ -277,7 +277,7 @@ create table Address ( addressId bigint not null primary key )
<sect1 id="assoc-bidirectional" revision="1"> <sect1 id="assoc-bidirectional" revision="1">
<title>双向关联Bidirectional associations</title> <title>双向关联Bidirectional associations</title>
<sect2 id="assoc-bidirectional-m21"> <sect2 id="assoc-bidirectional-m21" revision="2">
<title>一对多one to many) / 多对一many to one</title> <title>一对多one to many) / 多对一many to one</title>
<para> <para>
@ -308,6 +308,34 @@ create table Person ( personId bigint not null primary key, addressId bigint not
create table Address ( addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key )
]]></programlisting> ]]></programlisting>
<para>
如果你使用<literal>List</literal>(或者其他有序集合类),你需要设置外键对应的<literal>key</literal>列为 <literal>not null</literal>,让Hibernate来从集合端管理关联维护每个元素的索引通过设置<literal>update="false"</literal> and <literal>insert="false"</literal>来对另一端反向操作)。
</para>
<programlisting><![CDATA[<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class>]]></programlisting>
<para>
假若集合映射的<literal>&lt;key&gt;</literal>元素对应的底层外键字段是<literal>NOT NULL</literal>那么为这一key元素定义<literal>not-null="true"</literal>是很重要的。不要仅仅为可能的嵌套<literal>&lt;column&gt;</literal>元素定义<literal>not-null="true"</literal><literal>&lt;key&gt;</literal>元素也是需要的。
</para>
</sect2> </sect2>
<sect2 id="assoc-bidirectional-121"> <sect2 id="assoc-bidirectional-121">
@ -443,7 +471,7 @@ create table Address ( addressId bigint not null primary key )
inverse="true"> inverse="true">
<key column="addressId" <key column="addressId"
unique="true"/> unique="true"/>
<many-to-one name="address" <many-to-one name="person"
column="personId" column="personId"
not-null="true" not-null="true"
unique="true"/> unique="true"/>
@ -457,7 +485,7 @@ create table Address ( addressId bigint not null primary key )
</sect2> </sect2>
<sect2 id="assoc-bidirectional-join-m2m"> <sect2 id="assoc-bidirectional-join-m2m" revision="1">
<title>多对多many to many</title> <title>多对多many to many</title>
<para> <para>
@ -468,7 +496,7 @@ create table Address ( addressId bigint not null primary key )
<id name="id" column="personId"> <id name="id" column="personId">
<generator class="native"/> <generator class="native"/>
</id> </id>
<set name="addresses"> <set name="addresses" table="PersonAddress">
<key column="personId"/> <key column="personId"/>
<many-to-many column="addressId" <many-to-many column="addressId"
class="Address"/> class="Address"/>
@ -479,7 +507,7 @@ create table Address ( addressId bigint not null primary key )
<id name="id" column="addressId"> <id name="id" column="addressId">
<generator class="native"/> <generator class="native"/>
</id> </id>
<set name="people" inverse="true"> <set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/> <key column="addressId"/>
<many-to-many column="personId" <many-to-many column="personId"
class="Person"/> class="Person"/>
@ -495,6 +523,56 @@ create table Address ( addressId bigint not null primary key )
</sect2> </sect2>
</sect1> </sect1>
<sect1 id="assoc-complex">
<title>更复杂的关联映射</title>
<para>
更复杂的关联连接<emphasis>极为</emphasis>罕见。
通过在映射文档中嵌入SQL片断Hibernate也可以处理更为复杂的情况。比如假若包含历史帐户数据的表定义了<literal>accountNumber</literal>, <literal>effectiveEndDate</literal><literal>effectiveStartDate</literal>字段,按照下面映射:
</para>
<programlisting><![CDATA[<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"/>]]></programlisting>
<para>
那么我们可以对<emphasis>目前(current)</emphasis>实例(其<literal>effectiveEndDate</literal>为null)使用这样的关联映射:
</para>
<programlisting><![CDATA[<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula>'1'</formula>
</many-to-one>]]></programlisting>
<para>
更复杂的例子,假想<literal>Employee</literal><literal>Organization</literal>之间的关联是通过一个<literal>Employment</literal>中间表维护的,而中间表中填充了很多历史雇员数据。那“雇员的<emphasis>最新</emphasis>雇主”这个关联(最新雇主就是<literal>startDate</literal>最后的那个)可以这样映射:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
使用这一功能时可以充满创意但通常更加实用的是用HQL或条件查询来处理这些情形。
</para>
</sect1>
</chapter> </chapter>

View File

@ -229,8 +229,6 @@
<area id="class19" coords="20 55"/> <area id="class19" coords="20 55"/>
<area id="class20" coords="21 55"/> <area id="class20" coords="21 55"/>
<area id="class21" coords="22 55"/> <area id="class21" coords="22 55"/>
<area id="class22" coords="23 55"/>
<area id="class23" coords="24 55"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<class <programlisting><![CDATA[<class
name="ClassName" name="ClassName"
@ -254,7 +252,6 @@
rowid="rowid" rowid="rowid"
subselect="SQL expression" subselect="SQL expression"
abstract="true|false" abstract="true|false"
entity-name="EntityName"
node="element-name" node="element-name"
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
@ -359,7 +356,7 @@
</callout> </callout>
<callout arearefs="class17"> <callout arearefs="class17">
<para> <para>
<literal>entity-name</literal> (可选): Hibernate3允许一个类进行多次映射 <literal>entity-name</literal> (可选,默认为类名): Hibernate3允许一个类进行多次映射
默认情况是映射到不同的表并且允许使用Maps或XML代替Java层次的实体映射 默认情况是映射到不同的表并且允许使用Maps或XML代替Java层次的实体映射
(也就是实现动态领域模型,不用写持久化类-译注)。 (也就是实现动态领域模型,不用写持久化类-译注)。
更多信息请看<xref linkend="persistent-classes-dynamicmodels"/> and <xref linkend="xml"/> 更多信息请看<xref linkend="persistent-classes-dynamicmodels"/> and <xref linkend="xml"/>
@ -391,11 +388,6 @@
hierarchies中标识抽象超类。 hierarchies中标识抽象超类。
</para> </para>
</callout> </callout>
<callout arearefs="class22">
<para>
<literal>entity-name</literal> (可选, 默认为类名): 显式指定实体名
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -506,7 +498,7 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-id" revision="3"> <sect2 id="mapping-declaration-id" revision="4">
<title>id</title> <title>id</title>
<para> <para>
@ -570,8 +562,7 @@
</para> </para>
<para> <para>
<literal>unsaved-value</literal> 属性很重要!如果你的类的标识属性不是默认为 <literal>unsaved-value</literal> 属性在Hibernate3中几乎不再需要。
正常的Java默认值null或零你应该指定正确的默认值。
</para> </para>
<para> <para>
@ -808,13 +799,13 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-compositeid" revision="2"> <sect2 id="mapping-declaration-compositeid" revision="3">
<title>composite-id</title> <title>composite-id</title>
<programlisting><![CDATA[<composite-id <programlisting><![CDATA[<composite-id
name="propertyName" name="propertyName"
class="ClassName" class="ClassName"
unsaved-value="undefined|any|none" mapped="true|false"
access="field|property|ClassName" access="field|property|ClassName"
node="element-name|." node="element-name|."
> >
@ -824,12 +815,6 @@
...... ......
</composite-id>]]></programlisting> </composite-id>]]></programlisting>
<para>
For a table with a composite key, you may map multiple properties of the class
as identifier properties. The <literal>&lt;composite-id&gt;</literal> element
accepts <literal>&lt;key-property&gt;</literal> property mappings and
<literal>&lt;key-many-to-one&gt;</literal> mappings as child elements.
</para>
<para> <para>
如果表使用联合主键,你可以映射类的多个属性为标识符属性。 如果表使用联合主键,你可以映射类的多个属性为标识符属性。
<literal>&lt;composite-id&gt;</literal>元素接受<literal>&lt;key-property&gt;</literal> <literal>&lt;composite-id&gt;</literal>元素接受<literal>&lt;key-property&gt;</literal>
@ -849,31 +834,71 @@
<para> <para>
不幸的是,这种组合关键字的方法意味着一个持久化类是它自己的标识。除了对象自己之外, 不幸的是,这种组合关键字的方法意味着一个持久化类是它自己的标识。除了对象自己之外,
没有什么方便的“把手”可用。你必须自己初始化持久化类的实例,在使用组合关键字<literal>load()</literal> 没有什么方便的“把手”可用。你必须初始化持久化类的实例,填充它的标识符属性,再<literal>load()</literal>
持久化状态之前,必须填充他的联合属性。我们会在<xref linkend="components-compositeid"/>章中说明一种 组合关键字关联的持久状态。我们把这种方法称为<emphasis>embedded嵌入式</emphasis>的组合标识符,在重要的应用中不鼓励使用这种用法。
更加便捷的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效: </para>
<para>
第二种方法我们称为<emphasis>mapped(映射式)</emphasis>组合标识符 (mapped composite identifier),<literal>&lt;composite-id&gt;</literal>元素中列出的标识属性不但在持久化类出现,还形成一个独立的标识符类。
</para>
<programlisting><![CDATA[<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>]]></programlisting>
<para>
在这个例子中,组合标识符类<literal>MedicareId</literal>和实体类都含有<literal>medicareNumber</literal><literal>dependent</literal>属性。标识符类必须重载<literal>equals()</literal><literal>hashCode()</literal>并且实现<literal>Serializable</literal>接口。这种方法的缺点是出现了明显的代码重复。
</para>
<para>
下面列出的属性是用来指定一个映射式组合标识符的:
</para> </para>
<itemizedlist spacing="compact"> <itemizedlist spacing="compact">
<listitem> <listitem>
<para> <para>
<literal>name</literal> (可选):一个组件类型,持有复合标识(参见下一节)。 <literal>mapped</literal> (可选, 默认为<literal>false</literal>):
指明使用一个映射式组合标识符,其包含的属性映射同时在实体类和组合标识符类中出现。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<literal>class</literal> (可选 - 默认为通过反射(reflection)得到的属性类型) : <literal>class</literal> (可选,但对映射式组合标识符必须指定):
作为联合标识的组件类名(参见下一节)。 作为组合标识符类使用的类名.
</para>
</listitem>
<listitem>
<para>
<literal>unsaved-value</literal> (可选 - 默认为 <literal>undefined</literal>):
如果设置为<literal>any</literal>就表示瞬时transient实例应该被重新初始化或者如果
设置为<literal>none</literal>,则表示该实例是脱管对象。最好在所有的情况下都保持默认的值。
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para>
<xref linkend="components-compositeid"/>一节中,我们会描述第三种方式,那就是把组合标识符实现为一个组件(component)类,这是更方便的方法。下面的属性仅对这第三种方法有效:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>name</literal> (可选,但对这种方法而言必须): 包含此组件标识符的组件类型的名字 (参阅第9章).
</para>
</listitem>
<listitem>
<para>
<literal>access</literal> (可选 - 默认为<literal>property</literal>):
Hibernate应该使用的访问此属性值的策略
</para>
</listitem>
<listitem>
<para>
<literal>class</literal> (可选 - 默认会用反射来自动判定属性类型
): 用来作为组合标识符的组件类的类名(参阅下一节)
</para>
</listitem>
</itemizedlist>
<para>
这第三种方式,被称为<emphasis>identifier component(标识符组件)</emphasis>是我们对几乎所有应用都推荐使用的方式。
</para>
</sect2> </sect2>
@ -958,7 +983,7 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-version" revision="1"> <sect2 id="mapping-declaration-version" revision="4">
<title>版本version(可选)</title> <title>版本version(可选)</title>
<para> <para>
@ -973,6 +998,8 @@
<area id="version3" coords="4 70"/> <area id="version3" coords="4 70"/>
<area id="version4" coords="5 70"/> <area id="version4" coords="5 70"/>
<area id="version5" coords="6 70"/> <area id="version5" coords="6 70"/>
<area id="version6" coords="7 70"/>
<area id="version7" coords="8 70"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<version <programlisting><![CDATA[<version
column="version_column" column="version_column"
@ -980,6 +1007,8 @@
type="typename" type="typename"
access="field|property|ClassName" access="field|property|ClassName"
unsaved-value="null|negative|undefined" unsaved-value="null|negative|undefined"
generated="never|always"
insert="true|false"
node="element-name|@attribute-name|element/@attribute|." node="element-name|@attribute-name|element/@attribute|."
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
@ -1012,6 +1041,18 @@
<literal>undefined</literal>指明使用标识属性值进行判断。) <literal>undefined</literal>指明使用标识属性值进行判断。)
</para> </para>
</callout> </callout>
<callout arearefs="version6">
<para>
<literal>generated</literal> (可选 - 默认是 <literal>never</literal>):
表明此版本属性值是否实际上是由数据库生成的。请参阅<xref linkend="mapping-generated">generated properties</xref>部分的讨论。
</para>
</callout>
<callout arearefs="version7">
<para>
<literal>insert</literal> (可选 - 默认是 <literal>true</literal>):
表明此版本列应该包含在SQL插入语句中。只有当数据库字段有默认值<literal>0</literal>的时候,才可以设置为<literal>false</literal>
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -1030,7 +1071,7 @@
</para> </para>
</sect2> </sect2>
<sect2 id="mapping-declaration-timestamp"> <sect2 id="mapping-declaration-timestamp" revision="3">
<title>timestamp (optional)</title> <title>timestamp (optional)</title>
<para> <para>
@ -1045,12 +1086,16 @@
<area id="timestamp2" coords="3 70" /> <area id="timestamp2" coords="3 70" />
<area id="timestamp3" coords="4 70" /> <area id="timestamp3" coords="4 70" />
<area id="timestamp4" coords="5 70" /> <area id="timestamp4" coords="5 70" />
<area id="timestamp5" coords="6 70" />
<area id="timestamp6" coords="7 70" />
</areaspec> </areaspec>
<programlisting><![CDATA[<timestamp <programlisting><![CDATA[<timestamp
column="timestamp_column" column="timestamp_column"
name="propertyName" name="propertyName"
access="field|property|ClassName" access="field|property|ClassName"
unsaved-value="null|undefined" unsaved-value="null|undefined"
source="vm|db"
generated="never|always"
node="element-name|@attribute-name|element/@attribute|." node="element-name|@attribute-name|element/@attribute|."
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
@ -1079,16 +1124,28 @@
指明使用标识属性值进行这种判断。) 指明使用标识属性值进行这种判断。)
</para> </para>
</callout> </callout>
<callout arearefs="timestamp5">
<para>
<literal>source</literal> (可选 - 默认是 <literal>vm</literal>):
Hibernate如何才能获取到时间戳的值呢从数据库还是当前JVM从数据库获取会带来一些负担因为Hibernate必须访问数据库来获得“下一个值”但是在集群环境中会更安全些。还要注意并不是所有的<literal>Dialect方言</literal>都支持获得数据库的当前时间戳的,而支持的数据库中又有一部分因为精度不足,用于锁定是不安全的例如Oracle 8
</para>
</callout>
<callout arearefs="timestamp6">
<para>
<literal>generated</literal> (可选 - 默认是 <literal>never</literal>):
指出时间戳值是否实际上是由数据库生成的.请参阅<xref linkend="mapping-generated">generated properties</xref>的讨论。
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
<para> <para>
注意,<literal>&lt;timestamp&gt;</literal><literal>&lt;version type="timestamp"&gt;</literal>是等价的。 注意,<literal>&lt;timestamp&gt;</literal><literal>&lt;version type="timestamp"&gt;</literal>是等价的。并且<literal>&lt;timestamp use-db="true"&gt;</literal><literal>&lt;version type="dbtimestamp"&gt;</literal>是等价的。
</para> </para>
</sect2> </sect2>
<sect2 id="mapping-declaration-property" revision="2"> <sect2 id="mapping-declaration-property" revision="4">
<title>property</title> <title>property</title>
<para> <para>
@ -1101,8 +1158,8 @@
<area id="property2" coords="3 70"/> <area id="property2" coords="3 70"/>
<area id="property3" coords="4 70"/> <area id="property3" coords="4 70"/>
<areaset id="property4-5" coords=""> <areaset id="property4-5" coords="">
<area id="property4" coords='5 70'/> <area id="property4" coords='5 70'/>
<area id="property5" coords='6 70'/> <area id="property5" coords='6 70'/>
</areaset> </areaset>
<area id="property6" coords="7 70"/> <area id="property6" coords="7 70"/>
<area id="property7" coords="8 70"/> <area id="property7" coords="8 70"/>
@ -1110,6 +1167,7 @@
<area id="property9" coords="10 70"/> <area id="property9" coords="10 70"/>
<area id="property10" coords="11 70"/> <area id="property10" coords="11 70"/>
<area id="property11" coords="12 70"/> <area id="property11" coords="12 70"/>
<area id="property12" coords="13 70"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<property <programlisting><![CDATA[<property
name="propertyName" name="propertyName"
@ -1123,7 +1181,14 @@
unique="true|false" unique="true|false"
not-null="true|false" not-null="true|false"
optimistic-lock="true|false" optimistic-lock="true|false"
generated="never|insert|always"
node="element-name|@attribute-name|element/@attribute|." node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
<callout arearefs="property1"> <callout arearefs="property1">
@ -1188,6 +1253,12 @@
换句话说它决定这个属性发生脏数据时版本version的值是否增长。 换句话说它决定这个属性发生脏数据时版本version的值是否增长。
</para> </para>
</callout> </callout>
<callout arearefs="property12">
<para>
<literal>generated</literal> (可选 - 默认为 <literal>never</literal>):
表明此属性值是否实际上是由数据库生成的。请参阅<xref linkend="mapping-generated">generated properties</xref>的讨论。
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -1257,7 +1328,7 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-manytoone" revision="3"> <sect2 id="mapping-declaration-manytoone" revision="5">
<title>多对一many-to-one</title> <title>多对一many-to-one</title>
<para> <para>
@ -1285,6 +1356,7 @@
<area id="manytoone13" coords="14 70"/> <area id="manytoone13" coords="14 70"/>
<area id="manytoone14" coords="15 70"/> <area id="manytoone14" coords="15 70"/>
<area id="manytoone15" coords="16 70"/> <area id="manytoone15" coords="16 70"/>
<area id="manytoone16" coords="17 70"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<many-to-one <programlisting><![CDATA[<many-to-one
name="propertyName" name="propertyName"
@ -1299,11 +1371,16 @@
unique="true|false" unique="true|false"
not-null="true|false" not-null="true|false"
optimistic-lock="true|false" optimistic-lock="true|false"
lazy="true|proxy|false" lazy="proxy|no-proxy|false"
not-found="ignore|exception" not-found="ignore|exception"
entity-name="EntityName" entity-name="EntityName"
formula="arbitrary SQL expression"
node="element-name|@attribute-name|element/@attribute|." node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false" embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
@ -1377,7 +1454,7 @@
<callout arearefs="manytoone13"> <callout arearefs="manytoone13">
<para> <para>
<literal>lazy</literal> (可选 - 默认为 <literal>proxy</literal>): <literal>lazy</literal> (可选 - 默认为 <literal>proxy</literal>):
默认情况下,单点关联是经过代理的。<literal>lazy="true"</literal>指定此属性应该在实例变量第一次被访问时应该延迟抓取fetche lazily需要运行时字节码的增强 默认情况下,单点关联是经过代理的。<literal>lazy="no-proxy"</literal>指定此属性应该在实例变量第一次被访问时应该延迟抓取fetche lazily需要运行时字节码的增强
<literal>lazy="false"</literal>指定此关联总是被预先抓取。 <literal>lazy="false"</literal>指定此关联总是被预先抓取。
</para> </para>
</callout> </callout>
@ -1390,9 +1467,16 @@
</callout> </callout>
<callout arearefs="manytoone15"> <callout arearefs="manytoone15">
<para> <para>
<literal>entity-name</literal> (optional): 被关联的类的实体名。 <literal>entity-name</literal> (可选): 被关联的类的实体名。
</para> </para>
</callout> </callout>
<callout arearefs="manytoone16">
<para>
<literal>formula</literal> (可选):
SQL表达式用于定义<emphasis>computed计算出的</emphasis>外键值。
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -1403,7 +1487,8 @@
<literal>persist, merge, delete, save-update, evict, replicate, lock, refresh</literal> <literal>persist, merge, delete, save-update, evict, replicate, lock, refresh</literal>
以及特别的值<literal>delete-orphan</literal><literal>all</literal>,并且可以用逗号分隔符 以及特别的值<literal>delete-orphan</literal><literal>all</literal>,并且可以用逗号分隔符
来合并这些操作,例如,<literal>cascade="persist,merge,evict"</literal> 来合并这些操作,例如,<literal>cascade="persist,merge,evict"</literal>
<literal>cascade="all,delete-orphan"</literal>。更全面的解释请参考<xref linkend="objectstate-transitive"/>. <literal>cascade="all,delete-orphan"</literal>。更全面的解释请参考<xref linkend="objectstate-transitive"/>. 注意,单值关联 (many-to-one 和
one-to-one associations) 不支持删除孤儿orphan delete删除不再被引用的值.
</para> </para>
@ -1439,9 +1524,16 @@
如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为<literal>&lt;properties&gt;</literal>的元素 如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为<literal>&lt;properties&gt;</literal>的元素
里面映射所有关联的属性。 里面映射所有关联的属性。
</para> </para>
<para>
假若被引用的唯一主键是组件的属性,你可以指定属性路径:
</para>
<programlisting><![CDATA[<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>]]></programlisting>
</sect2> </sect2>
<sect2 id="mapping-declaration-onetoone" revision="2"> <sect2 id="mapping-declaration-onetoone" revision="3">
<title>一对一</title> <title>一对一</title>
<para> <para>
@ -1470,10 +1562,12 @@
property-ref="propertyNameFromAssociatedClass" property-ref="propertyNameFromAssociatedClass"
access="field|property|ClassName" access="field|property|ClassName"
formula="any SQL expression" formula="any SQL expression"
lazy="true|proxy|false" lazy="proxy|no-proxy|false"
entity-name="EntityName" entity-name="EntityName"
node="element-name|@attribute-name|element/@attribute|." node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false" embed-xml="true|false"
foreign-key="foreign_key_name"
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
<callout arearefs="onetoone1"> <callout arearefs="onetoone1">
@ -1523,7 +1617,7 @@
<callout arearefs="onetoone9"> <callout arearefs="onetoone9">
<para> <para>
<literal>lazy</literal> (可选 - 默认为 <literal>proxy</literal>): <literal>lazy</literal> (可选 - 默认为 <literal>proxy</literal>):
默认情况下,单点关联是经过代理的。<literal>lazy="true"</literal>指定此属性应该在实例变量第一次被访问时应该延迟抓取fetche lazily需要运行时字节码的增强 默认情况下,单点关联是经过代理的。<literal>lazy="no-proxy"</literal>指定此属性应该在实例变量第一次被访问时应该延迟抓取fetche lazily需要运行时字节码的增强
<literal>lazy="false"</literal>指定此关联总是被预先抓取。<emphasis>注意,如果<literal>constrained="false"</literal>, <literal>lazy="false"</literal>指定此关联总是被预先抓取。<emphasis>注意,如果<literal>constrained="false"</literal>,
不可能使用代理Hibernate会采取预先抓取</emphasis> 不可能使用代理Hibernate会采取预先抓取</emphasis>
</para> </para>
@ -1596,6 +1690,39 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-naturalid">
<title>自然ID(natural-id)</title>
<programlisting><![CDATA[<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id>]]></programlisting>
<para>
我们建议使用代用键键值不具备实际意义作为主键我们仍然应该尝试为所有的实体采用自然的键值作为附加——译者注标示。自然键natural key是一个或多个属性他们必须唯一且非空。如果它还是不可变的那就更理想了。在<literal>&lt;natural-id&gt;</literal>元素中列出自然键的属性。Hibernate会帮你生成必须的唯一键值和非空约束你的映射会更加的明显易懂原文是self-documenting自我注解
</para>
<para>
我们强烈建议你实现<literal>equals()</literal><literal>hashCode()</literal>方法,来比较实体的自然键属性。
</para>
<para>
这一映射不是为了把自然键作为主键而准备的。
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>mutable</literal> (可选, 默认为<literal>false</literal>):
默认情况下,自然标识属性被假定为时不可变的。
</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="mapping-declaration-component" revision="2"> <sect2 id="mapping-declaration-component" revision="2">
<title>组件(component), 动态组件(dynamic-component)</title> <title>组件(component), 动态组件(dynamic-component)</title>
@ -1782,7 +1909,7 @@
</sect2> </sect2>
<sect2 id="mapping-declaration-subclass" revision="3"> <sect2 id="mapping-declaration-subclass" revision="4">
<title>子类(subclass)</title> <title>子类(subclass)</title>
<para> <para>
@ -1804,7 +1931,9 @@
dynamic-update="true|false" dynamic-update="true|false"
dynamic-insert="true|false" dynamic-insert="true|false"
entity-name="EntityName" entity-name="EntityName"
node="element-name"> node="element-name"
extends="SuperclassName">
<property .... /> <property .... />
..... .....
</subclass>]]></programlisting> </subclass>]]></programlisting>
@ -1838,16 +1967,6 @@
<literal>&lt;version&gt;</literal><literal>&lt;id&gt;</literal> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须定义一个唯一的<literal>discriminator-value</literal>。如果没有指定就会使用Java类的全限定名。 <literal>&lt;version&gt;</literal><literal>&lt;id&gt;</literal> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须定义一个唯一的<literal>discriminator-value</literal>。如果没有指定就会使用Java类的全限定名。
</para> </para>
<para>
可以在单独的映射文件中,直接在<literal>hibernate-mapping</literal>下定义<literal>subclass</literal><literal>union-subclass</literal><literal>joined-subclass</literal>映射。这样你只要增加一个新的映射文件就可以继承一棵类继承树。你必须在子类的映射中指定<literal>extends</literal> 属性来指定已映射的超类。注意以前这个特性使得映射文件的顺序变得很重要。从Hibernate3开始当使用extends关键字的时候映射文件的次序便不重要了。而在单一映射文件中依旧需要保持将超类定义在子类之前这样的次序。
</para>
<programlisting><![CDATA[
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>]]></programlisting>
<para> <para>
更多关于继承映射的信息, 参考 <xref linkend="inheritance"/>章节. 更多关于继承映射的信息, 参考 <xref linkend="inheritance"/>章节.
@ -2175,7 +2294,7 @@
</para> </para>
</sect2> </sect2>
<sect2 id="mapping-column" revision="3"> <sect2 id="mapping-column" revision="4">
<title>字段和规则元素column and formula elements</title> <title>字段和规则元素column and formula elements</title>
<para> <para>
任何接受<literal>column</literal>属性的映射元素都可以选择接受<literal>&lt;column&gt;</literal> 子元素。同样的,<literal>formula</literal>也可以替换<literal>&lt;formula&gt;</literal>属性。 任何接受<literal>column</literal>属性的映射元素都可以选择接受<literal>&lt;column&gt;</literal> 子元素。同样的,<literal>formula</literal>也可以替换<literal>&lt;formula&gt;</literal>属性。
@ -2190,7 +2309,8 @@
unique-key="multicolumn_unique_key_name" unique-key="multicolumn_unique_key_name"
index="index_name" index="index_name"
sql-type="sql_type_name" sql-type="sql_type_name"
check="SQL expression"/>]]></programlisting> check="SQL expression"
default="SQL expression"/>]]></programlisting>
<programlisting><![CDATA[<formula>SQL expression</formula>]]></programlisting> <programlisting><![CDATA[<formula>SQL expression</formula>]]></programlisting>
<para> <para>
@ -2354,7 +2474,7 @@
</para> </para>
</sect2> </sect2>
<sect2 id="mapping-types-basictypes" revision="2"> <sect2 id="mapping-types-basictypes" revision="3">
<title>基本值类型</title> <title>基本值类型</title>
<para> <para>
@ -2453,6 +2573,18 @@
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
<literal>imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date,
imm_serializable, imm_binary</literal>
</term>
<listitem>
<para>
一般来说映射类型被假定为是可变的Java类型只有对不可变Java类型Hibernate会采取特定的优化措施应用程序会把这些对象作为不可变对象处理。比如你不应该对作为<literal>imm_timestamp</literal>映射的Date执行<literal>Date.setTime()</literal>。要改变属性的值,并且保存这一改变,应用程序必须对这一属性重新设置一个新的(不一样的)对象。
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</para> </para>
@ -2522,6 +2654,37 @@
</sect1> </sect1>
<sect1 id="mapping-entityname">
<title>多次映射同一个类</title>
<para>
对特定的持久花类,映射多次是允许的。这种情形下,你必须指定<emphasis>entity name</emphasis>来区别不同映射实体的对象实例。(默认情况下,实体名字和类名是相同的。)
Hibernate在操作持久化对象、编写查询条件或者把关联映射到指定实体时允许你指定这个entity name实体名字
</para>
<programlisting><![CDATA[<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>
<class name="Contract" table="ContractHistory"
entity-name="HistoricalContract">
...
<many-to-one name="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class>]]></programlisting>
<para>
注意这里关联是如何用<literal>entity-name</literal>来代替<literal>class</literal>的。
</para>
</sect1>
<sect1 id="mapping-quotedidentifiers"> <sect1 id="mapping-quotedidentifiers">
<title>SQL中引号包围的标识符</title> <title>SQL中引号包围的标识符</title>
<para> <para>
@ -2660,7 +2823,7 @@ public class Cat {
</para> </para>
</sect2> </sect2>
<sect2 id="mapping-annotations"> <sect2 id="mapping-annotations" revision="2">
<title>使用 JDK 5.0 的注解(Annotation)</title> <title>使用 JDK 5.0 的注解(Annotation)</title>
<para> <para>
@ -2682,22 +2845,83 @@ public class Customer implements Serializable {
@Transient @Transient
Integer age; Integer age;
@Dependent @Embedded
private Address homeAddress; private Address homeAddress;
@OneToMany(cascade=CascadeType.ALL, @OneToMany(cascade=CascadeType.ALL)
targetEntity="Order")
@JoinColumn(name="CUSTOMER_ID") @JoinColumn(name="CUSTOMER_ID")
Set orders; Set<Order> orders;
// Getter/setter and business methods // Getter/setter and business methods
}]]></programlisting> }]]></programlisting>
<para> <para>
注意:对 JDK 5.0 注解 (和 JSR-220)支持的工作仍然在进行中,并未完成。 注意:对 JDK 5.0 注解 (和 JSR-220)支持的工作仍然在进行中,并未完成。更多细节请参阅Hibernate Annotations 模块。
</para> </para>
</sect2> </sect2>
</sect1> </sect1>
<sect1 id="mapping-generated" revision="1">
<title>数据库生成属性Generated Properties</title>
<para>
Generated properties指的是其值由数据库生成的属性。一般来说如果对象有任何属性由数据库生成值Hibernate应用程序需要进行<literal>刷新(refresh)</literal>。但如果把属性标明为generated就可以转由Hibernate来负责这个动作。实际上。对定义了generated properties的实体,每当Hibernate执行一条SQL INSERT或者UPDATE语句会立刻执行一条select来获得生成的值。
</para>
<para>
被标明为generated的属性还必须是 non-insertable和 non-updateable的。只有<xref linkend="mapping-declaration-version">versions</xref><xref linkend="mapping-declaration-timestamp">timestamps</xref><xref linkend="mapping-declaration-property">简单属性simple properties</xref>可以被标明为generated。
</para>
<para>
<literal>never</literal> (默认) 标明此属性值不是从数据库中生成。
</para>
<para>
<literal>insert</literal> - 标明此属性值在insert的时候生成但是不会在随后的update时重新生成。比如说创建日期就归属于这类。注意虽然<xref linkend="mapping-declaration-version">version</xref><xref linkend="mapping-declaration-timestamp">timestamp</xref>属性可以被标注为generated但是不适用这个选项...
</para>
<para>
<literal>always</literal> - 标明此属性值在insert和update时都会被生成。
</para>
</sect1>
<sect1 id="mapping-database-object">
<title>辅助数据库对象(Auxiliary Database Objects)</title>
<para>
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 <literal>java.sql.Statement.execute()</literal>
method is valid here (ALTERs, INSERTS, etc). There are essentially two modes for
defining auxiliary database objects...
帮助CREATE和DROP任意数据库对象和Hibernate的schema交互工具组合起来可以提供在Hibernate映射文件中完全定义用户schema的能力。虽然这是为创建和销毁trigger(触发器或stored procedure(存储过程)等特别设计的,实际上任何可以在<literal>java.sql.Statement.execute()</literal>方法中执行的SQL命令都可以在此使用比如ALTER, INSERT等等。本质上有两种模式来定义辅助数据库对象...
</para>
<para>
第一种模式是在映射文件中显式声明CREATE和DROP命令
</para>
<programlisting><![CDATA[<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping>]]></programlisting>
<para>
第二种模式是提供一个类这个类知道如何组织CREATE和DROP命令。这个特别类必须实现<literal>org.hibernate.mapping.AuxiliaryDatabaseObject</literal>接口。
</para>
<programlisting><![CDATA[<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>]]></programlisting>
<para>
还有,这些数据库对象可以特别指定,仅在特定的方言中才使用。
</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect1>
</chapter> </chapter>

View File

@ -31,6 +31,10 @@ session.close();]]></programlisting>
</para> </para>
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting> <programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
<para>
但是,这不是绝对必须的,因为我们可以显式设置<literal>CacheMode</literal>来关闭与二级缓存的交互。
</para>
<sect1 id="batch-inserts"> <sect1 id="batch-inserts">
<title>批量插入Batch inserts</title> <title>批量插入Batch inserts</title>
@ -89,20 +93,57 @@ session.close();]]></programlisting>
</sect1> </sect1>
<sect1 id="batch-direct"> <sect1 id="batch-statelesssession">
<title>大批量更新/删除Bulk update/delete</title> <title>StatelessSession (无状态session)接口</title>
<para>
作为选择Hibernate提供了基于命令的API可以用detached object的形式把数据以流的方法加入到数据库或从数据库输出。<literal>StatelessSession</literal>没有持久化上下文也不提供多少高层的生命周期语义。特别是无状态session不实现第一级cache,也不和第二级缓存或者查询缓存交互。它不实现事务化写也不实现脏数据检查。用stateless session进行的操作甚至不级联到关联实例。stateless session忽略集合类(Collections)。通过stateless session进行的操作不触发Hibernate的事件模型和拦截器。无状态session对数据的混淆现象免疫因为它没有第一级缓存。无状态session是低层的抽象和低层JDBC相当接近。
</para>
<programlisting><![CDATA[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();]]></programlisting>
<para> <para>
注意在上面的例子中,查询返回的<literal>Customer</literal>实例立即被脱管(detach)。它们与任何持久化上下文都没有关系。
</para>
<para>
<literal>StatelessSession</literal> 接口定义的<literal>insert(), update()</literal><literal>delete()</literal>操作是直接的数据库行级别操作,其结果是立刻执行一条<literal>INSERT, UPDATE</literal><literal>DELETE</literal> 语句。因此,它们的语义和<literal>Session</literal> 接口定义的<literal>save(), saveOrUpdate()</literal><literal>delete()</literal> 操作有很大的不同。
</para>
</sect1>
<sect1 id="batch-direct" revision="2">
<title>DML(数据操作语言)风格的操作(DML-style operations)</title>
<para>
hence manipulating (using the SQL <literal>Data Manipulation Language</literal>
(DML) statements: <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>)
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 (<xref linkend="queryhql">HQL</xref>).
就像已经讨论的那样,自动和透明的 对象/关系 映射object/relational mapping关注于管理对象的状态。 就像已经讨论的那样,自动和透明的 对象/关系 映射object/relational mapping关注于管理对象的状态。
这就意味着对象的状态存在于内存,因此直接更新或者删除 (使用 SQL 语句 <literal>UPDATE</literal> 这就意味着对象的状态存在于内存,因此直接操作 (使用 SQL <literal>Data Manipulation Language</literal>(DML,数据操作语言)语句 <literal>INSERT</literal> ,<literal>UPDATE</literal>
<literal>DELETE</literal>) 数据库中的数据将不会影响内存中的对象状态和对象数据。 <literal>DELETE</literal>) 数据库中的数据将不会影响内存中的对象状态和对象数据。
不过Hibernate提供通过Hibernate查询语言<xref linkend="queryhql">HQL</xref>)来执行大批 不过Hibernate提供通过Hibernate查询语言<xref linkend="queryhql">HQL</xref>)来执行大批
量SQL风格的<literal>UPDATE</literal>)和(<literal>DELETE</literal> 语句的方法。 量SQL风格的DML语句的方法。
</para> </para>
<para> <para>
<literal>UPDATE</literal><literal>DELETE</literal>语句的语法为: <literal>UPDATE</literal><literal>DELETE</literal>语句的语法为:
<literal>( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?</literal> <literal>( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?</literal>
有几点说明: 有几点说明:
</para> </para>
@ -114,12 +155,13 @@ session.close();]]></programlisting>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
在FROM子句from-clause中只能有一个类名,并且它<emphasis>不能</emphasis>有别名 在FROM子句from-clause中只能有一个实体名,它可以是别名。如果实体名是别名,那么任何被引用的属性都必须加上此别名的前缀;如果不是别名,那么任何有前缀的属性引用都是非法的。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
不能在大批量HQL语句中使用连接显式或者隐式的都不行。不过在WHERE子句中可以使用子查询。 不能在大批量HQL语句中使用<xref linkend="queryhql-joins-forms">连接(join)</xref>显式或者隐式的都不行。不过在WHERE子句中可以使用子查询。可以在where子句中使用子查询子查询本身可以包含join。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -131,34 +173,36 @@ session.close();]]></programlisting>
<para> <para>
举个例子,使用<literal>Query.executeUpdate()</literal>方法执行一个HQL 举个例子,使用<literal>Query.executeUpdate()</literal>方法执行一个HQL
<literal>UPDATE</literal>语句: <literal>UPDATE</literal>语句(
(方法命名是来源于JDBC's <literal>PreparedStatement.executeUpdate()</literal>):
</para> </para>
<programlisting><![CDATA[Session session = sessionFactory.openSession(); <programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction(); Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer set name = :newName where name = :oldName"; String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate ) // or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
.setString( "newName", newName ) int updatedEntities = s.createQuery( hqlUpdate )
.setString( "oldName", oldName ) .setString( "newName", newName )
.executeUpdate(); .setString( "oldName", oldName )
tx.commit(); .executeUpdate();
session.close();]]></programlisting> tx.commit();
session.close();]]></programlisting>
<para> <para>
执行一个HQL <literal>DELETE</literal>,同样使用 <literal>Query.executeUpdate()</literal> 方法 执行一个HQL <literal>DELETE</literal>,同样使用 <literal>Query.executeUpdate()</literal> 方法:
(此方法是为 那些熟悉JDBC <literal>PreparedStatement.executeUpdate()</literal> 的人们而设定的)
</para> </para>
<programlisting><![CDATA[Session session = sessionFactory.openSession(); <programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction(); Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer where name = :oldName"; String hqlDelete = "delete Customer c where c.name = :oldName";
int deletedEntities = s.createQuery( hqlDelete ) // or String hqlDelete = "delete Customer where name = :oldName";
.setString( "oldName", oldName ) int deletedEntities = s.createQuery( hqlDelete )
.executeUpdate(); .setString( "oldName", oldName )
tx.commit(); .executeUpdate();
session.close();]]></programlisting> tx.commit();
session.close();]]></programlisting>
<para> <para>
<literal>Query.executeUpdate()</literal>方法返回的<literal>整型</literal>值表明了受此操作影响的记录数量。 <literal>Query.executeUpdate()</literal>方法返回的<literal>整型</literal>值表明了受此操作影响的记录数量。
@ -168,9 +212,50 @@ session.close();]]></programlisting>
</para> </para>
<para> <para>
注意上述大批量HQL操作的少数限制会在新版本中得到改进进一步详细信息请参考JIRA里的路线图(roadmap)。 <literal>INSERT</literal>语句的伪码是:
<literal>INSERT INTO EntityName properties_list select_statement</literal>.
要注意的是:
</para> </para>
<itemizedlist spacing="compact">
<listitem>
<para>
只支持INSERT INTO ... SELECT ...形式,不支持INSERT INTO ... VALUES ...形式.
</para>
<para>
properties_list和SQL <literal>INSERT</literal>语句中的<literal>字段定义(column speficiation)</literal>类似。对参与继承树映射的实体而言只有直接定义在给定的类级别的属性才能直接在properties_list中使用。超类的属性不被支持子类的属性无意义。换句话说<literal>INSERT</literal>天生不支持多态。
</para>
</listitem>
<listitem>
<para>
select_statement可以是任何合法的HQL选择查询不过要保证返回类型必须和要插入的类型完全匹配。目前这一检查是在查询编译的时候进行的而不是把它交给数据库。注意在Hibernate<literal>Type</literal>间如果只是<emphasis>等价equivalent</emphasis>而非<emphasis>相等(equal)</emphasis>,会导致问题。定义为<literal>org.hibernate.type.DateType</literal><literal>org.hibernate.type.TimestampType</literal>的两个属性可能会产生类型不匹配错误,虽然数据库级可能不加区分或者可以处理这种转换。
</para>
</listitem>
<listitem>
<para>
对id属性来说,insert语句给你两个选择。你可以明确地在properties_list表中指定id属性这样它的值是从对应的select表达式中获得或者在properties_list中省略它此时使用生成指。后一种选择只有当使用在数据库中生成值的id产生器时才能使用如果是“内存”中计算的类型生成器在解析时会抛出一个异常。注意为了说明这一问题数据库产生值的生成器是<literal>org.hibernate.id.SequenceGenerator</literal>(和它的子类),以及任何<literal>org.hibernate.id.PostInsertIdentifierGenerator</literal>接口的实现。这儿最值得注意的意外是<literal>org.hibernate.id.TableHiLoGenerator</literal>,它不能在此使用,因为它没有得到其值的途径。
</para>
</listitem>
<listitem>
<para>
对映射为<literal>version</literal><literal>timestamp</literal>的属性来说insert语句也给你两个选择你可以在properties_list表中指定此时其值从对应的select表达式中获得或者在properties_list中省略它此时使用在<literal>org.hibernate.type.VersionType</literal> 中定义的<literal>seed value(种子值)</literal>)。
</para>
</listitem>
</itemizedlist>
<para>
执行HQL <literal>INSERT</literal>语句的例子如下:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
</sect1> </sect1>
</chapter> </chapter>

View File

@ -13,13 +13,23 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>对持久类声明标识符属性。</term> <term>对持久类声明标识符属性( identifier properties)</term>
<listitem> <listitem>
<para> <para>
Hibernate中标识符属性是可选的不过有很多原因来说明你应该使用标识符属性。我们建议标识符应该是“人造”的(自动生成,不涉及业务含义)。虽然原生类型从语法上可能更易于使用,但使用<literal>long</literal><literal>java.lang.Long</literal>没有任何区别,。 Hibernate中标识符属性是可选的不过有很多原因来说明你应该使用标识符属性。我们建议标识符应该是“人造”的(自动生成,不涉及业务含义)。
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>使用自然键(natural keys)标识</term>
<listitem>
<para>
对所有的实体都标识出自然键,用<literal>&lt;natural-id&gt;</literal>进行映射。实现<literal>equals()</literal><literal>hashCode()</literal>,在其中用组成自然键的属性进行比较。
</para>
</listitem>
Y00008051221000980 2.789100万
</varlistentry>
<varlistentry> <varlistentry>
<term>为每个持久类写一个映射文件</term> <term>为每个持久类写一个映射文件</term>
<listitem> <listitem>
@ -72,7 +82,13 @@
<term>在性能瓶颈的地方使用硬编码的JDBC</term> <term>在性能瓶颈的地方使用硬编码的JDBC</term>
<listitem> <listitem>
<para> <para>
在对性能要求很严格的一些系统中,一些操作(例如批量更新和批量删除)也许直接使用JDBC会更好但是请先<emphasis>搞清楚</emphasis>这是否是一个瓶颈并且不要想当然认为JDBC一定会更快。如果确实需要直接使用JDBC那么最好打开一个 In performance-critical areas of the system, some kinds of operations might benefit from
direct JDBC. But please, wait until you <emphasis>know</emphasis> something is a bottleneck.
And don't assume that direct JDBC is necessarily faster. If you need to use direct JDBC, it might
be worth opening a Hibernate <literal>Session</literal> and using that JDBC connection. That
way you can still use the same transaction strategy and underlying connection provider.
在系统中对性能要求很严格的一些部分某些操作也许直接使用JDBC会更好。但是请先<emphasis>确认</emphasis>这的确是一个瓶颈并且不要想当然认为JDBC一定会更快。如果确实需要直接使用JDBC那么最好打开一个
Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得connection按照这种办法你仍然可以使用同样的transaction策略和底层的connection provider。 Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得connection按照这种办法你仍然可以使用同样的transaction策略和底层的connection provider。
</para> </para>
</listitem> </listitem>
@ -86,18 +102,18 @@ Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得c
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>在三层结构中,考虑使用 <literal>saveOrUpdate()</literal></term> <term>在三层结构中,考虑使用托管对象detached object</term>
<listitem> <listitem>
<para> <para>
当使用一个servlet / session bean 类型的架构的时候, 你可以把已加载的持久对象在session bean层和servlet / JSP 层之间来回传递。使用新的session来为每个请求服务使用 <literal>Session.update()</literal> 或者<literal>Session.saveOrUpdate()</literal>更新对象的持久状态 当使用一个servlet / session bean 类型的架构的时候, 你可以把已加载的持久对象在session bean层和servlet / JSP 层之间来回传递。使用新的session来为每个请求服务使用 <literal>Session.merge()</literal> 或者<literal>Session.saveOrUpdate()</literal>与数据库同步
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>在两层结构中,考虑断开session.</term> <term>在两层结构中,考虑使用长持久上下文(long persistence contexts).</term>
<listitem> <listitem>
<para> <para>
为了得到最佳的可伸缩性,数据库事务(Database Transaction)应该尽可能的短。但是,程序常常需要实现长时间运行的“应用程序事务(Application Transaction)”,包含一个从用户的观点来看的原子操作。这个应用程序事务可能跨越多次从用户请求到得到反馈的循环。请使用脱管对象(与session脱离的对象),或者在两层结构中把Hibernate Session从JDBC连接中脱离开下次需要用的时候再连接上。绝不要把一个Session用在多个应用程序事务(Application Transaction)中,否则你的数据可能会过期失效。 为了得到最佳的可伸缩性,数据库事务(Database Transaction)应该尽可能的短。但是,程序常常需要实现长时间运行的<emphasis>“应用程序事务(Application Transaction)”</emphasis>,包含一个从用户的观点来看的原子操作。这个应用程序事务可能跨越多次从用户请求到得到反馈的循环。用脱管对象(与session脱离的对象)来实现应用程序事务是常见的。或者,尤其在两层结构中把Hibernate Session从JDBC连接中脱离开下次需要用的时候再连接上。绝不要把一个Session用在多个应用程序事务(Application Transaction)中,否则你的数据可能会过期失效。
</para> </para>
</listitem> </listitem>
@ -106,7 +122,7 @@ Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得c
<term>不要把异常看成可恢复的</term> <term>不要把异常看成可恢复的</term>
<listitem> <listitem>
<para> <para>
这一点甚至比“最佳实践”还要重要,这是“必备常识”。当异常发生的时候,必须要回滚 <literal>Transaction</literal> ,关闭<literal>Session</literal>。如果你不这样做的话Hibernate无法保证内存状态精确的反应持久状态。尤其不要使用<literal>Session.load()</literal>来判断一个给定标识符的对象实例在数据库中是否存在,应该使用<literal>find()</literal> 这一点甚至比“最佳实践”还要重要,这是“必备常识”。当异常发生的时候,必须要回滚 <literal>Transaction</literal> ,关闭<literal>Session</literal>。如果你不这样做的话Hibernate无法保证内存状态精确的反应持久状态。尤其不要使用<literal>Session.load()</literal>来判断一个给定标识符的对象实例在数据库中是否存在,应该使用<literal>Session.get()</literal>或者进行一次查询.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -114,7 +130,18 @@ Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得c
<term>对于关联优先考虑lazy fetching </term> <term>对于关联优先考虑lazy fetching </term>
<listitem> <listitem>
<para> <para>
谨慎的使用主动外连接抓取(eager (outer-join) fetching)。对于大多数没有JVM级别缓存的持久对象的关联应该使用代理(proxies)或者具有延迟加载属性的集合(lazy collections)。对于被缓存的对象的关联,尤其是缓存的命中率非常高的情况下,应该使用<literal>outer-join="false"</literal>显式的禁止掉eager fetching。如果那些特殊的确实适合使用outer-join fetch 的场合,请在查询中使用<literal>left join</literal> 谨慎的使用主动抓取(eager fetching)。对于关联来说,若其目标是无法在第二级缓存中完全缓存所有实例的类,应该使用代理(proxies)与/或具有延迟加载属性的集合(lazy collections)。若目标是可以被缓存的,尤其是缓存的命中率非常高的情况下,应该使用<literal>lazy="false"</literal>明确的禁止掉eager fetching。如果那些特殊的确实适合使用join fetch 的场合,请在查询中使用<literal>left join fetch</literal>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
使用<emphasis>open session in view</emphasis>模式,或者执行严格的<emphasis>装配期(assembly phase)</emphasis>策略来避免再次抓取数据带来的问题
</term>
<listitem>
<para>
Hibernate让开发者们摆脱了繁琐的<emphasis>Data Transfer Objects</emphasis> (DTO)。在传统的EJB结构中DTO有双重作用首先他们解决了entity bean无法序列化的问题其次他们隐含地定义了一个装配期在此期间所有在view层需要用到的数据都被抓取、集中到了DTO中然后控制才被装到表示层。Hibernate终结了第一个作用。然而除非你做好了在整个渲染过程中都维护一个打开的持久化上下文(session)的准备你仍然需要一个装配期想象一下你的业务方法与你的表示层有严格的契约数据总是被放置到托管对象中。这并非是Hibernate的限制这是实现安全的事务化数据访问的基本需求。
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -126,14 +153,6 @@ Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得c
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>使用与业务有关的键值来实现<literal>equals()</literal><literal>hashCode()</literal> .</term>
<listitem>
<para>
如果你在Session外比较对象,你必须要实现<literal>equals()</literal><literal>hashCode()</literal>。在Session内部Java的对象识别机制是可以保证的。如果你实现了这些方法不要再使用数据库(主键)辨识!瞬时对象不具有(数据库)标识值Hibernate会在对象被保存的时候赋予它一个值。如果对象在被保存的时候位于Set内hash code就会变化要约就被违背。为了实现用与业务有关的键值编写<literal>equals()</literal><literal>hashCode()</literal>你应该使用类属性的唯一组合。记住这个键值只是当对象位于Set内部时才需要保证稳定且唯一并不是在其整个生命周期中都需要不需要达到数据库主键这样的稳定性。绝不要在<literal>equals()</literal>中比较集合(要考虑延迟装载),并且小心对待其他可能被代理过的类。
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term>不要用怪异的连接映射</term> <term>不要用怪异的连接映射</term>
<listitem> <listitem>
@ -142,6 +161,14 @@ Hibernate <literal>Session</literal> 然后从 <literal>Session</literal>获得c
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>偏爱双向关联</term>
<listitem>
<para>
单向关联更加难于查询。在大型应用中,几乎所有的关联必须在查询中可以双向导航。
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</chapter> </chapter>

View File

@ -63,7 +63,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
</sect1> </sect1>
<sect1 id="collections-mapping" revision="2"> <sect1 id="collections-mapping" revision="4">
<title>集合映射( Collection mappings </title> <title>集合映射( Collection mappings </title>
<para> <para>
@ -98,16 +98,17 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
<area id="mappingcollection10" coords="11 65"/> <area id="mappingcollection10" coords="11 65"/>
<area id="mappingcollection11" coords="12 65"/> <area id="mappingcollection11" coords="12 65"/>
<area id="mappingcollection12" coords="13 65"/> <area id="mappingcollection12" coords="13 65"/>
<area id="mappingcollection13" coords="14 65"/> <area id="mappingcollection13" coords="14 65"/>
<area id="mappingcollection14" coords="15 65"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<map <programlisting><![CDATA[<map
name="propertyName" name="propertyName"
table="table_name" table="table_name"
schema="schema_name" schema="schema_name"
lazy="true|false" lazy="true|extra|false"
inverse="true|false" inverse="true|false"
cascade="all|none|save-update|delete|all-delete-orphan" cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan"
sort="unsorted|natural|comparatorClass" sort="unsorted|natural|comparatorClass"
order-by="column_name asc|desc" order-by="column_name asc|desc"
where="arbitrary sql where condition" where="arbitrary sql where condition"
@ -115,6 +116,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
batch-size="N" batch-size="N"
access="field|property|ClassName" access="field|property|ClassName"
optimistic-lock="true|false" optimistic-lock="true|false"
mutable="true|false"
node="element-name|." node="element-name|."
embed-xml="true|false" embed-xml="true|false"
> >
@ -141,7 +143,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
</callout> </callout>
<callout arearefs="mappingcollection4"> <callout arearefs="mappingcollection4">
<para> <para>
<literal>lazy</literal> (可选--默认为true) 可以用来关闭延迟加载,指定一直使用预先抓取(对数组不适用) <literal>lazy</literal> (可选--默认为true) 可以用来关闭延迟加载(false),指定一直使用预先抓取,或者打开"extra-lazy" 抓取,此时大多数操作不会初始化集合类(适用于非常大的集合)
</para> </para>
</callout> </callout>
<callout arearefs="mappingcollection5"> <callout arearefs="mappingcollection5">
@ -185,15 +187,21 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
</callout> </callout>
<callout arearefs="mappingcollection12"> <callout arearefs="mappingcollection12">
<para> <para>
<literal>access</literal>(可选-默认为属性property):Hibernate取得属性值时使用的策略 <literal>access</literal>(可选-默认为属性property):Hibernate取得集合属性值时使用的策略
</para> </para>
</callout> </callout>
<callout arearefs="mappingcollection12"> <callout arearefs="mappingcollection13">
<para> <para>
<literal>乐观锁</literal> (可选 - 默认为 <literal>true</literal>): <literal>乐观锁</literal> (可选 - 默认为 <literal>true</literal>):
对集合的状态的改变会是否导致其所属的实体的版本增长。 (对一对多关联来说,关闭这个属性常常是有理的) 对集合的状态的改变会是否导致其所属的实体的版本增长。 (对一对多关联来说,关闭这个属性常常是有理的)
</para> </para>
</callout> </callout>
<callout arearefs="mappingcollection14">
<para>
<literal>mutable(可变)</literal> (可选 - 默认为<literal>true</literal>):
若值为<literal>false</literal>,表明集合中的元素不会改变(在某些情况下可以进行一些小的性能优化)。
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -272,7 +280,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
<callout arearefs="mapkey3"> <callout arearefs="mapkey3">
<para> <para>
<literal>type</literal> (可选,默认为整型<literal>integer</literal>):集合索引的类型。 <literal>type</literal> (必须):映射键(map key)的类型。
</para> </para>
</callout> </callout>
</calloutlist> </calloutlist>
@ -302,7 +310,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
</callout> </callout>
<callout arearefs="indexmanytomany3"> <callout arearefs="indexmanytomany3">
<para> <para>
<literal>class</literal> (必需):集合的索引使用的实体类。 <literal>class</literal> (必需):映射的键(map key)使用的实体类。
</para> </para>
</callout> </callout>
</calloutlist> </calloutlist>
@ -319,7 +327,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
从集合类可以产生很大一部分映射覆盖了很多常见的关系模型。我们建议你试验schema生成工具来体会一下不同的映射声明是如何被翻译为数据库表的。 从集合类可以产生很大一部分映射覆盖了很多常见的关系模型。我们建议你试验schema生成工具来体会一下不同的映射声明是如何被翻译为数据库表的。
</para> </para>
<sect2 id="collections-ofvalues" revision="1"> <sect2 id="collections-ofvalues" revision="2">
<title>值集合于多对多关联(Collections of values and many-to-many associations)</title> <title>值集合于多对多关联(Collections of values and many-to-many associations)</title>
@ -342,9 +350,9 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
column="column_name" column="column_name"
formula="any SQL expression" formula="any SQL expression"
type="typename" type="typename"
length="N" length="L"
precision="N" precision="P"
scale="N" scale="S"
not-null="true|false" not-null="true|false"
unique="true|false" unique="true|false"
node="element-name" node="element-name"
@ -382,6 +390,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
<area id="manytomany5" coords="6 60"/> <area id="manytomany5" coords="6 60"/>
<area id="manytomany6" coords="7 60"/> <area id="manytomany6" coords="7 60"/>
<area id="manytomany7" coords="8 60"/> <area id="manytomany7" coords="8 60"/>
<area id="manytomany8" coords="9 60"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<many-to-many <programlisting><![CDATA[<many-to-many
column="column_name" column="column_name"
@ -391,6 +400,7 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
unique="true|false" unique="true|false"
not-found="ignore|exception" not-found="ignore|exception"
entity-name="EntityName" entity-name="EntityName"
property-ref="propertyNameFromAssociatedClass"
node="element-name" node="element-name"
embed-xml="true|false" embed-xml="true|false"
/>]]></programlisting> />]]></programlisting>
@ -439,6 +449,11 @@ kittens = cat.getKittens(); //Okay, kittens collection is a Set
<literal>entity-name</literal> (可选): 被关联的类的实体名,作为<literal>class</literal>的替代。 <literal>entity-name</literal> (可选): 被关联的类的实体名,作为<literal>class</literal>的替代。
</para> </para>
</callout> </callout>
<callout arearefs="manytomany8">
<para>
<literal>property-ref</literal>: (可选) 被关联到此外键(foreign key)的类中的对应属性的名字。若未指定,使用被关联类的主键。
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>
@ -747,8 +762,8 @@ session.persist(category); // The relationship will be saved]]></p
</set> </set>
</class> </class>
<class name="eg.Child"> <class name="Child">
<id name="id" column="id"/> <id name="id" column="child_id"/>
.... ....
<many-to-one name="parent" <many-to-one name="parent"
class="Parent" class="Parent"
@ -764,6 +779,67 @@ session.persist(category); // The relationship will be saved]]></p
</sect2> </sect2>
<sect2 id="collections-indexedbidirectional">
<title>双向关联,涉及有序集合类</title>
<para>
对于有一端是<literal>&lt;list&gt;</literal>或者<literal>&lt;map&gt;</literal>的双向关联,需要加以特别考虑。假若子类中的一个属性映射到索引字段,没问题,我们仍然可以在集合类映射上使用<literal>inverse="true"</literal>
</para>
<programlisting><![CDATA[<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>
<class name="Child">
<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>]]></programlisting>
<para>
但是,假若子类中没有这样的属性存在,我们不能认为这个关联是真正的双向关联(信息不对称,在关联的一端有一些另外一端没有的信息)。在这种情况下,我们不能使用<literal>inverse="true"</literal>。我们需要这样用:
</para>
<programlisting><![CDATA[<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>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class>]]></programlisting>
<para>
注意在这个映射中,关联中集合类"值"一端负责来更新外键.TODO: Does this really result in some unnecessary update statements?
</para>
</sect2>
<sect2 id="collections-ternary"> <sect2 id="collections-ternary">
<title>三重关联Ternary associations</title> <title>三重关联Ternary associations</title>

View File

@ -5,7 +5,7 @@
<emphasis>Component</emphasis>这个概念在Hibernate中几处不同的地方为了不同的目的被重复使用. <emphasis>Component</emphasis>这个概念在Hibernate中几处不同的地方为了不同的目的被重复使用.
</para> </para>
<sect1 id="components-dependentobjects"> <sect1 id="components-dependentobjects" revision="2" >
<title>依赖对象Dependent objects</title> <title>依赖对象Dependent objects</title>
<para> <para>
@ -73,7 +73,7 @@
<programlisting><![CDATA[<class name="eg.Person" table="person"> <programlisting><![CDATA[<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string"> <id name="Key" column="pid" type="string">
<generator class="uuid.hex"/> <generator class="uuid"/>
</id> </id>
<property name="birthday" type="date"/> <property name="birthday" type="date"/>
<component name="Name" class="eg.Name"> <!-- class attribute optional --> <component name="Name" class="eg.Name"> <!-- class attribute optional -->
@ -111,7 +111,7 @@
<programlisting><![CDATA[<class name="eg.Person" table="person"> <programlisting><![CDATA[<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string"> <id name="Key" column="pid" type="string">
<generator class="uuid.hex"/> <generator class="uuid"/>
</id> </id>
<property name="birthday" type="date"/> <property name="birthday" type="date"/>
<component name="Name" class="eg.Name" unique="true">> <component name="Name" class="eg.Name" unique="true">>

View File

@ -384,6 +384,19 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</entry> </entry>
<entry> <entry>
输出所有SQL语句到控制台. 输出所有SQL语句到控制台.
有一个另外的选择是把<literal>org.hibernate.SQL</literal>这个log category设为<literal>debug</literal>
<para>
<emphasis role="strong">eg.</emphasis>
<literal>true</literal> | <literal>false</literal>
</para>
</entry>
</row>
<row>
<entry>
<literal>hibernate.format_sql</literal>
</entry>
<entry>
在log和console中打印出更漂亮的SQL。
<para> <para>
<emphasis role="strong">取值</emphasis> <emphasis role="strong">取值</emphasis>
<literal>true</literal> | <literal>false</literal> <literal>true</literal> | <literal>false</literal>
@ -804,7 +817,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</tgroup> </tgroup>
</table> </table>
<table frame="topbot" id="configuration-transaction-properties" revision="8"> <table frame="topbot" id="configuration-transaction-properties" revision="9">
<title> <title>
Hibernate事务属性 Hibernate事务属性
</title> </title>
@ -865,7 +878,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<literal>hibernate.transaction.flush_before_completion</literal> <literal>hibernate.transaction.flush_before_completion</literal>
</entry> </entry>
<entry> <entry>
如果开启, session在事务完成后将被自动清洗(flush). (在Hibernate和CMT一起使用时很有用.) 如果开启, session在事务完成后将被自动清洗(flush)。 现在更好的方法是使用自动session上下文管理。请参见<xref linkend="architecture-current-session"/>
<para> <para>
<emphasis role="strong">取值</emphasis> <emphasis role="strong">取值</emphasis>
<literal>true</literal> | <literal>false</literal> <literal>true</literal> | <literal>false</literal>
@ -877,7 +890,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<literal>hibernate.transaction.auto_close_session</literal> <literal>hibernate.transaction.auto_close_session</literal>
</entry> </entry>
<entry> <entry>
如果开启, session在事务完成后将被自动关闭. (在Hibernate和CMT一起使用时很有用.) 如果开启, session在事务完成后将被自动关闭。 现在更好的方法是使用自动session上下文管理。请参见<xref linkend="architecture-current-session"/>
<para> <para>
<emphasis role="strong">取值</emphasis> <emphasis role="strong">取值</emphasis>
<literal>true</literal> | <literal>false</literal> <literal>true</literal> | <literal>false</literal>
@ -888,7 +901,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</tgroup> </tgroup>
</table> </table>
<table frame="topbot" id="configuration-misc-properties" revision="7"> <table frame="topbot" id="configuration-misc-properties" revision="9">
<title> <title>
其他属性 其他属性
</title> </title>
@ -907,6 +920,20 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</row> </row>
</thead> </thead>
<tbody> <tbody>
<row>
<entry>
<literal>hibernate.current_session_context_class</literal>
</entry>
<entry>
为界定"当前"
<literal>Session</literal>指定一个策略。关于内置策略的详情,请参见<xref linkend="architecture-current-session"/>
<para>
<emphasis role="strong">eg.</emphasis>
<literal>jta</literal> | <literal>thread</literal> |
<literal>custom.Class</literal>
</para>
</entry>
</row>
<row> <row>
<entry> <entry>
<literal>hibernate.query.factory_class</literal> <literal>hibernate.query.factory_class</literal>
@ -938,11 +965,12 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<literal>hibernate.hbm2ddl.auto</literal> <literal>hibernate.hbm2ddl.auto</literal>
</entry> </entry>
<entry> <entry>
<literal>SessionFactory</literal>创建时自动将数据库schema的DDL导出到数据库. 使用 <literal>SessionFactory</literal>创建时,自动检查数据库机构,或者将数据库schema的DDL导出到数据库. 使用
<literal>create-drop</literal>时,在显式关闭<literal>SessionFactory</literal>将drop掉数据库schema. <literal>create-drop</literal>时,在显式关闭<literal>SessionFactory</literal>将drop掉数据库schema.
<para> <para>
<emphasis role="strong">取值</emphasis> <emphasis role="strong">取值</emphasis>
<literal>update</literal> | <literal>create</literal> | <literal>create-drop</literal> <literal>validate</literal> | <literal>update</literal> |
<literal>create</literal> | <literal>create-drop</literal>
</para> </para>
</entry> </entry>
</row> </row>
@ -1232,9 +1260,9 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</entry> </entry>
</row> </row>
<row> <row>
<entry><literal>org.hibernate.hql.ast</literal></entry> <entry><literal>org.hibernate.hql.AST</literal></entry>
<entry> <entry>
为HQL和SQL的自动状态转换和其他关于查询解析的信息记录日志 在解析查询的时候,记录HQL和SQL的AST分析日志
</entry> </entry>
</row> </row>
<row> <row>
@ -1399,10 +1427,10 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
<emphasis>JTA Session绑定:</emphasis> 如果使用EJB, Hibernate <literal>Session</literal> <emphasis>JTA Session绑定:</emphasis> Hibernate <literal>Session</literal>
可以自动绑定到JTA事务作用的范围. 只需简单地从JNDI查找<literal>SessionFactory</literal>并获得当前的 可以自动绑定到JTA事务作用的范围. 只需简单地从JNDI查找<literal>SessionFactory</literal>并获得当前的
<literal>Session</literal>. 当JTA事务完成时, 让Hibernate来处理 <literal>Session</literal>. 当JTA事务完成时, 让Hibernate来处理
<literal>Session</literal>的清洗(flush)与关闭. 在EJB的部署描述符中事务边界是声明式的. <literal>Session</literal>的清洗(flush)与关闭. 在EJB的部署描述符中事务边界是声明式的(CMT),或者自行编程(BMT/UserTransaction).
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
@ -1474,7 +1502,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</para> </para>
<para> <para>
Hibernate的一些特性 (即二级缓存, JTA与Session的自动绑定等等)需要访问在托管环境中的JTA <literal>TransactionManager</literal>. Hibernate的一些特性 (比如二级缓存, Contextual Sessions with JTA等等)需要访问在托管环境中的JTA <literal>TransactionManager</literal>.
由于J2EE没有标准化一个单一的机制,Hibernate在应用程序服务器中你必须指定Hibernate如何获得<literal>TransactionManager</literal>的引用: 由于J2EE没有标准化一个单一的机制,Hibernate在应用程序服务器中你必须指定Hibernate如何获得<literal>TransactionManager</literal>的引用:
</para> </para>
@ -1540,7 +1568,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</sect2> </sect2>
<sect2 id="configuration-optional-jndi" revision="2"> <sect2 id="configuration-optional-jndi" revision="3">
<title> <title>
JNDI绑定的<literal>SessionFactory</literal> JNDI绑定的<literal>SessionFactory</literal>
</title> </title>
@ -1569,42 +1597,23 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</para> </para>
<para> <para>
如果你使用与JNDI绑定的<literal>SessionFactory</literal>, EJB或任何其他类可以通过一个JNDI查询来获得这个<literal>SessionFactory</literal>. 假若你使用JNDI <literal>SessionFactory</literal>,EJB或者任何其它类都可以从JNDI中找到此<literal>SessionFactory</literal>
请注意, 如果你使用第一章中介绍的帮助类<literal>HibernateUtil</literal> - 类似Singleton(单实例)注册表, 那么这里的启动代码不是必要的. </para>
<literal>HibernateUtil</literal>更多被使用在非托管环境中.
<para>
我们建议,在受管理的环境中,把<literal>SessionFactory</literal>绑定到JNDI在其它情况下使用一个<literal>static(静态的)</literal>singleton。为了在你的应用程序代码中隐藏这些细节我们还建议你用一个helper类把实际查找<literal>SessionFactory</literal>的代码隐藏起来,比如<literal>HibernateUtil.getSessionFactory()</literal>。注意这个类也就可以方便地启动Hibernate参见第一章。
</para> </para>
</sect2> </sect2>
<sect2 id="configuration-j2ee-currentsession" revision="1"> <sect2 id="configuration-j2ee-currentsession" revision="4">
<title> <title>在JTA环境下使用Current Session context (当前session上下文)管理</title>
JTA和Session的自动绑定
</title>
<para> <para>
在非托管环境中,我们建议:<literal>HibernateUtil</literal>和静态<literal>SessionFactory</literal>一起工作, 在Hibernate中管理<literal>Session</literal>和transaction最好的方法是自动的"当前"<literal>Session</literal>管理。请参见<xref linkend="architecture-current-session">current sessions</xref>一节的讨论。使用<literal>"jta"</literal>session上下文假若在当前JTA事务中还没有Hibernate<literal>Session</literal>关联,第一次<literal>sessionFactory.getCurrentSession()</literal>调用会启动一个Session,并关联到当前的JTA事务。在<literal>"jta"</literal>上下文中用<literal>getCurrentSession()</literal>获得的<literal>Session</literal>会被设置为在transaction关闭的时候自动flush清洗、在transaction关闭之后自动关闭每句与句之后主动释放JDBC连接。这就可以根据JTA事务的生命周期来管理与之关联的<literal>Session</literal>,用户代码中就可以不再考虑这些管理。你的代码可以通过<literal>UserTransaction</literal>用编程方式使用JTA或者(我们建议为了便于移植代码使用Hibernate的<literal>Transaction</literal> API来设置transaction边界。如果你的代码运行于EJB容器中建议对CMT使用声明式事务声明。
<literal>ThreadLocal</literal>管理Hibernate <literal>Session</literal>
由于一些EJB可能会运行在同一个事务但不同线程的环境中, 所以这个方法不能照搬到EJB环境中.
我们建议在托管环境中,将<literal>SessionFactory</literal>绑定到JNDI上.
</para> </para>
<para>
请使用<literal>SessionFactory</literal><literal>getCurrentSession()</literal>方法来代替
直接使用<literal>ThreadLocal</literal>去获得Hibernate <literal>Session</literal>.
如果在当前JTA事务中没有Hibernate <literal>Session</literal>, 将会启动一个并将它关联到事务中.
对于使用<literal>getCurrentSession()</literal>获得的每个<literal>Session</literal>而言,
<literal>hibernate.transaction.flush_before_completion</literal>
<literal>hibernate.transaction.auto_close_session</literal>这两个配置选项会自动设置,
因此在容器结束JTA事务时这些<literal>Session</literal>会被自动清洗(flush)并关闭.
</para>
<para>
例如如果你使用DAO模式来编写你的持久层, 那么在需要时所有DAO将查找<literal>SessionFactory</literal>并打开"当前"Session.
没有必要在控制代码和DAO代码间传递<literal>SessionFactory</literal><literal>Session</literal>的实例.
</para>
</sect2> </sect2>
<sect2 id="configuration-j2ee-jmx" revision="1"> <sect2 id="configuration-j2ee-jmx" revision="1">
<title> <title>
JMX部署 JMX部署

View File

@ -8,7 +8,7 @@
以及允许对Hibernate功能进行扩展。 以及允许对Hibernate功能进行扩展。
</para> </para>
<sect1 id="objectstate-interceptors" revision="1"> <sect1 id="objectstate-interceptors" revision="3">
<title> <title>
拦截器(Interceptors) 拦截器(Interceptors)
</title> </title>
@ -21,19 +21,25 @@
<literal>Auditable</literal>接口的对象被更新时,同步更新<literal>lastUpdateTimestamp</literal>属性。 <literal>Auditable</literal>接口的对象被更新时,同步更新<literal>lastUpdateTimestamp</literal>属性。
</para> </para>
<para>
你可以直接实现<literal>Interceptor</literal>接口,也可以(最好)继承自<literal>EmptyInterceptor</literal>
</para>
<programlisting><![CDATA[package org.hibernate.test; <programlisting><![CDATA[package org.hibernate.test;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import org.hibernate.Interceptor; import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type; import org.hibernate.type.Type;
public class AuditInterceptor implements Interceptor, Serializable { public class AuditInterceptor extends EmptyInterceptor {
private int updates; private int updates;
private int creates; private int creates;
private int loads;
public void onDelete(Object entity, public void onDelete(Object entity,
Serializable id, Serializable id,
@ -67,6 +73,9 @@ public class AuditInterceptor implements Interceptor, Serializable {
Object[] state, Object[] state,
String[] propertyNames, String[] propertyNames,
Type[] types) { Type[] types) {
if ( entity instanceof Auditable ) {
loads++;
}
return false; return false;
} }
@ -88,34 +97,38 @@ public class AuditInterceptor implements Interceptor, Serializable {
return false; return false;
} }
public void postFlush(Iterator entities) { public void afterTransactionCompletion(Transaction tx) {
System.out.println("Creations: " + creates + ", Updates: " + updates); if ( tx.wasCommitted() ) {
} System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
}
public void preFlush(Iterator entities) {
updates=0; updates=0;
creates=0; creates=0;
loads=0;
} }
...
}]]></programlisting> }]]></programlisting>
<para> <para>
创建会话(session)的时候可以指定拦截器。 拦截器可以有两种:<literal>Session</literal>范围内的,和<literal>SessionFactory</literal>范围内的。
</para>
<para>
当使用某个重载的SessionFactory.openSession()使用<literal>Interceptor</literal>作为参数调用打开一个session的时候就指定了<literal>Session</literal>范围内的拦截器。
</para> </para>
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting> <programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
<para> <para>
你也可以使用<literal>Configuration</literal>来设置一个全局范围的拦截器 <literal>SessionFactory</literal>范围内的拦截器要通过<literal>Configuration</literal>中注册,而这必须在创建<literal>SessionFactory</literal>之前。在这种情况下,给出的拦截器会被这个<literal>SessionFactory</literal>所打开的所有session使用了除非session打开时明确指明了使用的拦截器。<literal>SessionFactory</literal>范围内的拦截器必须是线程安全的因为多个session可能并发使用这个拦截器要因此小心不要保存与session相关的状态
</para> </para>
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting> <programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
</sect1> </sect1>
<sect1 id="objectstate-events" revision="2"> <sect1 id="objectstate-events" revision="4">
<title> <title>
事件系统(Event system) 事件系统(Event system)
</title> </title>
@ -149,25 +162,27 @@ public class AuditInterceptor implements Interceptor, Serializable {
下面是一个用户定制的加载事件(load event)的监听器: 下面是一个用户定制的加载事件(load event)的监听器:
</para> </para>
<programlisting><![CDATA[public class MyLoadListener extends DefaultLoadEventListener { <programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
// this is the single method defined by the LoadEventListener interface // this is the single method defined by the LoadEventListener interface
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType) public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
throws HibernateException { throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) { if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
throw MySecurityException("Unauthorized access"); throw MySecurityException("Unauthorized access");
} }
return super.onLoad(event, loadType);
} }
}]]></programlisting> }]]></programlisting>
<para> <para>
你还需要修改一处配置来告诉Hibernate以使用选定的监听器来替代默认的监听器。 你还需要修改一处配置来告诉Hibernate,除了默认的监听器,还要附加选定的监听器。
</para> </para>
<programlisting><![CDATA[<hibernate-configuration> <programlisting><![CDATA[<hibernate-configuration>
<session-factory> <session-factory>
... ...
<listener type="load" class="MyLoadListener"/> <event type="load">
<listener class="com.eg.MyLoadListener"/>
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
</event>
</session-factory> </session-factory>
</hibernate-configuration>]]></programlisting> </hibernate-configuration>]]></programlisting>
@ -176,7 +191,8 @@ public class AuditInterceptor implements Interceptor, Serializable {
</para> </para>
<programlisting><![CDATA[Configuration cfg = new Configuration(); <programlisting><![CDATA[Configuration cfg = new Configuration();
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );]]></programlisting> LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
<para> <para>
通过在XML配置文件声明而注册的监听器不能共享实例。如果在多个<literal>&lt;listener/&gt;</literal>节点中使用 通过在XML配置文件声明而注册的监听器不能共享实例。如果在多个<literal>&lt;listener/&gt;</literal>节点中使用
@ -191,7 +207,7 @@ cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() )
</sect1> </sect1>
<sect1 id="objectstate-decl-security"> <sect1 id="objectstate-decl-security" revision="2">
<title> <title>
Hibernate的声明式安全机制 Hibernate的声明式安全机制
</title> </title>
@ -210,6 +226,10 @@ cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() )
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/> <listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting> <listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
<para>
注意,<literal>&lt;listener type="..." class="..."/&gt;</literal>只是<literal>&lt;event type="..."&gt;&lt;listener class="..."/&gt;&lt;/event&gt;</literal>的简写,对每一个事件类型都必须严格的有一个监听器与之对应。
</para>
<para> <para>
接下来,仍然在<literal>hibernate.cfg.xml</literal>文件中,绑定角色的权限: 接下来,仍然在<literal>hibernate.cfg.xml</literal>文件中,绑定角色的权限:
</para> </para>

View File

@ -346,7 +346,7 @@ alter table line_items
<para> <para>
这些例子全部来自于Hibernate的test suite同时你也可以找到其他有用的例子。 这些例子全部来自于Hibernate的test suite同时你也可以找到其他有用的例子。
可以参考Hibernate的<literal>src</literal>目录。 可以参考Hibernate的<literal>test</literal>目录。
</para> </para>
<para>TODO: put words around this stuff</para> <para>TODO: put words around this stuff</para>
@ -497,6 +497,46 @@ alter table line_items
</class>]]></programlisting> </class>]]></programlisting>
</sect2> </sect2>
<sect2 id="example-mappings-composite-key-manytomany">
<title>共有组合键属性的多对多(Many-to-many with shared composite key attribute)</title>
<programlisting><![CDATA[<class name="User" table="`User`">
<composite-id>
<key-property name="name"/>
<key-property name="org"/>
</composite-id>
<set name="groups" table="UserGroup">
<key>
<column name="userName"/>
<column name="org"/>
</key>
<many-to-many class="Group">
<column name="groupName"/>
<formula>org</formula>
</many-to-many>
</set>
</class>
<class name="Group" table="`Group`">
<composite-id>
<key-property name="name"/>
<key-property name="org"/>
</composite-id>
<property name="description"/>
<set name="users" table="UserGroup" inverse="true">
<key>
<column name="groupName"/>
<column name="org"/>
</key>
<many-to-many class="User">
<column name="userName"/>
<formula>org</formula>
</many-to-many>
</set>
</class>
]]></programlisting>
</sect2>
<sect2 id="example-mappings-content-discrimination"> <sect2 id="example-mappings-content-discrimination">
<title>Content based discrimination</title> <title>Content based discrimination</title>
@ -552,7 +592,7 @@ alter table line_items
</class>]]></programlisting> </class>]]></programlisting>
</sect2> </sect2>
<sect2 id="example-mappings-association-alternatekeys" > <sect2 id="example-mappings-association-alternatekeys" revision="2">
<title>Associations on alternate keys</title> <title>Associations on alternate keys</title>
<programlisting><![CDATA[<class name="Person"> <programlisting><![CDATA[<class name="Person">
@ -593,7 +633,7 @@ alter table line_items
<class name="Account"> <class name="Account">
<id name="accountId" length="32"> <id name="accountId" length="32">
<generator class="uuid.hex"/> <generator class="uuid"/>
</id> </id>
<many-to-one name="user" <many-to-one name="user"

View File

@ -1,7 +1,7 @@
 <chapter id="inheritance">  <chapter id="inheritance">
<title>继承映射(Inheritance Mappings)</title> <title>继承映射(Inheritance Mappings)</title>
<sect1 id="inheritance-strategies" revision="2"> <sect1 id="inheritance-strategies" revision="3">
<title> 三种策略</title> <title> 三种策略</title>
@ -50,6 +50,18 @@
</para> </para>
<para>
在不同的映射文件中定义 <literal>subclass</literal>, <literal>union-subclass</literal>,
<literal>joined-subclass</literal>是被允许的,只需直接定义在<literal>hibernate-mapping</literal>之下 。也就是说你可以仅加入一个新的映射文件就扩展类层次。你必须在subclass的映射中指明<literal>extends</literal>属性给出一个之前定义的超类的名字。注意以前这一功能对映射文件的顺序有严格的要求。自从Hibernate 3开始当使用extends关键字的时候映射文件的顺序不再有影响。但在每个映射文件本身之中顺序还是必须超类在前子类在后。
</para>
<programlisting><![CDATA[
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>]]></programlisting>
<sect2 id="inheritance-tableperclass" > <sect2 id="inheritance-tableperclass" >
<title>每个类分层结构一张表(Table per class hierarchy)</title> <title>每个类分层结构一张表(Table per class hierarchy)</title>
@ -122,7 +134,7 @@
</sect2> </sect2>
<sect2 id="inheritance-tablepersubclass-discriminator"> <sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
<title>每个子类一张表(Table per subclass),使用辨别标志(Discriminator)</title> <title>每个子类一张表(Table per subclass),使用辨别标志(Discriminator)</title>
@ -144,22 +156,26 @@
... ...
<subclass name="CreditCardPayment" discriminator-value="CREDIT"> <subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT"> <join table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/> <property name="creditCardType" column="CCTYPE"/>
... ...
</join> </join>
</subclass> </subclass>
<subclass name="CashPayment" discriminator-value="CASH"> <subclass name="CashPayment" discriminator-value="CASH">
<join table="CASH_PAYMENT"> <join table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
... ...
</join> </join>
</subclass> </subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE"> <subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select"> <join table="CHEQUE_PAYMENT" fetch="select">
<key column="PAYMENT_ID"/>
... ...
</join> </join>
</subclass> </subclass>
</class>]]></programlisting> </class>]]></programlisting>
<para> <para>
可选的声明<literal>fetch="select"</literal>是用来告诉Hibernate在查询超类时 可选的声明<literal>fetch="select"</literal>是用来告诉Hibernate在查询超类时
不要使用外部连接(outer join)来抓取子类<literal>ChequePayment</literal>的数据。 不要使用外部连接(outer join)来抓取子类<literal>ChequePayment</literal>的数据。
@ -204,7 +220,7 @@
</sect2> </sect2>
<sect2 id="inheritance-tableperconcrete" revision="1"> <sect2 id="inheritance-tableperconcrete" revision="2">
<title>每个具体类一张表(Table per concrete class)</title> <title>每个具体类一张表(Table per concrete class)</title>
<para> <para>
@ -231,7 +247,7 @@
</class>]]></programlisting> </class>]]></programlisting>
<para> <para>
这里涉及三张表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。 这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。
</para> </para>
<para> <para>
@ -240,6 +256,10 @@
不允许在联合子类(union subclass)的继承层次中使用标识生成器策略(identity generator strategy), 不允许在联合子类(union subclass)的继承层次中使用标识生成器策略(identity generator strategy),
实际上, 主键的种子(primary key seed)不得不为同一继承层次中的全部被联合子类所共用. 实际上, 主键的种子(primary key seed)不得不为同一继承层次中的全部被联合子类所共用.
</para> </para>
<para>
假若超类是抽象类,请使用<literal>abstract="true"</literal>。当然,假若它不是抽象的,需要一个额外的表(上面的例子中,默认是<literal>PAYMENT</literal>),来保存超类的实例。
</para>
</sect2> </sect2>

View File

@ -1,9 +1,8 @@
<chapter id="performance"> <chapter id="performance">
<title>提升性能 <title>提升性能
</title> </title>
<sect1 id="performance-fetching"> <sect1 id="performance-fetching" revision="2">
<title> <title>
抓取策略(Fetching strategies) 抓取策略(Fetching strategies)
</title> </title>
@ -66,6 +65,11 @@
<emphasis>Lazy collection fetching延迟集合抓取</emphasis>- 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。) <emphasis>Lazy collection fetching延迟集合抓取</emphasis>- 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。)
</para> </para>
</listitem> </listitem>
<listitem>
<para>
<emphasis>"Extra-lazy" collection fetching,"Extra-lazy"集合抓取</emphasis> -对集合类中的每个元素而言都是直到需要时才去访问数据库。除非绝对必要Hibernate不会试图去把整个集合都抓取到内存里来适用于非常大的集合
</para>
</listitem>
<listitem> <listitem>
<para> <para>
<emphasis>Proxy fetching代理抓取</emphasis> - 对返回单值的关联而言当其某个方法被调用而非对其关键字进行get操作时才抓取。 <emphasis>Proxy fetching代理抓取</emphasis> - 对返回单值的关联而言当其某个方法被调用而非对其关键字进行get操作时才抓取。
@ -73,7 +77,12 @@
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<emphasis>Lazy attribute fetching属性延迟加载</emphasis> - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取(需要运行时字节码强化)。这一方法很少是必要的。 <emphasis>"No-proxy" fetching,非代理抓取</emphasis> - 对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符,也会导致关联抓取)但是更加透明因为对应用程序来说不再看到proxy。这种方法需要在编译期间进行字节码增强操作因此很少需要用到。
</para>
</listitem>
<listitem>
<para>
<emphasis>Lazy attribute fetching属性延迟加载</emphasis> - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
@ -129,7 +138,7 @@ Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></prog
</sect2> </sect2>
<sect2 id="performance-fetching-custom" revision="3"> <sect2 id="performance-fetching-custom" revision="4">
<title> <title>
调整抓取策略Tuning fetch strategies 调整抓取策略Tuning fetch strategies
@ -148,7 +157,7 @@ Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></prog
<programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting> <programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting>
<para> <para>
在映射文档中定义的抓取策略将会有产生以下影响: 在映射文档中定义的<literal>抓取</literal>策略将会对以下列表条目产生影响:
</para> </para>
<itemizedlist> <itemizedlist>
@ -160,7 +169,7 @@ Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></prog
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
只有在关联之间进行导航时,才会隐式的取得数据(延迟抓取) 只有在关联之间进行导航时,才会隐式的取得数据。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -168,8 +177,16 @@ Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></prog
<literal>条件查询</literal> <literal>条件查询</literal>
</para> </para>
</listitem> </listitem>
<listitem>
<para>
使用了<literal>subselect</literal>抓取的HQL查询
</para>
</listitem>
</itemizedlist> </itemizedlist>
<para>
不管你使用哪种抓取策略定义为非延迟的类图会被保证一定装载入内存。注意这可能意味着在一条HQL查询后紧跟着一系列的查询。
</para>
<para> <para>
通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中,
@ -327,10 +344,14 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
<para> <para>
Hibernate将会识别出那些重载了<literal>equals()</literal>、或<literal>hashCode()</literal>方法的持久化类。 Hibernate将会识别出那些重载了<literal>equals()</literal>、或<literal>hashCode()</literal>方法的持久化类。
</para> </para>
<para>
若选择<literal>lazy="no-proxy"</literal>而非默认的<literal>lazy="proxy"</literal>,我们可以避免类型转换带来的问题。然而,这样我们就需要编译期字节码增强,并且所有的操作都会导致立刻进行代理初始化。
</para>
</sect2> </sect2>
<sect2 id="performance-fetching-initialization"> <sect2 id="performance-fetching-initialization" revision="1">
<title>实例化集合和代理Initializing collections and proxies <title>实例化集合和代理Initializing collections and proxies
</title> </title>
@ -368,9 +389,7 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
结束时关闭<literal>Session</literal>(这里使用了<emphasis>在展示层保持打开Session模式Open Session in View</emphasis> 结束时关闭<literal>Session</literal>(这里使用了<emphasis>在展示层保持打开Session模式Open Session in View</emphasis>
当然,这将依赖于应用框架中异常需要被正确的处理。在返回界面给用户之前,乃至在生成界面过程中发生异常的情况下, 当然,这将依赖于应用框架中异常需要被正确的处理。在返回界面给用户之前,乃至在生成界面过程中发生异常的情况下,
正确关闭<literal>Session</literal>和结束事务将是非常重要的, 正确关闭<literal>Session</literal>和结束事务将是非常重要的,
Servlet过滤器必须如此访问<literal>Session</literal>才能保证正确使用Session。 请参见Hibernate wiki上的"Open Session in View"模式,你可以找到示例。
我们推荐使用<literal>ThreadLocal</literal> 变量保存当前的<literal>Session</literal>
(可以参考<xref linkend="quickstart-playingwithcats"/>的例子实现)。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -610,7 +629,7 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
</tgroup> </tgroup>
</table> </table>
<sect2 id="performance-cache-mapping"> <sect2 id="performance-cache-mapping" revision="2">
<title>缓存映射Cache mappings <title>缓存映射Cache mappings
</title> </title>
@ -621,21 +640,39 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
<programlistingco> <programlistingco>
<areaspec> <areaspec>
<area id="cache1" coords="2 70"/> <area id="cache1" coords="2 70"/>
<area id="cache2" coords="3 70"/>
<area id="cache3" coords="4 70"/>
</areaspec> </areaspec>
<programlisting><![CDATA[<cache <programlisting><![CDATA[<cache
usage="transactional|read-write|nonstrict-read-write|read-only" usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>]]></programlisting> />]]></programlisting>
<calloutlist> <calloutlist>
<callout arearefs="cache1"> <callout arearefs="cache1">
<para> <para>
<literal>usage</literal>说明了缓存的策略: <literal>usage</literal>(必须)说明了缓存的策略:
<literal>transactional</literal> <literal>transactional</literal>
<literal>read-write</literal> <literal>read-write</literal>
<literal>nonstrict-read-write</literal> <literal>nonstrict-read-write</literal>
<literal>read-only</literal> <literal>read-only</literal>
</para> </para>
</callout> </callout>
<callout arearefs="cache2">
<para>
<literal>region</literal> (可选, 默认为类或者集合的名字(class or
collection role name)) 指定第二级缓存的区域名(name of the second level cache
region)
</para>
</callout>
<callout arearefs="cache3">
<para>
<literal>include</literal> (可选,默认为 <literal>all</literal>)
<literal>non-lazy</literal> 当属性级延迟抓取打开时, 标记为<literal>lazy="true"</literal>的实体的属性可能无法被缓存
</para>
</callout>
</calloutlist> </calloutlist>
</programlistingco> </programlistingco>

View File

@ -107,24 +107,6 @@ public class Cat {
这里要遵循四条主要的规则: 这里要遵循四条主要的规则:
</para> </para>
<sect2 id="persistent-classes-pojo-accessors" revision="1">
<title>为持久化字段声明访问器(accessors)和是否可变的标志(mutators)</title>
<para>
<literal>Cat</literal>为它的所有持久化字段声明了访问方法。很多其他ORM工具直接对
实例变量进行持久化。我们相信从持久化机制中分离这种实现细节要好得多。
Hibernate持久化JavaBeans风格的属性认可如下形式的方法名
<literal>getFoo</literal>, <literal>isFoo</literal><literal>setFoo</literal>
如果需要,你总是可以切换特定的属性的指示字段的访问方法。
</para>
<para>
属性<emphasis>不需要</emphasis>要声明为public的。Hibernate默认使用
<literal>protected</literal><literal>private</literal>的get/set方法对
对属性进行持久化。
</para>
</sect2>
<sect2 id="persistent-classes-pojo-constructor" revision="1"> <sect2 id="persistent-classes-pojo-constructor" revision="1">
<title>实现一个默认的即无参数的构造方法constructor</title> <title>实现一个默认的即无参数的构造方法constructor</title>
@ -210,6 +192,23 @@ public class Cat {
</para> </para>
</sect2> </sect2>
<sect2 id="persistent-classes-pojo-accessors" revision="2">
<title>为持久化字段声明访问器(accessors)和是否可变的标志(mutators)(可选)</title>
<para>
<literal>Cat</literal>为它的所有持久化字段声明了访问方法。很多其他ORM工具直接对
实例变量进行持久化。我们相信,在关系数据库schema和类的内部数据之间引入间接层(原文为"非直接"indirection)会好一些。默认情况下Hibernate持久化JavaBeans风格的属性认可如下形式的方法名
<literal>getFoo</literal>, <literal>isFoo</literal><literal>setFoo</literal>
如果需要,你总是可以切换特定的属性的指示字段的访问方法。
</para>
<para>
属性<emphasis>不需要</emphasis>要声明为public的。Hibernate默认使用
<literal>protected</literal><literal>private</literal>的get/set方法对
对属性进行持久化。
</para>
</sect2>
</sect1> </sect1>
<sect1 id="persistent-classes-inheritance"> <sect1 id="persistent-classes-inheritance">
@ -437,6 +436,55 @@ dynamicSession.close()
</para> </para>
</sect1> </sect1>
<sect1 id="persistent-classes-tuplizers" revision="0">
<title>元组片断映射(Tuplizers)</title>
<para>
<literal>org.hibernate.tuple.Tuplizer</literal>, 以及其子接口,负责根据给定的<literal>org.hibernate.EntityMode</literal>,来复现片断数据。对于给定的片断数据来说,可以认为其是一种数据结构, "tuplizer"就是一种映射器它知道如何创建这样的数据结构以及如何给这个数据结构赋值。比如说对于POJO这种Entity Mode对应的tuplizer知道通过其构造器来创建一个POJO再通过其属性访问器来访问POJO属性。有两大类Tuplizer分别是<literal>org.hibernate.tuple.EntityTuplizer</literal><literal>org.hibernate.tuple.ComponentTuplizer</literal>接口。如前所示,<literal>EntityTuplizer</literal>负责管理实体,而<literal>ComponentTuplizer</literal>则是针对组件。
</para>
<para>
用户也可以插入其自定义的tuplizer。或许您需要一种特别的<literal>java.util.Map</literal>实现它不是在dynamic-map entity-mode时使用的 <literal>java.util.HashMap</literal> ;或许您需要与众不同的特别代理生成策略(proxy generation strategy)。通过自定义tuplizer实现这两个目标您都可以达到。Tuplizer定义被附加到它们期望管理的entity或者component映射。回到我们的customer entity例子:
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</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 );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}]]></programlisting>
</sect1>
<para> <para>
TODO在property和proxy的包里用户扩展文件框架。 TODO在property和proxy的包里用户扩展文件框架。

View File

@ -62,7 +62,7 @@ List cats = crit.list();]]></programlisting>
</para> </para>
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class) <programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
.add( Restrictions.sql("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) ) .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
.list();]]></programlisting> .list();]]></programlisting>
<para> <para>
@ -113,7 +113,7 @@ List cats = sess.createCriteria(Cat.class)
</sect1> </sect1>
<sect1 id="querycriteria-associations"> <sect1 id="querycriteria-associations" revision="2">
<title>关联</title> <title>关联</title>
<para> <para>
@ -122,9 +122,9 @@ List cats = sess.createCriteria(Cat.class)
</para> </para>
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class) <programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%") .add( Restrictions.like("name", "F%") )
.createCriteria("kittens") .createCriteria("kittens")
.add( Restrictions.like("name", "F%") .add( Restrictions.like("name", "F%") )
.list();]]></programlisting> .list();]]></programlisting>
<para> <para>
@ -151,14 +151,14 @@ List cats = sess.createCriteria(Cat.class)
<para> <para>
<literal>Cat</literal>实例所保存的之前两次查询所返回的kittens集合是 <literal>Cat</literal>实例所保存的之前两次查询所返回的kittens集合是
<emphasis>没有</emphasis>被条件预过滤的。如果你希望只获得符合条件的kittens <emphasis>没有</emphasis>被条件预过滤的。如果你希望只获得符合条件的kittens
你必须使用<literal>returnMaps()</literal> 你必须使用<literal>ResultTransformer</literal>
</para> </para>
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class) <programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
.createCriteria("kittens", "kt") .createCriteria("kittens", "kt")
.add( Restrictions.eq("name", "F%") ) .add( Restrictions.eq("name", "F%") )
.returnMaps() .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
.list(); .list();
Iterator iter = cats.iterator(); Iterator iter = cats.iterator();
while ( iter.hasNext() ) { while ( iter.hasNext() ) {
@ -368,9 +368,57 @@ session.createCriteria(Cat.class, "cat")
.list();]]></programlisting> .list();]]></programlisting>
</sect1> </sect1>
<!--TODO: ResultSetTransformer + aliasing. AliasToBeanTransformer allow returning arbitrary <!--TODO: ResultSetTransformer + aliasing. AliasToBeanTransformer allow returning arbitrary
user objects - similar to setResultClass in JDO2. General use of ResultTransformer user objects - similar to setResultClass in JDO2. General use of ResultTransformer
could also be explained. --> could also be explained. -->
<sect1 id="query-criteria-naturalid">
<title>根据自然标识查询(Queries by natural identifier)</title>
<para>
对大多数查询,包括条件查询而言,因为查询缓存的失效(invalidation)发生得太频繁查询缓存不是非常高效。然而有一种特别的查询可以通过不变的自然键优化缓存的失效算法。在某些应用中这种类型的查询比较常见。条件查询API对这种用例提供了特别规约。
</para>
<para>
首先你应该对你的entity使用<literal>&lt;natural-id&gt;</literal>来映射自然键,然后打开第二级缓存。
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
注意,此功能对具有<emphasis>mutable</emphasis>自然键的entity并不适用。
</para>
<para>
然后打开Hibernate 查询缓存。
</para>
<para>
现在,我们可以用<literal>Restrictions.naturalId()</literal>来使用更加高效的缓存算法。
</para>
<programlisting><![CDATA[session.createCriteria(User.class)
.add( Restrictions.naturalId()
.set("name", "gavin")
.set("org", "hb")
).setCacheable(true)
.uniqueResult();]]></programlisting>
</sect1>
</chapter> </chapter>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="queryhql"> <chapter id="queryhql">
<title>HQL: Hibernate查询语言</title> <title>HQL: Hibernate查询语言</title>
<para> <para>
@ -75,7 +74,7 @@
</sect1> </sect1>
<sect1 id="queryhql-joins" revision="1"> <sect1 id="queryhql-joins" revision="2">
<title>关联(Association)与连接(Join)</title> <title>关联(Association)与连接(Join)</title>
<para> <para>
@ -126,6 +125,14 @@
join cat.mate as mate join cat.mate as mate
left join cat.kittens as kitten]]></programlisting> left join cat.kittens as kitten]]></programlisting>
<para>
通过HQL的<literal>with</literal>关键字你可以提供额外的join条件。
</para>
<programlisting><![CDATA[from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0]]></programlisting>
<para> <para>
还有,一个"fetch"连接允许仅仅使用一个选择语句就将相关联的对象或一组值的集合随着他们的父对象的初始化而被初始化,这种方法在使用到集合的情况下尤其有用,对于关联和集合来说,它有效的代替了映射文件中的外联接 还有,一个"fetch"连接允许仅仅使用一个选择语句就将相关联的对象或一组值的集合随着他们的父对象的初始化而被初始化,这种方法在使用到集合的情况下尤其有用,对于关联和集合来说,它有效的代替了映射文件中的外联接
与延迟声明lazy declarations. 查看 与延迟声明lazy declarations. 查看
@ -142,11 +149,16 @@
并不在查询的结果中直接返回,但可以通过他们的父对象来访问到他们。 并不在查询的结果中直接返回,但可以通过他们的父对象来访问到他们。
</para> </para>
<para> <programlisting><![CDATA[from Cat as cat
注意<literal>fetch</literal>构造变量在使用了<literal>scroll()</literal><literal>iterate()</literal>函数 inner join fetch cat.mate
的查询中是不能使用的。最后注意,使用<literal>full join fetch</literal><literal>right join fetch</literal>是没有意义的。 left join fetch cat.kittens child
left join fetch child.kittens]]></programlisting>
<para>
假若使用<literal>iterate()</literal>来调用查询,请注意<literal>fetch</literal>构造是不能使用的(<literal>scroll()</literal> 可以使用)。<literal>fetch</literal>也不应该与<literal>setMaxResults()</literal><literal>setFirstResult()</literal>共用,这是因为这些操作是基于结果集的,而在预先抓取集合类时可能包含重复的数据,也就是说无法预先知道精确的行数。<literal>fetch</literal>还不能与独立的 <literal>with</literal>条件一起使用。通过在一次查询中fetch多个集合可以制造出笛卡尔积因此请多加注意。对bag映射来说同时join fetch多个集合角色可能在某些情况下给出并非预期的结果也请小心。最后注意使用<literal>full join fetch</literal><literal>right join fetch</literal>是没有意义的。
</para> </para>
<para> <para>
如果你使用属性级别的延迟获取lazy fetching这是通过重新编写字节码实现的可以使用 <literal>fetch 如果你使用属性级别的延迟获取lazy fetching这是通过重新编写字节码实现的可以使用 <literal>fetch
all properties</literal> all properties</literal>
@ -159,6 +171,24 @@ all properties</literal>
</sect1> </sect1>
<sect1 id="queryhql-joins-forms">
<title>join 语法的形式</title>
<para>
HQL支持两种关联join的形式<literal>implicit(隐式)</literal><literal>explicit显式</literal>
</para>
<para>
上一节中给出的查询都是使用<literal>explicit(显式)</literal>形式的其中form子句中明确给出了join关键字。这是建议使用的方式。
</para>
<para>
<literal>implicit隐式</literal>形式不使用join关键字。关联使用"点号"来进行“引用”。<literal>implicit</literal> join可以在任何HQL子句中出现.<literal>implicit</literal> join在最终的SQL语句中以inner join的方式出现。
</para>
<programlisting><![CDATA[from Cat as cat where cat.mate.name like '%s%']]></programlisting>
</sect1>
<sect1 id="queryhql-select"> <sect1 id="queryhql-select">
<title>select子句</title> <title>select子句</title>
@ -512,7 +542,7 @@ where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting
<listitem> <listitem>
<para> <para>
EJB-QL 3.0定义的任何函数或操作:<literal>substring(), trim(), EJB-QL 3.0定义的任何函数或操作:<literal>substring(), trim(),
lower(), upper(), length(), locate(), abs(), sqrt(), bit_length()</literal> lower(), upper(), length(), locate(), abs(), sqrt(), bit_length() mod()</literal>
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -520,12 +550,30 @@ where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting
<literal>coalesce()</literal><literal>nullif()</literal> <literal>coalesce()</literal><literal>nullif()</literal>
</para> </para>
</listitem> </listitem>
<listitem>
<para>
<literal>str()</literal> 把数字或者时间值转换为可读的字符串
</para>
</listitem>
<listitem> <listitem>
<para> <para>
<literal>cast(... as ...)</literal>, 其第二个参数是某Hibernate类型的名字以及<literal>extract(... from ...)</literal>只要ANSI <literal>cast(... as ...)</literal>, 其第二个参数是某Hibernate类型的名字以及<literal>extract(... from ...)</literal>只要ANSI
<literal>cast()</literal><literal>extract()</literal> 被底层数据库支持 <literal>cast()</literal><literal>extract()</literal> 被底层数据库支持
</para> </para>
</listitem> </listitem>
<listitem>
<para>
HQL <literal>index()</literal> 函数作用于join的有序集合的别名。
</para>
</listitem>
<listitem>
<para>
HQL函数把集合作为参数:<literal>size(), minelement(), maxelement(), minindex(), maxindex()</literal>,还有特别的<literal>elements()</literal><literal>indices</literal>函数,可以与数量词加以限定:<literal>some, all, exists, any, in</literal>
</para>
</listitem>
<listitem> <listitem>
<para> <para>
任何数据库支持的SQL标量函数比如<literal>sign()</literal>, 任何数据库支持的SQL标量函数比如<literal>sign()</literal>,
@ -534,7 +582,7 @@ where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
JDBC参数传入 <literal>?</literal> JDBC风格的参数传入 <literal>?</literal>
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -544,7 +592,7 @@ where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
SQL 直接常量 <literal>'foo'</literal>, <literal>69</literal>, <literal>'1970-01-01 10:00:01.0'</literal> SQL 直接常量 <literal>'foo'</literal>, <literal>69</literal>, <literal>6.66E+2</literal>, <literal>'1970-01-01 10:00:01.0'</literal>
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -768,7 +816,7 @@ order by count(kitten) asc, sum(kitten.weight) desc]]></programlisting>
</sect1> </sect1>
<sect1 id="queryhql-subqueries"> <sect1 id="queryhql-subqueries" revision="2">
<title>子查询</title> <title>子查询</title>
<para> <para>
@ -796,6 +844,13 @@ where cat.name not in (
select name.nickName from Name as name select name.nickName from Name as name
)]]></programlisting> )]]></programlisting>
<programlisting><![CDATA[select cat.id, (select max(kit.weight) from cat.kitten kit)
from Cat as cat]]></programlisting>
<para>
注意HQL自查询只可以在select或者where子句中出现。
</para>
<para> <para>
在select列表中包含一个表达式以上的子查询你可以使用一个元组构造符tuple constructors 在select列表中包含一个表达式以上的子查询你可以使用一个元组构造符tuple constructors
</para> </para>
@ -940,11 +995,12 @@ order by account.type.sortOrder, account.accountNumber, payment.dueDate]]></prog
</sect1> </sect1>
<sect1 id="queryhql-bulk"> <sect1 id="queryhql-bulk" revision="2">
<title>批量的UPDATE &amp; DELETE语句</title> <title>批量的UPDATE和DELETE</title>
<para> <para>
HQL现在支持UPDATE与DELETE语句. 查阅 HQL现在支持 <literal>update</literal>, <literal>delete</literal>
<literal>insert ... select ...</literal>语句. 查阅
<xref linkend="batch-direct"/> 以获得更多信息。 <xref linkend="batch-direct"/> 以获得更多信息。
</para> </para>
</sect1> </sect1>

View File

@ -1,5 +1,5 @@
<chapter id="querysql" revision="2"> <chapter id="querysql" revision="2">
<title>Native SQL查询<!--Native SQL--></title> <title>Native SQL查询</title>
<para> <para>
你也可以使用你的数据库的Native SQL语言来查询数据。这对你在要使用数据库的某些特性的时候(比如说在查询提示或者Oracle中的 你也可以使用你的数据库的Native SQL语言来查询数据。这对你在要使用数据库的某些特性的时候(比如说在查询提示或者Oracle中的
<literal>CONNECT</literal>关键字)这是非常有用的。这就能够扫清你把原来直接使用SQL/JDBC 的程序迁移到基于 <literal>CONNECT</literal>关键字)这是非常有用的。这就能够扫清你把原来直接使用SQL/JDBC 的程序迁移到基于
@ -10,17 +10,32 @@
Hibernate3允许你使用手写的sql来完成所有的create,update,delete,和load操作包括存储过程 Hibernate3允许你使用手写的sql来完成所有的create,update,delete,和load操作包括存储过程
</para> </para>
<sect1 id="querysql-creating"> <sect1 id="querysql-creating" revision="3">
<title>创建一个基于SQL的<literal>Query</literal></title> <title>使用<literal>SQLQuery</literal></title>
<para> <para>对原生SQL查询执行的控制是通过<literal>SQLQuery</literal>接口进行的,通过执行<literal>Session.createSQLQuery()</literal>获取这个接口。最简单的情况下,我们可以采用以下形式:</para>
SQL查询是通过<literal>SQLQuery</literal>接口来控制的它是通过调用Session.createSQLQuery()方法来获得
</para>
<programlisting><![CDATA[List cats = sess.createSQLQuery("select {cat.*} from cats cat") <programlisting><![CDATA[List cats = sess.createSQLQuery("select * from cats")
.addEntity("cat", Cat.class); .addEntity(Cat.class)
.setMaxResults(50); .list();]]></programlisting>
.list();]]></programlisting>
<para>这个查询指定了:</para>
<itemizedlist>
<listitem>
<para>SQL查询字符串</para>
</listitem>
<listitem>
<para>查询返回的实体</para>
</listitem>
</itemizedlist>
<para>这里,结果集字段名被假设为与映射文件中指明的字段名相同。对于连接了多个表的查询,这就可能造成问题,因为可能在多个表中出现同样名字的字段。下面的方法就可以避免字段名重复的问题:</para>
<programlisting><![CDATA[List cats = sess.createSQLQuery("select {cat.*} from cats cat")
.addEntity("cat", Cat.class)
.list();]]></programlisting>
<para> <para>
这个查询指定了: 这个查询指定了:
@ -44,9 +59,16 @@
</para> </para>
<para> <para>
<literal>addJoin()</literal>方法可以被用于载入其他的实体和集合的关联TODO:examples! <literal>addJoin()</literal>方法可以被用于载入其他的实体和集合的关联.
</para> </para>
<programlisting><![CDATA[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();]]></programlisting>
<para> <para>
原生的SQL查询可能返回一个简单的标量值或者一个标量和实体的结合体。 原生的SQL查询可能返回一个简单的标量值或者一个标量和实体的结合体。
</para> </para>
@ -54,6 +76,14 @@
<programlisting><![CDATA[Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat") <programlisting><![CDATA[Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE); .addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();]]></programlisting> .uniqueResult();]]></programlisting>
<para>除此之外你还可以在你的hbm文件中描述结果集映射信息在查询中使用。</para>
<programlisting><![CDATA[List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();]]></programlisting>
</sect1> </sect1>
@ -79,12 +109,90 @@ List loggedCats = sess.createSQLQuery(sql)
.list();]]></programlisting> .list();]]></programlisting>
<para> <para>
<emphasis>注意:</emphasis>如果你明确地列出了每个属性,你必须包含这个类<emphasis>和它的子类的属性</emphasis>! <emphasis>and its subclasses</emphasis>! <emphasis>注意:</emphasis>如果你明确地列出了每个属性,你必须包含<emphasis>这个类</emphasis><emphasis>它的子类</emphasis>的属性!
</para> </para>
<para>
下表列出了使用别名注射参数的不同可能性。注意:下面结果中的别名只是示例,实用时每个别名需要唯一并且不同的名字。
</para>
<table frame="topbot" id="aliasinjection-summary">
<title>别名注射(alias injection names)</title>
<tgroup cols="4">
<colspec colwidth="1*" />
<colspec colwidth="1*" />
<colspec colwidth="2.5*" />
<thead>
<row>
<entry>描述</entry>
<entry>语法</entry>
<entry>示例</entry>
</row>
</thead>
<tbody>
<row>
<entry>简单属性</entry>
<entry><literal>{[aliasname].[propertyname]</literal></entry>
<entry><literal>A_NAME as {item.name}</literal></entry>
</row>
<row>
<entry>复合属性</entry>
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
<entry><literal>CURRENCY as {item.amount.currency}, VALUE as {item.amount.value}</literal></entry>
</row>
<row>
<entry>实体辨别器(Discriminator of an entity)</entry>
<entry><literal>{[aliasname].class}</literal></entry>
<entry><literal>DISC as {item.class}</literal></entry>
</row>
<row>
<entry>实体的所有属性</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{item.*}</literal></entry>
</row>
<row>
<entry>集合键(collection key)</entry>
<entry><literal>{[aliasname].key}</literal></entry>
<entry><literal>ORGID as {coll.key}</literal></entry>
</row>
<row>
<entry>集合id</entry>
<entry><literal>{[aliasname].id}</literal></entry>
<entry><literal>EMPID as {coll.id}</literal></entry>
</row>
<row>
<entry>集合元素</entry>
<entry><literal>{[aliasname].element}</literal></entry>
<entry><literal>XID as {coll.element}</literal></entry>
<entry></entry>
</row>
<row>
<entry>集合元素的属性</entry>
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
<entry><literal>NAME as {coll.element.name}</literal></entry>
</row>
<row>
<entry>集合元素的所有属性</entry>
<entry><literal>{[aliasname].element.*}</literal></entry>
<entry><literal>{coll.element.*}</literal></entry>
</row>
<row>
<entry>集合的所有属性</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{coll.*}</literal></entry>
</row>
</tbody>
</tgroup>
</table>
</sect1> </sect1>
<sect1 id="querysql-namedqueries" revision="2"> <sect1 id="querysql-namedqueries" revision="3">
<title>命名SQL查询</title> <title>命名SQL查询</title>
<para> <para>
@ -92,17 +200,39 @@ List loggedCats = sess.createSQLQuery(sql)
需要调用<literal>addEntity()</literal>方法. 需要调用<literal>addEntity()</literal>方法.
</para> </para>
<programlisting><![CDATA[<sql-query name="mySqlQuery"> <programlisting><![CDATA[<sql-query name="persons">
<return alias="person" class="eg.Person"/> <return alias="person" class="eg.Person"/>
SELECT person.NAME AS {person.name}, SELECT person.NAME AS {person.name},
person.AGE AS {person.age}, person.AGE AS {person.age},
person.SEX AS {person.sex} person.SEX AS {person.sex}
FROM PERSON person WHERE person.NAME LIKE 'Hiber%' FROM PERSON person
WHERE person.NAME LIKE :namePattern
</sql-query>]]></programlisting> </sql-query>]]></programlisting>
<programlisting><![CDATA[List people = sess.getNamedQuery("mySqlQuery") <programlisting><![CDATA[List people = sess.getNamedQuery("persons")
.setString("namePattern", namePattern)
.setMaxResults(50) .setMaxResults(50)
.list();]]></programlisting> .list();]]></programlisting>
<para><literal>&lt;return-join&gt;</literal>
<literal>&lt;load-collection&gt;</literal> 元素是用来连接关联以及将查询定义为预先初始化各个集合的。</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para> <para>
一个命名查询可能会返回一个标量值.你必须使用<literal>&lt;return-scalar&gt;</literal>元素来指定字段的别名和 一个命名查询可能会返回一个标量值.你必须使用<literal>&lt;return-scalar&gt;</literal>元素来指定字段的别名和
@ -117,17 +247,38 @@ List loggedCats = sess.createSQLQuery(sql)
FROM PERSON p WHERE p.NAME LIKE 'Hiber%' FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query>]]></programlisting> </sql-query>]]></programlisting>
<para> <para>
<literal>&lt;return-join&gt;</literal><literal>&lt;load-collection&gt;</literal>元素分别用作 你可以把结果集映射的信息放在外部的<literal>&lt;resultset&gt;</literal>元素中,这样就可以在多个命名查询间,或者通过<literal>setResultSetMapping()</literal>API来访问。(此处原文即存疑。原文为You can externalize the resultset mapping informations in a
外连接和定义那些初始化集合的查询 <literal>&lt;resultset&gt;</literal> element to either reuse them accross
</para> several named queries or through the
<literal>setResultSetMapping()</literal> API.)
</para>
<programlisting><![CDATA[<resultset name="personAddress">
<return alias="person" class="eg.Person"/>
<return-join alias="address" property="person.mailingAddress"/>
</resultset>
<sql-query name="personsWith" resultset-ref="personAddress">
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>]]></programlisting>
<sect2 id="propertyresults"> <sect2 id="propertyresults">
<title>使用return-property来明确地指定字段/别名</title> <title>使用return-property来明确地指定字段/别名</title>
<para> <para>
使用<literal>&lt;return-property&gt;</literal>你可以明确的告诉Hibernate使用哪些字段,这和使用<literal>{}</literal>-语法 使用<literal>&lt;return-property&gt;</literal>你可以明确的告诉Hibernate使用哪些字段别名,这取代了使用<literal>{}</literal>-语法
来让Hibernate注入它自己的别名是相反的. 来让Hibernate注入它自己的别名.
</para> </para>
<programlisting><![CDATA[<sql-query name="mySqlQuery"> <programlisting><![CDATA[<sql-query name="mySqlQuery">
@ -165,19 +316,19 @@ List loggedCats = sess.createSQLQuery(sql)
</para> </para>
<para> <para>
如果你映射一个识别器(discriminator),你必须使用&lt;return-discriminator&gt;来指定识别器字段 如果你映射一个识别器(discriminator),你必须使用<literal>&lt;return-discriminator&gt;</literal> 来指定识别器字段
</para> </para>
</sect2> </sect2>
<sect2 id="sp_query"> <sect2 id="sp_query" revision="1">
<title>使用存储过程来查询</title> <title>使用存储过程来查询</title>
<para> <para>
Hibernate 3引入了对存储过程查询的支持. Hibernate 3引入了对存储过程查询(stored procedure)和函数(function)的支持.以下的说明中,这二者一般都适用。
存储过程必须返回一个结果集,作为Hibernate能够使用的第一个外部参数. 存储过程/函数必须返回一个结果集,作为Hibernate能够使用的第一个外部参数.
下面是一个Oracle9和更高版本的存储过程例子. 下面是一个Oracle9和更高版本的存储过程例子.</para>
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION selectAllEmployments <programlisting><![CDATA[CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR RETURN SYS_REFCURSOR
@ -191,8 +342,9 @@ BEGIN
FROM EMPLOYMENT; FROM EMPLOYMENT;
RETURN st_cursor; RETURN st_cursor;
END;]]></programlisting> END;]]></programlisting>
<para>
在Hibernate里要要使用这个查询,你需要通过命名查询来映射它. 在Hibernate里要要使用这个查询,你需要通过命名查询来映射它.
</para>
<programlisting><![CDATA[<sql-query name="selectAllEmployees_SP" callable="true"> <programlisting><![CDATA[<sql-query name="selectAllEmployees_SP" callable="true">
<return alias="emp" class="Employment"> <return alias="emp" class="Employment">
@ -209,14 +361,13 @@ BEGIN
</return> </return>
{ ? = call selectAllEmployments() } { ? = call selectAllEmployments() }
</sql-query>]]></programlisting> </sql-query>]]></programlisting>
</para>
<para> <para>
注意存储过程当前仅仅返回标量和实体.现在不支持<literal>&lt;return-join&gt;</literal><literal>&lt;load-collection&gt;</literal> 注意存储过程当前仅仅返回标量和实体.现在不支持<literal>&lt;return-join&gt;</literal><literal>&lt;load-collection&gt;</literal>
</para> </para>
<sect3 id="querysql-limits-storedprocedures"> <sect3 id="querysql-limits-storedprocedures" revision="1">
<title>使用存储过程的规则和限制</title> <title>使用存储过程的规则和限制</title>
<para> <para>
@ -229,23 +380,19 @@ BEGIN
对存储过程进行的查询无法使用<literal>setFirstResult()/setMaxResults()</literal>进行分页。 对存储过程进行的查询无法使用<literal>setFirstResult()/setMaxResults()</literal>进行分页。
</para> </para>
<para>建议采用的调用方式是标准SQL92: <literal>{ ? = call
functionName(&lt;parameters&gt;) }</literal> 或者 <literal>{ ? = call
procedureName(&lt;parameters&gt;}</literal>.原生调用语法不被支持。</para>
<para> <para>
对于Oracle有如下规则: 对于Oracle有如下规则:
</para> </para>
<itemizedlist spacing="compact"> <itemizedlist spacing="compact">
<listitem> <listitem>
<para> <para>函数必须返回一个结果集。存储过程的第一个参数必须是<literal>OUT</literal>它返回一个结果集。这是通过Oracle 9或10的<literal>SYS_REFCURSOR</literal>类型来完成的。在Oracle中你需要定义一个<literal>REF CURSOR</literal>类型参见Oracle的手册。</para>
存储过程必须返回一个结果集.它通过返回SYS_REFCURSOR实现(在Oracle9或10),在Oracle里你需要定义一个<literal>REF CURSOR</literal> </listitem>
类型
</para>
</listitem>
<listitem>
<para>
推荐的格式是 <literal>{ ? = call procName(&lt;parameters&gt;) }</literal>
<literal>{ ? = call procName }</literal>(这更像是Oracle规则而不是Hibernate规则)
</para>
</listitem>
</itemizedlist> </itemizedlist>
<para> <para>
@ -315,8 +462,8 @@ BEGIN
<para> <para>
你能够通过设定日志调试级别为<literal>org.hiberante.persister.entity</literal>,来查看Hibernate所期待的顺序。在这个级别下 你能够通过设定日志调试级别为<literal>org.hiberante.persister.entity</literal>,来查看Hibernate所期待的顺序。在这个级别下
Hibernate将会打印出create,update和delete实体的静态SQL。如果想看到预想中的顺序。记得不要将定制SQL包含在映射文件里 Hibernate将会打印出create,update和delete实体的静态SQL。(如果想看到预计的顺序。记得不要将定制SQL包含在映射文件里
因为他们会重载Hibernate生成的静态SQL。 因为他们会重载Hibernate生成的静态SQL。)
</para> </para>
<para> <para>
@ -348,9 +495,12 @@ END updatePerson;]]></programlisting>
你可能需要声明你自己的SQL(或HQL)来装载实体 你可能需要声明你自己的SQL(或HQL)来装载实体
</para> </para>
<programlisting><![CDATA[<sql-query name="person"> <programlisting><![CDATA[<sql-query name="person">
<return alias="p" class="Person" lock-mode="upgrade"/> <return alias="pers" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {p.name}, ID AS {p.id} FROM PERSON WHERE ID=? FOR UPDATE SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>]]></programlisting> </sql-query>]]></programlisting>
<para> <para>
@ -369,29 +519,33 @@ END updatePerson;]]></programlisting>
这也可以用于存储过程 这也可以用于存储过程
</para> </para>
<para> <para>你甚至可以定一个用于集合装载的查询:</para>
TODO: 未完成的例子
</para>
<programlisting><![CDATA[<sql-query name="organizationEmployments"> <programlisting><![CDATA[<set name="employments" inverse="true">
<load-collection alias="empcol" role="Organization.employments"/> <key/>
SELECT {empcol.*} <one-to-many class="Employment"/>
FROM EMPLOYMENT empcol <loader query-ref="employments"/>
</set>]]></programlisting>
<programlisting><![CDATA[<sql-query name="employments">
<load-collection alias="emp" role="Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
<sql-query name="organizationCurrentEmployments">
<return alias="emp" class="Employment"/>
<synchronize table="EMPLOYMENT"/>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, ID AS {emp.id}
FROM EMPLOYMENT
WHERE EMPLOYER = :id AND ENDDATE IS NULL
ORDER BY STARTDATE ASC
</sql-query>]]></programlisting> </sql-query>]]></programlisting>
</sect1> <para>你甚至还可以定义一个实体装载器,它通过连接抓取装载一个集合:</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect1>
</chapter> </chapter>

View File

@ -338,7 +338,7 @@ Indexes: cat_pkey primary key btree (cat_id)]]></programlisting>
new Configuration().configure().buildSessionFactory();]]></programlisting> new Configuration().configure().buildSessionFactory();]]></programlisting>
<para> <para>
通过对<literal>configure()</literal>的调用来装载<literal>hibernate.cfg.xml</literal>配置文件,并初始化成一<literal>Configuration</literal>实例。 通过对<literal>configure()</literal>的调用来装载<literal>hibernate.cfg.xml</literal>配置文件,并初始化<literal>Configuration</literal>实例。
在创建 <literal>SessionFactory</literal><emphasis>之前</emphasis>(它是不可变的),你可以访问<literal>Configuration</literal>来设置其他属性(甚至修改映射的元数据)。我们应该在哪儿创建<literal>SessionFactory</literal>,在我们的程序中又如何访问它呢? 在创建 <literal>SessionFactory</literal><emphasis>之前</emphasis>(它是不可变的),你可以访问<literal>Configuration</literal>来设置其他属性(甚至修改映射的元数据)。我们应该在哪儿创建<literal>SessionFactory</literal>,在我们的程序中又如何访问它呢?
<literal>SessionFactory</literal>通常只是被初始化一次,比如说通过一个<emphasis>load-on-startup</emphasis> servlet的来初始化。这意味着你不应该在serlvet中把它作为一个实例变量来持有而应该放在其他地方。进一步的说我们需要使用<emphasis>单例Singleton</emphasis>模式,我们才能更容易的在程序中访问<literal>SessionFactory</literal>。下面的方法就同时解决了两个问题:对<literal>SessionFactory</literal>的初始配置与便捷使用。 <literal>SessionFactory</literal>通常只是被初始化一次,比如说通过一个<emphasis>load-on-startup</emphasis> servlet的来初始化。这意味着你不应该在serlvet中把它作为一个实例变量来持有而应该放在其他地方。进一步的说我们需要使用<emphasis>单例Singleton</emphasis>模式,我们才能更容易的在程序中访问<literal>SessionFactory</literal>。下面的方法就同时解决了两个问题:对<literal>SessionFactory</literal>的初始配置与便捷使用。
</para> </para>

View File

@ -194,7 +194,7 @@ sess.refresh(cat); //re-read the state (after the trigger executes)]]></programl
你也可以用原生SQL(native SQL)描述查询Hibernate提供了将结果集(result set)转化为对象的部分支持。 你也可以用原生SQL(native SQL)描述查询Hibernate提供了将结果集(result set)转化为对象的部分支持。
</para> </para>
<sect2 id="objectstate-querying-executing"> <sect2 id="objectstate-querying-executing" revision="1">
<title>执行查询</title> <title>执行查询</title>
<para> <para>
@ -221,12 +221,17 @@ List kittens = session.createQuery(
Cat mother = (Cat) session.createQuery( Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?") "select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi) .setEntity(0, izi)
.uniqueResult();]]></programlisting> .uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());]]></programlisting>
<para> <para>
一个查询通常在调用<literal>list()</literal>时被执行,执行结果会完全装载进内存中的一个集合(collection)。 一个查询通常在调用<literal>list()</literal>时被执行,执行结果会完全装载进内存中的一个集合(collection)。
查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用<literal>list()</literal>的快捷方式<literal>uniqueResult()</literal> 查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用<literal>list()</literal>的快捷方式<literal>uniqueResult()</literal>
注意,使用集合预先抓取的查询往往会返回多次根对象(他们的集合类都被初始化了)。你可以通过一个集合来过滤这些重复对象。
</para> </para>
<sect3 id="objectstate-querying-executing-iterate"> <sect3 id="objectstate-querying-executing-iterate">
@ -276,7 +281,7 @@ while ( kittensAndMothers.hasNext() ) {
</sect3> </sect3>
<sect3 id="objectstate-querying-executing-scalar"> <sect3 id="objectstate-querying-executing-scalar" revision="1">
<title>标量(Scalar)结果</title> <title>标量(Scalar)结果</title>
<para> <para>
@ -289,9 +294,9 @@ while ( kittensAndMothers.hasNext() ) {
"group by cat.color") "group by cat.color")
.list() .list()
.iterator(); .iterator();
while ( results.hasNext() ) { while ( results.hasNext() ) {
Object[] row = results.next(); Object[] row = (Object[]) results.next();
Color type = (Color) row[0]; Color type = (Color) row[0];
Date oldest = (Date) row[1]; Date oldest = (Date) row[1];
Integer count = (Integer) row[2]; Integer count = (Integer) row[2];
@ -932,7 +937,9 @@ sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed! // change to izi is not flushed!
... ...
tx.commit(); // flush occurs]]></programlisting> tx.commit(); // flush occurs
sess.close();]]></programlisting>
<para> <para>
刷出(flush)期间可能会抛出异常。例如一个DML操作违反了约束 刷出(flush)期间可能会抛出异常。例如一个DML操作违反了约束
@ -941,7 +948,7 @@ tx.commit(); // flush occurs]]></programlisting>
</sect1> </sect1>
<sect1 id="objectstate-transitive"> <sect1 id="objectstate-transitive" revision="1">
<title>传播性持久化(transitive persistence)</title> <title>传播性持久化(transitive persistence)</title>
<para> <para>
@ -1064,6 +1071,16 @@ tx.commit(); // flush occurs]]></programlisting>
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para>
Finally, note that cascading of operations can be applied to an object graph at
<emphasis>call time</emphasis> or at <emphasis>flush time</emphasis>. All operations,
if enabled, are cascaded to associated entities reachable when the operation is
executed. However, <literal>save-upate</literal> and <literal>delete-orphan</literal>
are transitive for all associated entities reachable during flush of the
<literal>Session</literal>.
最后,注意操作的级联可能是在<emphasis>调用期(call time)</emphasis>或者<emphasis>写入期(flush time)</emphasis>作用到对象图上的。所有的操作,假若允许,都在操作被执行的时候级联到可触及的关联实体上。然而,<literal>save-upate</literal><literal>delete-orphan</literal>是在<literal>Session</literal> flush的时候才作用到所有可触及的被关联对象上的。
</para>
</sect1> </sect1>

View File

@ -51,48 +51,72 @@
首先要定制你的映射文件来改善生成的schema。 首先要定制你的映射文件来改善生成的schema。
</para> </para>
<sect2 id="toolsetguide-s1-2" revision="1"> <sect2 id="toolsetguide-s1-2" revision="3">
<title>对schema定制化(Customizing the schema)</title> <title>对schema定制化(Customizing the schema)</title>
<para> <para>
很多Hibernate映射元素定义了一个可选的<literal>length</literal>属性。你可以通过这个属性设置字段的长度。 (如果是Or, for numeric/decimal data types, the precision.) 很多Hibernate映射元素定义了可选的<literal>length</literal><literal>precision</literal> 或者 <literal>scale</literal>属性。你可以通过这个属性设置字段的长度、精度、小数点位数
</para> </para>
<para> <programlisting><![CDATA[<property name="zip" length="5"/>]]></programlisting>
有些tag接受<literal>not-null</literal>属性(用来在表字段上生成<literal>NOT NULL</literal>约束)和<literal>unique</literal>属性(用来在表字段上生成<literal>UNIQUE</literal>约束)。 <programlisting><![CDATA[<property name="balance" precision="12" scale="2"/>]]></programlisting>
</para>
<para> <para>
有些tag接受<literal>index</literal>属性用来指定字段的index名字。<literal>unique-key</literal>属性可以对成组的字段指定一个组合键约束(unit key constraint)。目前,<literal>unique-key</literal>属性指定的值<emphasis>并不会</emphasis>被当作这个约束的名字,它们只是在用来在映射文件内部用作区分的 有些tag还接受<literal>not-null</literal>属性(用来在表字段上生成<literal>NOT NULL</literal>约束)和<literal>unique</literal>属性(用来在表字段上生成<literal>UNIQUE</literal>约束)
</para> </para>
<programlisting><![CDATA[<many-to-one name="bar" column="barId" not-null="true"/>]]></programlisting>
<programlisting><![CDATA[<element column="serialNumber" type="long" not-null="true" unique="true"/>]]></programlisting>
<para>
<literal>unique-key</literal>属性可以对成组的字段指定一个唯一键约束(unique key constraint)。目前,<literal>unique-key</literal>属性指定的值在生成DDL时<emphasis>并不会</emphasis>被当作这个约束的名字,它们只是在用来在映射文件内部用作区分的。
</para>
<programlisting><![CDATA[<many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/>
<property name="employeeId" unique-key="OrgEmployee"/>]]></programlisting>
<para> <para>
示例: <literal>index</literal>属性会用对应的字段一个或多个生成一个index,它指出了这个index的名字。如果多个字段对应的index名字相同就会生成包含这些字段的index。
</para> </para>
<programlisting><![CDATA[<property name="foo" type="string" length="64" not-null="true"/> <programlisting><![CDATA[<property name="lastName" index="CustName"/>
<property name="firstName" index="CustName"/>]]></programlisting>
<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>
<element column="serial_number" type="long" not-null="true" unique="true"/>]]></programlisting>
<para> <para>
另外,这些元素还接受<literal>&lt;column&gt;</literal>子元素。在定义跨越多字段的类型时特别有用 <literal>foreign-key</literal>属性可以用来覆盖任何生成的外键约束的名字。
</para> </para>
<programlisting><![CDATA[<property name="foo" type="string"> <programlisting><![CDATA[<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>]]></programlisting>
<column name="foo" length="64" not-null="true" sql-type="text"/>
</property>
<property name="bar" type="my.customtypes.MultiColumnType"/> <para>
<column name="fee" not-null="true" index="bar_idx"/> 很多映射元素还接受<literal>&lt;column&gt;</literal>子元素。这在定义跨越多字段的类型时特别有用。
<column name="fi" not-null="true" index="bar_idx"/> </para>
<column name="fo" not-null="true" index="bar_idx"/>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
<literal>default</literal>属性为字段指定一个默认值 (在保存被映射的类的新实例之前,你应该将同样的值赋于对应的属性)。
</para>
<programlisting><![CDATA[<property name="credits" type="integer" insert="false">
<column name="credits" default="10"/>
</property>]]></programlisting>
<programlisting><![CDATA[<version name="version" type="integer" insert="false">
<column name="version" default="0"/>
</property>]]></programlisting> </property>]]></programlisting>
<para> <para>
<literal>sql-type</literal>属性允许用户覆盖默认的Hibernate类型到SQL数据类型的映射。 <literal>sql-type</literal>属性允许用户覆盖默认的Hibernate类型到SQL数据类型的映射。
</para> </para>
<programlisting><![CDATA[<property name="balance" type="float">
<column name="balance" sql-type="decimal(13,3)"/>
</property>]]></programlisting>
<para> <para>
<literal>check</literal>属性允许用户指定一个约束检查。 <literal>check</literal>属性允许用户指定一个约束检查。
@ -100,9 +124,9 @@
<programlisting><![CDATA[<property name="foo" type="integer"> <programlisting><![CDATA[<property name="foo" type="integer">
<column name="foo" check="foo > 10"/> <column name="foo" check="foo > 10"/>
</property> </property>]]></programlisting>
<class name="Foo" table="foos" check="bar < 100.0"> <programlisting><![CDATA[<class name="Foo" table="foos" check="bar < 100.0">
... ...
<property name="bar" type="float"/> <property name="bar" type="float"/>
</class>]]></programlisting> </class>]]></programlisting>
@ -124,9 +148,19 @@
<row> <row>
<entry><literal>length</literal></entry> <entry><literal>length</literal></entry>
<entry>数字</entry> <entry>数字</entry>
<entry>字段长度/小数点精度</entry> <entry>字段长度</entry>
</row> </row>
<row>
<entry><literal>precision</literal></entry>
<entry>数字</entry>
<entry>精度(decimal precision)</entry>
</row>
<row>
<entry><literal>scale</literal></entry>
<entry>数字</entry>
<entry>小数点位数(decimal scale)</entry>
</row>
<row> <row>
<entry><literal>not-null</literal></entry> <entry><literal>not-null</literal></entry>
<entry><literal>true|false</literal></entry> <entry><literal>true|false</literal></entry>
@ -151,16 +185,30 @@
<entry><literal>foreign-key</literal></entry> <entry><literal>foreign-key</literal></entry>
<entry><literal>foreign_key_name</literal></entry> <entry><literal>foreign_key_name</literal></entry>
<entry> <entry>
指明一个外键的名字,它是为关联生成的。 specifies the name of the foreign key constraint generated
for an association, for a <literal>&lt;one-to-one&gt;</literal>,
<literal>&lt;many-to-one&gt;</literal>, <literal>&lt;key&gt;</literal>,
or <literal>&lt;many-to-many&gt;</literal> mapping element. Note that
<literal>inverse="true"</literal> sides will not be considered
by <literal>SchemaExport</literal>.
指明一个外键的名字,它是为关联生成的,或者<literal>&lt;one-to-one&gt;</literal><literal>&lt;many-to-one&gt;</literal>, <literal>&lt;key&gt;</literal>, 或者<literal>&lt;many-to-many&gt;</literal>映射元素。注意<literal>inverse="true"</literal><literal>SchemaExport</literal>时会被忽略。
</entry> </entry>
</row> </row>
<row> <row>
<entry><literal>sql-type</literal></entry> <entry><literal>sql-type</literal></entry>
<entry><literal>column_type</literal></entry> <entry><literal>SQL 字段类型</literal></entry>
<entry> <entry>
覆盖默认的字段类型(只能用于<literal>&lt;column&gt;</literal>属性) 覆盖默认的字段类型(只能用于<literal>&lt;column&gt;</literal>属性)
</entry> </entry>
</row> </row>
<row>
<entry><literal>default</literal></entry>
<entry>SQL表达式</entry>
<entry>
为字段指定默认值
</entry>
</row>
<row> <row>
<entry><literal>check</literal></entry> <entry><literal>check</literal></entry>
<entry>SQL 表达式</entry> <entry>SQL 表达式</entry>
@ -173,9 +221,29 @@
</tgroup> </tgroup>
</table> </table>
<para>
<literal>&lt;comment&gt;</literal>元素可以让你在生成的schema中加入注释。
</para>
<programlisting><![CDATA[<class name="Customer" table="CurCust">
<comment>Current customers only</comment>
...
</class>]]></programlisting>
<programlisting><![CDATA[<property name="balance">
<column name="bal">
<comment>Balance in USD</comment>
</column>
</property>]]></programlisting>
<para>
结果是在生成的DDL中包含<literal>comment on table</literal> 或者
<literal>comment on column</literal>语句(假若支持的话)。
</para>
</sect2> </sect2>
<sect2 id="toolsetguide-s1-3"> <sect2 id="toolsetguide-s1-3" revision="2">
<title>运行该工具</title> <title>运行该工具</title>
<para> <para>
@ -207,6 +275,10 @@
<entry><literal>--drop</literal></entry> <entry><literal>--drop</literal></entry>
<entry>只进行drop tables的步骤</entry> <entry>只进行drop tables的步骤</entry>
</row> </row>
<row>
<entry><literal>--create</literal></entry>
<entry>只创建表</entry>
</row>
<row> <row>
<entry><literal>--text</literal></entry> <entry><literal>--text</literal></entry>
<entry>不执行在数据库中运行的步骤</entry> <entry>不执行在数据库中运行的步骤</entry>
@ -215,6 +287,10 @@
<entry><literal>--output=my_schema.ddl</literal></entry> <entry><literal>--output=my_schema.ddl</literal></entry>
<entry>把输出的ddl脚本输出到一个文件</entry> <entry>把输出的ddl脚本输出到一个文件</entry>
</row> </row>
<row>
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
<entry>选择一个命名策略(<literal>NamingStrategy</literal>)</entry>
</row>
<row> <row>
<entry><literal>--config=hibernate.cfg.xml</literal></entry> <entry><literal>--config=hibernate.cfg.xml</literal></entry>
<entry>从XML文件读入Hibernate配置</entry> <entry>从XML文件读入Hibernate配置</entry>
@ -228,7 +304,7 @@
<entry>把脚本中的SQL语句对齐和美化</entry> <entry>把脚本中的SQL语句对齐和美化</entry>
</row> </row>
<row> <row>
<entry><literal>--delimiter=x</literal></entry> <entry><literal>--delimiter=;</literal></entry>
<entry>为脚本设置行结束符</entry> <entry>为脚本设置行结束符</entry>
</row> </row>
</tbody> </tbody>
@ -332,7 +408,7 @@ new SchemaExport(cfg).create(false, true);]]></programlisting>
</sect2> </sect2>
<sect2 id="toolsetguide-s1-6"> <sect2 id="toolsetguide-s1-6" revision="2">
<title>对schema的增量更新(Incremental schema updates)</title> <title>对schema的增量更新(Incremental schema updates)</title>
<para> <para>
@ -360,10 +436,22 @@ new SchemaExport(cfg).create(false, true);]]></programlisting>
<entry><literal>--quiet</literal></entry> <entry><literal>--quiet</literal></entry>
<entry>不要把脚本输出到stdout</entry> <entry>不要把脚本输出到stdout</entry>
</row> </row>
<row>
<entry><literal>--text</literal></entry>
<entry>不把脚本输出到数据库</entry>
</row>
<row>
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
<entry>选择一个命名策略 (<literal>NamingStrategy</literal>)</entry>
</row>
<row> <row>
<entry><literal>--properties=hibernate.properties</literal></entry> <entry><literal>--properties=hibernate.properties</literal></entry>
<entry>从指定文件读入数据库属性</entry> <entry>从指定文件读入数据库属性</entry>
</row> </row>
<row>
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
<entry>指定一个 <literal>.cfg.xml</literal>文件</entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
@ -400,6 +488,76 @@ new SchemaUpdate(cfg).execute(false);]]></programlisting>
</sect2> </sect2>
<sect2 id="toolsetguide-s1-8" revision="1">
<title>Schema 校验</title>
<para>
<literal>SchemaValidator</literal>工具会比较数据库现状是否与映射文档“匹配”。注意,<literal>SchemaValidator</literal> 严重依赖于JDBC的metadata API因此不是对所有的JDBC驱动都适用。这一工具在测试的时候特别有用。
</para>
<para>
<literal>java -cp </literal><emphasis>hibernate_classpaths</emphasis>
<literal>org.hibernate.tool.hbm2ddl.SchemaValidator</literal> <emphasis>options mapping_files</emphasis>
</para>
<table frame="topbot">
<title><literal>SchemaValidator</literal>命令行参数</title>
<tgroup cols="2">
<colspec colwidth="1.5*"/>
<colspec colwidth="2*"/>
<thead>
<row>
<entry>选项</entry>
<entry>描述</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
<entry>选择一个命名策略 (<literal>NamingStrategy</literal>)</entry>
</row>
<row>
<entry><literal>--properties=hibernate.properties</literal></entry>
<entry>从文件中读取数据库属性</entry>
</row>
<row>
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
<entry>指定一个<literal>.cfg.xml</literal>文件</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
你可以在你的应用程序中嵌入<literal>SchemaValidator</literal>
</para>
<programlisting><![CDATA[Configuration cfg = ....;
new SchemaValidator(cfg).validate();]]></programlisting>
</sect2>
<sect2 id="toolsetguide-s1-9">
<title>使用Ant进行schema校验</title>
<para>
你可以在Ant脚本中调用<literal>SchemaValidator</literal>:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect2>
</sect1> </sect1>
</chapter> </chapter>

View File

@ -1,82 +1,81 @@
<chapter id="transactions" revision="1"> <chapter id="transactions" revision="2">
<title>事务和并发</title> <title>事务和并发</title>
<para> <para>
Hibernate的事务和并发控制很容易掌握。Hibernate直接使用JDBC连接和JTA资源不添加任何附加锁定 Hibernate的事务和并发控制很容易掌握。Hibernate直接使用JDBC连接和JTA资源不添加任何附加锁定
行为。我们强烈推荐你花点时间了解JDBC编程ANSI SQL查询语言和你使用 行为。我们强烈推荐你花点时间了解JDBC编程ANSI SQL查询语言和你使用
的数据库系统的事务隔离规范。Hibernate只添加自动版本管理而不会锁 的数据库系统的事务隔离规范。
定内存中的对象,也不会改变数据库事务的隔离级别。基本上,使用
Hibernate就好像直接使用JDBC(或者JTA/CMT)来访问你的数据库资源。
</para> </para>
<para>
Hibernate不锁定内存中的对象。你的应用程序会按照你的数据库事务的隔离级别规定的那样运作。幸亏有了<literal>Session</literal>使得Hibernate通过标识符查找和实体查询不是返回标量值的报表查询提供了可重复的读取Repeatable reads功能<literal>Session</literal>同时也是事务范围内的缓存cache
</para>
<para> <para>
除了自动版本管理针对行级悲观锁定Hibernate也提供了辅助的API它使用了 除了自动乐观并发控制提供版本管理针对行级悲观锁定Hibernate也提供了辅助的(较小的)API它使用了
<literal>SELECT FOR UPDATE</literal>的SQL语法。本章后面会讨论这个API。 <literal>SELECT FOR UPDATE</literal>的SQL语法。本章后面会讨论乐观并发控制和这个API。
</para> </para>
<para> <para>
我们从<literal>Configuration</literal>层、<literal>SessionFactory</literal>层, 和 我们从<literal>Configuration</literal>层、<literal>SessionFactory</literal>层, 和
<literal>Session</literal>层开始讨论Hibernate的并行控制、数据库事务和应用 <literal>Session</literal>层开始讨论Hibernate的并行控制、数据库事务和应用
程序的长事务。 程序的长事务。
</para> </para>
<sect1 id="transactions-basics"> <sect1 id="transactions-basics" revision="1">
<title>Session和事务范围(transaction scopes)</title> <title>Session和事务范围(transaction scope)</title>
<para> <para>
一个<literal>SessionFactory</literal>对象的创建代价很昂贵,它是线程安全的对象,它被设计成可以 <literal>SessionFactory</literal>对象的创建代价很昂贵,它是线程安全的对象,它为所有的应用程序线程所共享。它只创建一次,通常是在应用程序启动的时候,由一个<literal>Configuraion</literal>的实例来创建。
为所有的应用程序线程所共享。它只创建一次,通常是在应用程序启动的时候,由一个
<literal>Configuraion</literal>的实例来创建。
</para> </para>
<para> <para>
一个<literal>Session</literal>的对象是轻型的,非线程安全的,对于单个业务进程,单个的 <literal>Session</literal>对象的创建代价比较小,是非线程安全的,对于单个请求,单个会话、单个的
工作单元而言,它只被使用一次,然后就丢弃。只有在需要的时候,<literal>Session</literal> 工作单元而言,它只被使用一次,然后就丢弃。只有在需要的时候,一个<literal>Session</literal>对象
才会获取一个JDBC的<literal>Connection</literal>(或一个<literal>Datasource</literal> 才会获取一个JDBC的<literal>Connection</literal>(或一个<literal>Datasource</literal>
对象。所以你可以放心的打开和关闭<literal>Session</literal>,甚至当你并不确定一个特定的请 对象,因此假若不使用的时候它不消费任何资源。
求是否需要数据访问时,你也可以这样做。(一旦你实现下面提到的使用了请求拦截的模式,这就
变得很重要了。
</para> </para>
<para> <para>
此外我们还要考虑数据库事务。数据库事务应该尽可能的短,降低数据库锁定造成的资源争用。 此外我们还要考虑数据库事务。数据库事务应该尽可能的短,降低数据库中的锁争用。
数据库长事务会导致你的应用程序无法扩展到高的并发负载 数据库长事务会阻止你的应用程序扩展到高的并发负载。因此,假若在用户思考期间让数据库事务开着,直到整个工作单元完成才关闭这个事务,这绝不是一个好的设计
</para> </para>
<para> <para>
一个操作单元(Unit of work)的范围是多大单个的Hibernate <literal>Session</literal>能跨越多个 一个操作单元(Unit of work)的范围是多大单个的Hibernate <literal>Session</literal>能跨越多个
数据库事务吗?还是一个<literal>Session</literal>的作用范围对应一个数据库事务的范围?应该何时打开 数据库事务吗?还是一个<literal>Session</literal>的作用范围对应一个数据库事务的范围?应该何时打开
<literal>Session</literal>,何时关闭<literal>Session</literal>?,你又如何划分数据库事务的边界呢? <literal>Session</literal>,何时关闭<literal>Session</literal>?,你又如何划分数据库事务的边界呢?
</para> </para>
<sect2 id="transactions-basics-uow"> <sect2 id="transactions-basics-uow" revision="1">
<title>操作单元(Unit of work)</title> <title>操作单元(Unit of work)</title>
<para> <para>
首先,别<emphasis>session-per-operation</emphasis>这种反模式了,也就是说,在单个线程中, 首先,别用<emphasis>session-per-operation</emphasis>这种反模式了,也就是说,在单个线程中,
不要因为一次简单的数据库调用,就打开和关闭一次<literal>Session</literal>!数据库事务也是如此。 不要因为一次简单的数据库调用,就打开和关闭一次<literal>Session</literal>!数据库事务也是如此。
应用程序中的数据库调用是按照计划好的次序,分组为原子的操作单元。(注意,这也意味着,应用程 应用程序中的数据库调用是按照计划好的次序,分组为原子的操作单元。(注意,这也意味着,应用程
序中在单个的SQL语句发送之后自动事务提交(auto-commit)模式失效了。这种模式专门为SQL控制台操作设计的。 序中在单个的SQL语句发送之后自动事务提交(auto-commit)模式失效了。这种模式专门为SQL控制台操作设计的。
Hibernate禁止立即自动事务提交模式或者期望应用服务器禁止立即自动事务提交模式。 Hibernate禁止立即自动事务提交模式或者期望应用服务器禁止立即自动事务提交模式。数据库事务绝不是可有可无的任何与数据库之间的通讯都必须在某个事务中进行不管你是在读还是在写数据。对读数据而言应该避免auto-commit行为因为很多小的事务比一个清晰定义的工作单元性能差。后者也更容易维护和扩展。
</para> </para>
<para> <para>
在多用户的client/server应用程序中最常用的模式是 <emphasis>每个请求一个会话(session-per-request)</emphasis> 在多用户的client/server应用程序中最常用的模式是 <emphasis>每个请求一个会话(session-per-request)</emphasis>
在这种模式下来自客户端的请求被发送到服务器端即Hibernate持久化层运行的地方 在这种模式下来自客户端的请求被发送到服务器端即Hibernate持久化层运行的地方
个新的Hibernate <literal>Session</literal>被打开,并且执行这个操作单元中所有的数据库操作。 个新的Hibernate <literal>Session</literal>被打开,并且执行这个操作单元中所有的数据库操作。
一旦操作完成(同时发送到客户端的响应也准备就绪session被同步然后关闭。你也可以使用单 一旦操作完成(同时客户端的响应也准备就绪session被同步然后关闭。你也可以使用单
个数据库事务来处理客户端请求,在你打开<literal>Session</literal>之后启动事务,在你关闭 个数据库事务来处理客户端请求,在你打开<literal>Session</literal>之后启动事务,在你关闭
<literal>Session</literal>之前提交事务。会话和请求之间的关系是一对一的关系,这种模式对 <literal>Session</literal>之前提交事务。会话和请求之间的关系是一对一的关系,这种模式对
于大多数应用程序来说是很棒的。 于大多数应用程序来说是很棒的。
</para> </para>
<para> <para>
真正的挑战在于如何去实现这种模式:不仅<literal>Session</literal>和事务必须被正确的开始和结束, 实现才是真正的挑战。Hibernate内置了对"当前session(current session)" 的管理,用于简化此模式。你要做的一切就是在服务器端要处理请求的时候,开启事务,在响应发送给客户之前结束事务。你可以用任何方式来完成这一操作,通常的方案有<literal>ServletFilter</literal>在service方法中进行pointcut的AOP拦截器或者proxy/interception容器。EJB容器是实现横切诸如EJB session bean上的事务分界用CMT对事务进行声明等方面的标准手段。假若你决定使用编程式的事务分界请参考本章后面讲到的Hibernate <literal>Transaction</literal> API这对易用性和代码可移植性都有好处。
而且他们也必须能被数据访问操作访问。用拦截器来实现操作单元的划分,该拦截器在客户端请求达到服 </para>
务器端的时候开始,在服务器端发送响应(即,<literal>ServletFilter</literal>)之前结束。我们推荐
使用一个<literal>ThreadLocal</literal> 变量,把 <literal>Session</literal>绑定到处理客户端请求的线 <para>
程上去。这种方式可以让运行在该线程上的所有程序代码轻松的访问<literal>Session</literal>(就像访问一 在任何时间,任何地方,你的应用代码可以通过简单的调用<literal>sessionFactory.getCurrentSession()</literal>来访问"当前session",用于处理请求。你总是会得到当前数据库事务范围内的<literal>Session</literal>。在使用本地资源或JTA环境时必须配置它请参见<xref linkend="architecture-current-session"/>
个静态变量那样)。你也可以在一个<literal>ThreadLocal</literal> 变量中保持事务上下文环境,不过这依赖 </para>
于你所选择的数据库事务划分机制。这种实现模式被称之为 <emphasis>ThreadLocal Session</emphasis>
<emphasis>Open Session in View</emphasis>。你可以很容易的扩展本文前面章节展示的 <para>
<literal>HibernateUtil</literal> 辅助类来实现这种模式。当然,你必须找到一种实现拦截器的方法,并 有时,将<literal>Session</literal>和数据库事务的边界延伸到"展示层被渲染后"会带来便利。有些serlvet应用程序在对请求进行处理后有个单独的渲染期这种延伸对这种程序特别有用。假若你实现你自己的拦截器把事务边界延伸到展示层渲染结束后非常容易。然而假若你依赖有容器管理事务的EJB这就不太容易了因为事务会在EJB方法返回后结束而那是在任何展示层渲染开始之前。请访问Hibernate网站和论坛你可以找到<emphasis>Open Session in View</emphasis>这一模式的提示和示例。
且可以把拦截器集成到你的应用环境中。请参考Hibernate网站上面的提示和例子。 </para>
</para>
</sect2> </sect2>
<sect2 id="transactions-basics-apptx"> <sect2 id="transactions-basics-apptx" revision="1">
<title>应用程序事务(Application transactions)</title> <title>长对话</title>
<para> <para>
session-per-request模式不仅仅是一个可以用来设计操作单元的有用概念。很多业务处理流程都需 session-per-request模式不仅仅是一个可以用来设计操作单元的有用概念。很多业务处理都需
要一系列完整的和用户之间的交互,即用户对数据库的交叉访问。在基于web的应用和企业 要一系列完整的与用户之间的交互,而这些用户是指对数据库有交叉访问的用户。在基于web的应用和企业
应用中,跨用户交互的数据库事务是无法接受的。考虑下面的例子: 应用中,跨用户交互的数据库事务是无法接受的。考虑下面的例子:
</para> </para>
<itemizedlist> <itemizedlist>
@ -95,17 +94,17 @@
</itemizedlist> </itemizedlist>
<para> <para>
从用户的角度来看,我们把这个操作单元称为<emphasis>应用程序长事务</emphasis>application transaction 从用户的角度来看,我们把这个操作单元称为长时间运行的<emphasis>对话</emphasis>conversation,或者(or <emphasis>应用事务</emphasis>,application transaction)
在你的应用程序中,可以有很多种方法来实现它。 在你的应用程序中,可以有很多种方法来实现它。
</para> </para>
<para> <para>
头一个幼稚的做法是,在用户思考的过程中,保持<literal>Session</literal>和数据库事务是打开的, 头一个幼稚的做法是,在用户思考的过程中,保持<literal>Session</literal>和数据库事务是打开的,
保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操作。这种方式当然是一个反模式, 保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操作。这种方式当然是一个反模式,
因为数据库锁定的维持会导致应用程序无法扩展并发用户的数目。 因为锁争用会导致应用程序无法扩展并发用户的数目。
</para> </para>
<para> <para>
很明显,我们必须使用多个数据库事务来实现一个应用程序事务。在这个例子中,维护业务处理流程 很明显,我们必须使用多个数据库事务来实现这个对话。在这个例子中,维护业务处理
事务隔离变成了应用程序层的部分责任。单个应用程序事务通常跨越多个数据库事务。如果仅仅只有一 事务隔离变成了应用程序层的部分责任。一个对话通常跨越多个数据库事务。如果仅仅只有一
个数据库事务(最后的那个事务)保存更新过的数据,而所有其他事务只是单纯的读取数据(例如在一 个数据库事务(最后的那个事务)保存更新过的数据,而所有其他事务只是单纯的读取数据(例如在一
个跨越多个请求/响应周期的向导风格的对话框中),那么应用程序事务将保证其原子性。这种方式比听 个跨越多个请求/响应周期的向导风格的对话框中),那么应用程序事务将保证其原子性。这种方式比听
起来还要容易实现特别是当你使用了Hibernate的下述特性的时候 起来还要容易实现特别是当你使用了Hibernate的下述特性的时候
@ -115,7 +114,7 @@
<listitem> <listitem>
<para> <para>
<emphasis>自动版本化</emphasis> - Hibernate能够自动进行乐观并发控制 ,如果在用户思考 <emphasis>自动版本化</emphasis> - Hibernate能够自动进行乐观并发控制 ,如果在用户思考
的过程中发生并发修改冲突Hibernate能够自动检测到。 的过程中发生并发修改Hibernate能够自动检测到。一般我们只在对话结束时才检查。
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -129,17 +128,17 @@
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<emphasis>长生命周期的Session</emphasis> Long Session- Hibernate 的<literal>Session</literal> <emphasis>Extended (or Long) Session</emphasis> - Hibernate 的<literal>Session</literal>
可以在数据库事务提交之后和底层的JDBC连接断开当一个新的客户端请求到来的时候它又重新连接上底层的 可以在数据库事务提交之后和底层的JDBC连接断开当一个新的客户端请求到来的时候它又重新连接上底层的
JDBC连接。这种模式被称之为<emphasis>session-per-application-transaction</emphasis>,这种情况可 JDBC连接。这种模式被称之为<emphasis>session-per-conversation</emphasis>,这种情况可
能会造成不必要的Session和JDBC连接的重新关联。自动版本化被用来隔离并发修改。 能会造成不必要的Session和JDBC连接的重新关联。自动版本化被用来隔离并发修改, <literal>Session</literal>通常不允许自动flush,而是明确flush
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para> <para>
<emphasis>session-per-request-with-detached-objects</emphasis> <emphasis>session-per-request-with-detached-objects</emphasis>
<emphasis>session-per-application-transaction</emphasis> 各有优缺点,我们在本章后面乐观并发 <emphasis>session-per-conversation</emphasis> 各有优缺点,我们在本章后面乐观并发
控制那部分再进行讨论。 控制那部分再进行讨论。
</para> </para>
</sect2> </sect2>
@ -212,7 +211,7 @@
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
<literal>Session</literal>一个非线程安全的。如果一个<literal>Session</literal> <literal>Session</literal> 对象是非线程安全的。如果一个<literal>Session</literal>
实例允许共享的话那些支持并发运行的东东例如HTTP requestsession beans,或者是 实例允许共享的话那些支持并发运行的东东例如HTTP requestsession beans,或者是
Swing workers将会导致出现资源争用race condition。如果在<literal>HttpSession</literal>中有 Swing workers将会导致出现资源争用race condition。如果在<literal>HttpSession</literal>中有
Hibernate 的<literal>Session</literal>的话稍后讨论你应该考虑同步访问你的Http session。 Hibernate 的<literal>Session</literal>的话稍后讨论你应该考虑同步访问你的Http session。
@ -251,18 +250,18 @@
<para> <para>
数据库(或者系统)事务的声明总是必须的。在数据库事务之外,就无法和数据库通讯(这可能会让那些习惯于 数据库(或者系统)事务的声明总是必须的。在数据库事务之外,就无法和数据库通讯(这可能会让那些习惯于
自动提交事务模式的开发人员感到迷惑)。永远使用清晰的事务声明,即使只读操作也是如此。进行 自动提交事务模式的开发人员感到迷惑)。永远使用清晰的事务声明,即使只读操作也是如此。进行
显式的事务声明并不总是需要的,这取决于你的事务隔离级别和数据库的能力,但不管怎么说,声明事务总归有益无害。 显式的事务声明并不总是需要的,这取决于你的事务隔离级别和数据库的能力,但不管怎么说,声明事务总归有益无害。当然,一个单独的数据库事务总是比很多琐碎的事务性能更好,即时对读数据而言也是一样。
</para> </para>
<para> <para>
一个Hibernate应用程序可以运行在非托管环境中也就是独立运行的应用程序简单Web应用程序 一个Hibernate应用程序可以运行在非托管环境中也就是独立运行的应用程序简单Web应用程序
或者Swing图形桌面应用程序也可以运行在托管的J2EE环境中。在一个非托管环境中Hibernate 或者Swing图形桌面应用程序也可以运行在托管的J2EE环境中。在一个非托管环境中Hibernate
通常自己负责管理数据库连接池。应用程序开发人员必须手工设置事务声明,换句话说,就是手工启 通常自己负责管理数据库连接池。应用程序开发人员必须手工设置事务声明,换句话说,就是手工启
动,提交,或者回滚数据库事务。一个托管的环境通常提供了容器管理事务,例如事务装配通过可声 动,提交,或者回滚数据库事务。一个托管的环境通常提供了容器管理事务(CMT),例如事务装配通过可声
明的方式定义在EJB session beans的部署描述符中。可编程式事务声明不再需要即使是 明的方式定义在EJB session beans的部署描述符中。可编程式事务声明不再需要即使是
<literal>Session</literal> 的同步也可以自动完成。 <literal>Session</literal> 的同步也可以自动完成。
</para> </para>
<para> <para>
让持久层具备可移植性是人们的理想。Hibernate提供了一套称为<literal>Transaction</literal>的封装API 让持久层具备可移植性是人们的理想,这种移植发生在非托管的本地资源环境与依赖JTA但是使用BMT而非CMT的系统之间。在两种情况下你都可以使用编程式的事务管理。Hibernate提供了一套称为<literal>Transaction</literal>的封装API
用来把你的部署环境中的本地事务管理系统转换到Hibernate事务上。这个API是可选的但是我们强烈 用来把你的部署环境中的本地事务管理系统转换到Hibernate事务上。这个API是可选的但是我们强烈
推荐你使用除非你用CMT session bean。 推荐你使用除非你用CMT session bean。
</para> </para>
@ -297,10 +296,10 @@
session的同步(flush,刷出)前面已经讨论过了,我们现在进一步考察在托管和非托管环境下的事务声明和异常处理。 session的同步(flush,刷出)前面已经讨论过了,我们现在进一步考察在托管和非托管环境下的事务声明和异常处理。
</para> </para>
<sect2 id="transactions-demarcation-nonmanaged"> <sect2 id="transactions-demarcation-nonmanaged" revision="2">
<title>非托管环境</title> <title>非托管环境</title>
<para> <para>
如果Hibernat持久层运行在一个非托管环境中数据库连接通常由Hibernate的连接池机制 如果Hibernat持久层运行在一个非托管环境中数据库连接通常由Hibernate的简单即非DataSource)连接池机制
来处理。session/transaction处理方式如下所示 来处理。session/transaction处理方式如下所示
</para> </para>
<programlisting><![CDATA[//Non-managed environment idiom <programlisting><![CDATA[//Non-managed environment idiom
@ -323,38 +322,47 @@ finally {
}]]></programlisting> }]]></programlisting>
<para> <para>
你不需要显式<literal>flush()</literal> <literal>Session</literal> - 你不需要显式<literal>flush()</literal> <literal>Session</literal> -
<literal>commit()</literal>的调用会自动触发session的同步。 <literal>commit()</literal>的调用会自动触发session的同步(取决于session的<xref linkend="objectstate-flushing">FlushMode</xref>)。调用 <literal>close()</literal> 标志session的结束。<literal>close()</literal>方法重要的暗示是,<literal>session</literal>释放了JDBC连接。这段Java代码在非托管环境下和JTA环境下都可以运行
</para> </para>
<para> <para>
调用 <literal>close()</literal> 标志session的结束。 更加灵活的方案是Hibernate内置的"current session"上下文管理,前文已经讲过:
<literal>close()</literal>方法重要的暗示是,<literal>session</literal>释放了JDBC连接。
</para> </para>
<para> <programlisting><![CDATA[// Non-managed environment idiom with getCurrentSession()
这段Java代码是可移植的可以在非托管环境和JTA环境中运行。 try {
</para> factory.getCurrentSession().beginTransaction();
// do some work
...
factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // or display error message
}]]></programlisting>
<para> <para>
你很可能从未在一个标准的应用程序的业务代码中见过这样的用法;致命的(系统)异常应该总是 你很可能从未在一个通常的应用程序的业务代码中见过这样的代码片断:致命的(系统)异常应该总是
在应用程序“顶层”被捕获。换句话说执行Hibernate调用的代码在持久层和处理 在应用程序“顶层”被捕获。换句话说执行Hibernate调用的代码在持久层和处理
<literal>RuntimeException</literal>异常的代码(通常只能清理和退出应用程序)应该在不同 <literal>RuntimeException</literal>异常的代码(通常只能清理和退出应用程序)应该在不同
的应用程序逻辑层。这对于你设计自己的软件系统来说是一个挑战,只要有可能,你就应该使用 的应用程序逻辑层。Hibernate的当前上下文管理可以极大地简化这一设计你所有的一切就是<literal>SessionFactory</literal>。 异常处理将在本章稍后进行讨论。
J2EE/EJB容器服务。异常处理将在本章稍后进行讨论。
</para> </para>
<para> <para>
请注意,你应该选择 <literal>org.hibernate.transaction.JDBCTransactionFactory</literal> 请注意,你应该选择 <literal>org.hibernate.transaction.JDBCTransactionFactory</literal>
(这是默认选项). (这是默认选项),对第二个例子来说,<literal>hibernate.current_session_context_class</literal>应该是<literal>"thread"</literal>
</para> </para>
</sect2> </sect2>
<sect2 id="transactions-demarcation-jta"> <sect2 id="transactions-demarcation-jta" revision="3">
<title>使用JTA</title> <title>使用JTA</title>
<para> <para>
如果你的持久层运行在一个应用服务器中例如在EJB session beans的后面Hibernate获取 如果你的持久层运行在一个应用服务器中例如在EJB session beans的后面Hibernate获取
的每个数据源连接将自动成为全局JTA事务的一部分。Hibernate提供了两种策略进行JTA集成。 的每个数据源连接将自动成为全局JTA事务的一部分。
你可以安装一个独立的JTA实现使用它而不使用EJB。Hibernate提供了两种策略进行JTA集成。
</para> </para>
<para> <para>
@ -381,36 +389,34 @@ finally {
sess.close(); sess.close();
}]]></programlisting> }]]></programlisting>
<para>
如果你希望使用与事务绑定的<literal>Session</literal>,也就是使用<literal>getCurrentSession()</literal>来简化上下文管理你将不得不直接使用JTA <literal>UserTransaction</literal>API。
</para>
<programlisting><![CDATA[// BMT idiom with getCurrentSession()
try {
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
tx.begin();
// Do some work on Session bound to transaction
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or display error message
}]]></programlisting>
<para> <para>
在CMT方式下事务声明是在session bean的部署描述符中而不需要编程。 在CMT方式下事务声明是在session bean的部署描述符中而不需要编程。
除非你设置了属性<literal>hibernate.transaction.flush_before_completion</literal> 因此,代码被简化为:
<literal>hibernate.transaction.auto_close_session</literal><literal>true</literal> </para>
否则你必须自己同步和关闭<literal>Session</literal>。Hibernate可以为你自动同步和关闭
<literal>Session</literal>。你唯一要做的就是当发生异常时进行事务回滚。幸运的是,
在一个CMT bean中事务回滚甚至可以由容器自动进行因为由session bean方法抛出的未处理的
<literal>RuntimeException</literal>异常可以通知容器设置全局事务回滚。<emphasis>这意味着
在CMT中你完全无需使用Hibernate的<literal>Transaction</literal> API 。</emphasis>
</para>
<para>
请注意当你配置Hibernate事务工厂的时候在一个BMT session bean中你应该选择
<literal>org.hibernate.transaction.JTATransactionFactory</literal>,在一个
CMT session bean中选择<literal>org.hibernate.transaction.CMTTransactionFactory</literal>
记住,同时也要设置<literal>org.hibernate.transaction.manager_lookup_class</literal>
</para>
<para>
如果你使用CMT环境并且让容器自动同步和关闭session你可能也希望在你代码的不同部分使用
同一个session。一般来说在一个非托管环境中你可以使用一个<literal>ThreadLocal</literal>
变量来持有这个session但是单个EJB方法调用可能会在不同的线程中执行举例来说一个session
bean调用另一个session bean。如果你不想在应用代码中被传递<literal>Session</literal>
象实例的问题困扰的话,那么<literal>SessionFactory</literal> 提供的
<literal>getCurrentSession()</literal>方法就很适合你该方法返回一个绑定到JTA事务
上下文环境中的session实例。这也是把Hibernate集成到一个应用程序中的最简单的方法这个“当
前的”session总是可以自动同步和自动关闭不考虑上述的属性设置。我们的session/transaction
管理代码减少到如下所示:
</para>
<programlisting><![CDATA[// CMT idiom <programlisting><![CDATA[// CMT idiom
Session sess = factory.getCurrentSession(); Session sess = factory.getCurrentSession();
@ -420,19 +426,23 @@ Session sess = factory.getCurrentSession();
]]></programlisting> ]]></programlisting>
<para> <para>
换句话来说,在一个托管环境下,你要做的所有的事情就是调用 在CMT/EJB中甚至会自动rollback因为假若有未捕获的<literal>RuntimeException</literal>从session bean方法中抛出这就会通知容器把全局事务回滚。<emphasis>这就意味着在BMT或者CMT中你根本就不需要使用Hibernate <literal>Transaction</literal> API 你自动得到了绑定到事务的“当前”Session。
<literal>SessionFactory.getCurrentSession()</literal>,然后进行你的数据访问,把其余的工作 </emphasis>
交给容器来做。事务在你的session bean的部署描述符中以可声明的方式来设置。session的生命周期完全
由Hibernate来管理。
</para> </para>
<para> <para>
<literal>after_statement</literal>连接释放方式有一个警告。因为JTA规范的一个很愚蠢的限制Hibernate不可能自动清理任何未关闭的<literal>ScrollableResults</literal> 或者<literal>Iterator</literal>,它们是由<literal>scroll()</literal><literal>iterate()</literal>产生的。你<emphasis>must</emphasis>通过在<literal>finally</literal>块中,显式调用<literal>ScrollableResults.close()</literal>或者<literal>Hibernate.close(Iterator)</literal>方法来释放底层数据库游标。(当然大部分程序完全可以很容易的避免在CMT代码中出现<literal>scroll()</literal><literal>iterate()</literal>。) 注意当你配置Hibernate的transaction factory的时候在直接使用JTA的时候BMT你应该选择<literal>org.hibernate.transaction.JTATransactionFactory</literal>,在CMT session bean中选择<literal>org.hibernate.transaction.CMTTransactionFactory</literal>。记得也要设置<literal>hibernate.transaction.manager_lookup_class</literal>。还有,确认你的<literal>hibernate.current_session_context_class</literal>未设置(为了向下兼容),或者设置为<literal>"jta"</literal>
</para>
<para>
<literal>getCurrentSession()</literal>在JTA环境中有一个弊端。对<literal>after_statement</literal>连接释放方式有一个警告这是被默认使用的。因为JTA规范的一个很愚蠢的限制Hibernate不可能自动清理任何未关闭的<literal>ScrollableResults</literal> 或者<literal>Iterator</literal>,它们是由<literal>scroll()</literal><literal>iterate()</literal>产生的。你<emphasis>must</emphasis>通过在<literal>finally</literal>块中,显式调用<literal>ScrollableResults.close()</literal>或者<literal>Hibernate.close(Iterator)</literal>方法来释放底层数据库游标。(当然大部分程序完全可以很容易的避免在JTA或CMT代码中出现<literal>scroll()</literal><literal>iterate()</literal>。)
</para> </para>
</sect2> </sect2>
<sect2 id="transactions-demarcation-exceptions"> <sect2 id="transactions-demarcation-exceptions">
<title>异常处理</title> <title>异常处理</title>
<para> <para>
@ -497,6 +507,38 @@ Session sess = factory.getCurrentSession();
</sect2> </sect2>
<sect2 id="transactions-demarcation-timeout">
<title>事务超时</title>
<para>
EJB这样的托管环境有一项极为重要的特性而它从未在非托管环境中提供过那就是事务超时。在出现错误的事务行为的时候超时可以确保不会无限挂起资源、对用户没有交代。在托管(JTA)环境之外Hibernate无法完全提供这一功能。但是Hiberante至少可以控制数据访问确保数据库级别的死锁和返回巨大结果集的查询被限定在一个规定的时间内。在托管环境中Hibernate会把事务超时转交给JTA。这一功能通过Hibernate <literal>Transaction</literal>对象进行抽象。
</para>
<programlisting><![CDATA[
Session sess = factory.openSession();
try {
//set transaction timeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}]]></programlisting>
<para>
注意<literal>setTimeout()</literal>不应该在CMT bean中调用此时事务超时值应该是被声明式定义的。
</para>
</sect2>
</sect1> </sect1>
<sect1 id="transactions-optimistic"> <sect1 id="transactions-optimistic">
@ -504,7 +546,7 @@ Session sess = factory.getCurrentSession();
<para> <para>
唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、
或者时间戳来检测更新冲突并且防止更新丢失。Hibernate为使用乐观并发控制的代码提供了三种可 或者时间戳来检测更新冲突并且防止更新丢失。Hibernate为使用乐观并发控制的代码提供了三种可
能的方法,应用程序在编写这些代码时,可以采用它们。我们已经在前面应用程序长事务那部分展示了 能的方法,应用程序在编写这些代码时,可以采用它们。我们已经在前面应用程序对话那部分展示了
乐观并发控制的应用场景,此外,在单个数据库事务范围内,版本检查也提供了防止更新丢失的好处。 乐观并发控制的应用场景,此外,在单个数据库事务范围内,版本检查也提供了防止更新丢失的好处。
</para> </para>
@ -514,17 +556,19 @@ Session sess = factory.getCurrentSession();
未能充分利用Hibernate功能的实现代码中每次和数据库交互都需要一个新的 未能充分利用Hibernate功能的实现代码中每次和数据库交互都需要一个新的
<literal>Session</literal>,而且开发人员必须在显示数据之前从数据库中重 <literal>Session</literal>,而且开发人员必须在显示数据之前从数据库中重
新载入所有的持久化对象实例。这种方式迫使应用程序自己实现版本检查来确保 新载入所有的持久化对象实例。这种方式迫使应用程序自己实现版本检查来确保
应用程序事务的隔离,从数据访问的角度来说是最低效的。这种使用方式和 对话事务的隔离,从数据访问的角度来说是最低效的。这种使用方式和
entity EJB最相似。 entity EJB最相似。
</para> </para>
<programlisting><![CDATA[// foo is an instance loaded by a previous Session <programlisting><![CDATA[// foo is an instance loaded by a previous Session
session = factory.openSession(); session = factory.openSession();
Transaction t = session.beginTransaction(); Transaction t = session.beginTransaction();
int oldVersion = foo.getVersion(); int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() ); // load the current state session.load( foo, foo.getKey() ); // load the current state
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar"); foo.setProperty("bar");
t.commit(); t.commit();
session.close();]]></programlisting> session.close();]]></programlisting>
@ -536,7 +580,7 @@ session.close();]]></programlisting>
<para> <para>
当然,如果你的应用是在一个低数据并发环境下,并不需要版本检查的话,你照样可以使用 当然,如果你的应用是在一个低数据并发环境下,并不需要版本检查的话,你照样可以使用
这种方式,只不过跳过版本检查就是了。在这种情况下,<emphasis>最晚提交生效</emphasis> 这种方式,只不过跳过版本检查就是了。在这种情况下,<emphasis>最晚提交生效</emphasis>
<emphasis>last commit wins</emphasis>)就是你的应用程序长事务的默认处理策略。 <emphasis>last commit wins</emphasis>)就是你的长对话的默认处理策略。
请记住这种策略可能会让应用软件的用户感到困惑,因为他们有可能会碰上更新丢失掉却没 请记住这种策略可能会让应用软件的用户感到困惑,因为他们有可能会碰上更新丢失掉却没
有出错信息,或者需要合并更改冲突的情况。 有出错信息,或者需要合并更改冲突的情况。
</para> </para>
@ -544,19 +588,19 @@ session.close();]]></programlisting>
<para> <para>
很明显,手工进行版本检查只适合于某些软件规模非常小的应用场景,对于大多数软件应用场景 很明显,手工进行版本检查只适合于某些软件规模非常小的应用场景,对于大多数软件应用场景
来说并不现实。通常情况下,不仅是单个对象实例需要进行版本检查,整个被修改过的关 来说并不现实。通常情况下,不仅是单个对象实例需要进行版本检查,整个被修改过的关
联对象图也都需要进行版本检查。作为标准设计范例Hibernate使用长生命周期 联对象图也都需要进行版本检查。作为标准设计范例Hibernate使用扩展周期的
<literal>Session</literal>的方式,或者脱管对象实例的方式来提供自动版本检查。 <literal>Session</literal>的方式,或者脱管对象实例的方式来提供自动版本检查。
</para> </para>
</sect2> </sect2>
<sect2 id="transactions-optimistic-longsession"> <sect2 id="transactions-optimistic-longsession">
<title>长生命周期session和自动版本化</title> <title>扩展周期的session和自动版本化</title>
<para> <para>
单个 <literal>Session</literal>实例和它所关联的所有持久化对象实例都被用于整个 单个 <literal>Session</literal>实例和它所关联的所有持久化对象实例都被用于整个
应用程序事务。Hibernate在同步的时候进行对象实例的版本检查如果检测到并发修 对话,这被称为<emphasis>session-per-conversation</emphasis>。Hibernate在同步的时候进行对象实例的版本检查如果检测到并发修
改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常(通常的抉择是给用户 改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常(通常的抉择是给用户
提供一个合并更改,或者在无脏数据情况下重新进行业务操作的机会)。 提供一个合并更改,或者在无脏数据情况下重新进行业务对话的机会)。
</para> </para>
<para> <para>
在等待用户交互的时候, <literal>Session</literal> 断开底层的JDBC连接。这种方式 在等待用户交互的时候, <literal>Session</literal> 断开底层的JDBC连接。这种方式
@ -564,42 +608,42 @@ session.close();]]></programlisting>
的重新关联,在每个数据库事务中,应用程序也不需要载入读取对象实例。 的重新关联,在每个数据库事务中,应用程序也不需要载入读取对象实例。
</para> </para>
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session <programlisting><![CDATA[// foo is an instance loaded earlier by the old session
session.reconnect(); // Obtain a new JDBC connection Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction
Transaction t = session.beginTransaction();
foo.setProperty("bar"); foo.setProperty("bar");
t.commit(); // End database transaction, flushing the change and checking the version
session.disconnect(); // Return JDBC connection ]]></programlisting> session.flush(); // Only for last transaction in conversation
t.commit(); // Also return JDBC connection
session.close(); // Only for last transaction in conversation]]></programlisting>
<para> <para>
<literal>foo</literal> 对象始终和载入它的<literal>Session</literal>相关联。 <literal>foo</literal>对象知道它是在哪个<literal>Session</literal>中被装入的。在一个旧session中开启一个新的数据库事务会导致session获取一个新的连接并恢复session的功能。将数据库事务提交使得session从JDBC连接断开并将此连接交还给连接池。在重新连接之后要强制对你没有更新的数据进行一次版本检查你可以对所有可能被其他事务修改过的对象使用参数<literal>LockMode.READ</literal>来调用<literal>Session.lock()</literal>。你不用lock任何你<emphasis>正在</emphasis>更新的数据。一般你会在扩展的<literal>Session</literal>上设置<literal>FlushMode.NEVER</literal>,因此只有最后一个数据库事务循环才会真正的吧整个对话中发生的修改发送到数据库。因此,只有这最后一次数据库事务才会包含<literal>flush()</literal>操作,然后在整个对话结束后,还要<literal>close()</literal>这个session。
<literal>Session.reconnect()</literal>获取一个新的数据库连接(或者
你可以提供一个并且继续当前的session。<literal>Session.disconnect()</literal>
方法把session与JDBC连接断开把数据库连接返回到连接池除非是你自己提供的数据
库连接。在Session重新连接上数据库连接之后你可以对任何可能被其他事务更新过
的对象调用<literal>Session.lock()</literal>,设置<literal>LockMode.READ</literal>
锁定模式,这样你就可以对那些你不准备更新的数据进行强制版本检查。此外,你并不需要
锁定那些你<emphasis>准备</emphasis>更新的数据。
</para>
<para>
假若对<literal>disconnect()</literal><literal>reconnect()</literal>的显式调用发生得太频繁了,你可以使用<literal>hibernate.connection.release_mode</literal>来代替。
</para> </para>
<para> <para>
如果在用户思考的过程中,<literal>Session</literal>因为太大了而不能保存,那么这种模式是有 如果在用户思考的过程中,<literal>Session</literal>因为太大了而不能保存,那么这种模式是有
问题的。举例来说,一个<literal>HttpSession</literal>应该尽可能的小。由于 问题的。举例来说,一个<literal>HttpSession</literal>应该尽可能的小。由于
<literal>Session</literal>是一级缓存,并且保持了所有被载入过的对象,因此 <literal>Session</literal>是一级缓存,并且保持了所有被载入过的对象,因此
我们只应该在那些少量的request/response情况下使用这种策略。而且在这种情况下 我们只应该在那些少量的request/response情况下使用这种策略。你应该只把一个<literal>Session</literal>用于单个对话,因为它很快就会出现脏数据。
<literal>Session</literal> 里面很快就会有脏数据出现,因此请牢牢记住这一建议。
</para> </para>
<para>
注意早期的Hibernate版本需要明确的对<literal>Session</literal>进行disconnec和reconnect。这些方法现在已经过时了打开事务和关闭事务会起到同样的效果。
</para>
<para> <para>
此外,也请注意,你应该让与数据库连接断开的<literal>Session</literal>对持久层保持 此外,也请注意,你应该让与数据库连接断开的<literal>Session</literal>对持久层保持
关闭状态。换句话说使用有状态的EJB session bean来持有<literal>Session</literal> 关闭状态。换句话说,在三层环境中,使用有状态的EJB session bean来持有<literal>Session</literal>
而不要把它传递到web层甚至把它序列化到一个单独的层保存在<literal>HttpSession</literal>中。 而不要把它传递到web层甚至把它序列化到一个单独的层保存在<literal>HttpSession</literal>中。
</para> </para>
<para>
扩展session模式或者被称为<emphasis>每次对话一个session(session-per-conversation)</emphasis>, 在与自动管理当前session上下文联用的时候会更困难。你需要提供你自己的<literal>CurrentSessionContext</literal>实现。请参阅Hibernate Wiki以获得示例。
</para>
</sect2> </sect2>
<sect2 id="transactions-optimistic-detached"> <sect2 id="transactions-optimistic-detached">
@ -766,6 +810,61 @@ session.close();]]></programlisting>
这一点可以确保应用程序的可移植性。 这一点可以确保应用程序的可移植性。
</para> </para>
</sect1> </sect1>
<sect1 id="transactions-connection-release">
<title>连接释放模式(Connection Release Modes)</title>
<para>
Hibernate关于JDBC连接管理的旧(2.x)行为是,<literal>Session</literal>在第一次需要的时候获取一个连接在session关闭之前一直会持有这个连接。Hibernate引入了连接释放的概念来告诉session如何处理它的JDBC连接。注意下面的讨论只适用于采用配置<literal>ConnectionProvider</literal>来提供连接的情况,用户自己提供的连接与这里的讨论无关。通过<literal>org.hibernate.ConnectionReleaseMode</literal>的不同枚举值来使用不用的释放模式:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>ON_CLOSE</literal> - 基本上就是上面提到的老式行为。Hibernate session在第一次需要进行JDBC操作的时候获取连接然后持有它直到session关闭。
</para>
</listitem>
<listitem>
<para>
<literal>AFTER_TRANSACTION</literal> - 在<literal>org.hibernate.Transaction</literal>结束后释放连接。
</para>
</listitem>
<listitem>
<para>
<literal>AFTER_STATEMENT</literal> (也被称做积极释放) - 在每一条语句被执行后就释放连接。但假若语句留下了与session相关的资源那就不会被释放。目前唯一的这种情形就是使用<literal>org.hibernate.ScrollableResults</literal>
</para>
</listitem>
</itemizedlist>
<para>
<literal>hibernate.connection.release_mode</literal>配置参数用来指定使用哪一种释放模式。可能的值有:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>auto</literal>(默认) - 这一选择把释放模式委派给<literal>org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode()</literal>方法。对JTATransactionFactory来说它会返回ConnectionReleaseMode.AFTER_STATEMENT;对JDBCTransactionFactory来说则是ConnectionReleaseMode.AFTER_TRANSACTION。很少需要修改这一默认行为因为假若设置不当就会带来bug或者给用户代码带来误导。
</para>
</listitem>
<listitem>
<para>
<literal>on_close</literal> - 使用 ConnectionReleaseMode.ON_CLOSE. 这种方式是为了向下兼容的,但是已经完全不被鼓励使用了。
</para>
</listitem>
<listitem>
<para>
<literal>after_transaction</literal> - 使用ConnectionReleaseMode.AFTER_TRANSACTION。这一设置不应该在JTA环境下使用。也要注意使用ConnectionReleaseMode.AFTER_TRANSACTION的时候假若session 处于auto-commit状态连接会像AFTER_STATEMENT那样被释放。
</para>
</listitem>
<listitem>
<para>
<literal>after_statement</literal> - 使用ConnectionReleaseMode.AFTER_STATEMENT。除此之外会查询配置的<literal>ConnectionProvider</literal>,是否它支持这一设置((<literal>supportsAggressiveRelease()</literal>)。假若不支持释放模式会被设置为ConnectionReleaseMode.AFTER_TRANSACTION。只有在你每次调用<literal>ConnectionProvider.getConnection()</literal>获取底层JDBC连接的时候都可以确信获得同一个连接的时候这一设置才是安全的或者在auto-commit环境中你可以不管是否每次都获得同一个连接的时候这才是安全的。
</para>
</listitem>
</itemizedlist>
</sect1>
</chapter> </chapter>

File diff suppressed because it is too large Load Diff