hibernate-orm/reference/zh-cn/modules/persistent_classes.xml

447 lines
16 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<chapter id="persistent-classes" revision="2">
<title>持久化类(Persistent Classes)</title>
<para>
在应用程序中用来实现业务问题实体的在电子商务应用程序中的Customer和Order
类就是持久化类。不能认为所有的持久化类的实例都是持久的状态——一个实例的状态也可能
是瞬时的或脱管的。
</para>
<para>
如果这些持久化类遵循一些简单的规则Hibernate能够工作得最好这些规则被称作
简单传统Java对象(POJO:Plain Old Java Object)编程模型。但是这些规则没有一个是必需的。
实际上Hibernate3对于你的持久化类几乎不做任何设想。你可以用其他的方法来表达领域模型
比如,使用<literal>Map</literal>实例的树型结构。
</para>
<sect1 id="persistent-classes-pojo">
<title>一个简单的POJO例子</title>
<para>
大多数Java程序需要用一个持久化类来表示猫科动物。
</para>
<programlisting><![CDATA[package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}]]></programlisting>
<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">
<title>实现一个默认的即无参数的构造方法constructor</title>
<para>
<literal>Cat</literal>有一个无参数的构造方法。所有的持久化类都必须有一个
默认的构造方法可以不是public的这样的话Hibernate就可以使用
<literal>Constructor.newInstance()</literal>来实例化它们。
我们建议在Hibernate中为了运行期代理的生成构造方法至少是
<emphasis>包(package)</emphasis>内可见的。
</para>
</sect2>
<sect2 id="persistent-classes-pojo-identifier" revision="2">
<title>提供一个标识属性identifier property可选 </title>
<para>
<literal>Cat</literal>有一个属性叫做<literal>id</literal>。这个属性映射数据库表的主
键字段。这个属性可以叫任何名字,其类型可以是任何的原始类型、原始类型的包装类型、
<literal>java.lang.String</literal> 或者是 <literal>java.util.Date</literal>
(如果你的老式数据库表有联合主键,你甚至可以用一个用户自定义的类,该类拥有这些类型
的属性。参见后面的关于联合标识符的章节。)
</para>
<para>
标识符属性是可选的。可以不用管它让Hibernate内部来追踪对象的识别。
不推荐使用这个属性。
</para>
<para>
实际上,一些功能只对那些声明了标识符属性的类起作用:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
托管对象的传播性重新和session关联级联更新或级联合并
——参阅 <xref linkend="objectstate-transitive"/>
</para>
</listitem>
<listitem>
<para>
<literal>Session.saveOrUpdate()</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Session.merge()</literal>
</para>
</listitem>
</itemizedlist>
<para>
我们建议你对持久化类声明命名一致的标识属性。我们还建议你使用一
个可以为空(也就是说,不是原始类型)的类型。
</para>
</sect2>
<sect2 id="persistent-classes-pojo-final">
<title>使用非final的类 (可选)</title>
<para>
<emphasis>代理proxies</emphasis>是Hibernate的一个重要的功能它依赖的条件是持久
化类或者是非final的或者是实现了一个所有方法都声明为public的接口。
</para>
<para>
你可以用Hibernate持久化一个没有实现任何接口的<literal>final</literal>类,但是你
不能使用代理来延迟关联加载,这会限制你进行性能优化的选择。
</para>
<para>
你也应该避免在非final类中声明 <literal>public final</literal>的方法。如果你想使用一
个有<literal>public final</literal>方法的类,你必须通过设置<literal>lazy="false"</literal>
来明确的禁用代理。
</para>
</sect2>
</sect1>
<sect1 id="persistent-classes-inheritance">
<title>实现继承Inheritance</title>
<para>
子类也必须遵守第一条和第二条规则。它从超类<literal>Cat</literal>继承了标识属性。
</para>
<programlisting><![CDATA[package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-equalshashcode" revision="1">
<title>实现<literal>equals()</literal><literal>hashCode()</literal></title>
<para>
如果你有如下需求,你必须重载
<literal>equals()</literal><literal>hashCode()</literal>方法:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
想把持久类的实例放入<literal>Set</literal>中(当表示多值关联时,推荐这么做)
</para>
</listitem>
<listitem>
<para>
想重用脱管实例
</para>
</listitem>
</itemizedlist>
<para>
Hibernate保证持久化标识数据库的行和仅在特定会话范围内的Java标识是等值的。因此一旦
我们混合了从不同会话中获取的实例,如果我们希望<literal>Set</literal>有明确的语义,我们必
须实现<literal>equals()</literal><literal>hashCode()</literal>
</para>
<para>
实现<literal>equals()</literal>/<literal>hashCode()</literal>最显而易见的方法是比较两个对象
标识符的值。如果值相同,则两个对象对应于数据库的同一行,因此它们是相等的(如果都被添加到
<literal>Set</literal>,则在<literal>Set</literal>中只有一个元素)。不幸的是,对生成的标识不能
使用这种方法。Hibernate仅对那些持久化对象赋标识值一个新创建的实例将不会有任何标识值。此外
如果一个实例没有被保存(unsaved),并且在一个<literal>Set</literal>中,保存它将会给这个对象
赋一个标识值。如果<literal>equals()</literal><literal>hashCode()</literal>是基于标识值
实现的,则其哈希码将会改变,违反<literal>Set</literal>的契约。建议去Hibernate的站点看关于这个
问题的全部讨论。注意这不是一个Hibernate问题而是一般的Java对象标识和相等的语义问题。
</para>
<para>
我们建议使用<emphasis>业务键值相等(Business key equality)</emphasis>来实现<literal>equals()</literal>
<literal>hashCode()</literal>。业务键值相等的意思是,<literal>equals()</literal>方法
仅仅比较来自业务键的属性,一个业务键将标识在真实世界里(一个<emphasis>天生的</emphasis>候选键)
的实例。
</para>
<programlisting><![CDATA[public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}]]></programlisting>
<para>
注意,业务键不必是象数据库的主键那样是固定不变的(参见<xref linkend="transactions-basics-identity"/>)。
对业务键而言,不可变或唯一的属性是好的候选。
</para>
</sect1>
<sect1 id="persistent-classes-dynamicmodels">
<title>动态模型(Dynamic models)</title>
<para>
<emphasis>注意,以下特性在当前是基于实验考虑的,可能会在将来改变。</emphasis>
</para>
<para>
运行期的持久化实体没有必要象POJO类或JavaBean对象一样表示。Hibernate也支持动态模型
(在运行期使用<literal>Map</literal><literal>Map</literal>和象DOM4J的树模型那
样的实体表示。使用这种方法,你不用写持久化类,只写映射文件就行了。
</para>
<para>
Hibernate默认工作在普通POJO模式。你可以使用配置选项<literal>default_entity_mode</literal>
对特定的<literal>SessionFactory</literal>,设置一个默认的实体表示模式。
(参见<xref linkend="configuration-optional-properties"/>。)
</para>
<para>
下面是用<literal>Map</literal>来表示的例子。首先,在映射文件中,要声明
<literal>entity-name</literal>来代替(或外加)一个类名。
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping>]]></programlisting>
<para>
注意虽然是用目标类名来声明关联的但是关联的目标类型除了是POJO之外也可以
是一个动态的实体。
</para>
<para>
在使用<literal>dynamic-map</literal><literal>SessionFactory</literal>
设置了默认的实体模式之后,可以在运行期使用<literal>Map</literal>
<literal>Map</literal>
</para>
<programlisting><![CDATA[Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();]]></programlisting>
<para>
动态映射的好处是,使原型在不需要实体类实现的情况下,快速转变时间。然而,你无法进行
编译期的类型检查并可能由此会处理很多的运行期异常。幸亏有了Hibernate映射它使得数
据库的schema能容易的规格化和合理化并允许稍后添加正确的领域模型的最新实现。
</para>
<para>
实体表示模式也能在每个<literal>Session</literal>的基础上设置:
</para>
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
]]></programlisting>
<para>
请注意,用<literal>EntityMode</literal>调用<literal>getSession()</literal>是在
<literal>Session</literal>的API中而不是<literal>SessionFactory</literal>
这样,新的<literal>Session</literal>共享底层的JDBC连接事务和其他的上下文信
息。这意味着,你不需要在第二个<literal>Session</literal>中调用
<literal>flush()</literal><literal>close()</literal>,同样的,把事务和连接的处理
交给原来的工作单元。
</para>
<para>
关于XML表示能力的更多信息可以在<xref linkend="xml"/>中找到。
</para>
</sect1>
<para>
TODO在property和proxy的包里用户扩展文件框架。
</para>
</chapter>