1106 lines
50 KiB
XML
1106 lines
50 KiB
XML
<chapter id="objectstate">
|
||
|
||
<title>与对象共事</title>
|
||
|
||
<para>
|
||
Hibernate是完整的对象/关系映射解决方案,它提供了对象<emphasis>状态管理(state management)</emphasis>的功能,使开发者不再需要理会底层数据库系统的细节。
|
||
也就是说,相对于常见的JDBC/SQL持久层方案中需要<literal>管理SQL语句</literal>,Hibernate采用了更自然的面向对象的视角来持久化Java应用中的数据。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
换句话说,使用Hibernate的开发者应该总是关注对象的<emphasis>状态(state)</emphasis>,不必考虑SQL语句的执行。
|
||
这部分细节已经由Hibernate掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。
|
||
</para>
|
||
|
||
<sect1 id="objectstate-overview">
|
||
|
||
<title>Hibernate对象状态(object states)</title>
|
||
|
||
|
||
<para>
|
||
Hibernate定义并支持下列对象状态(state):
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
|
||
<para>
|
||
<emphasis>瞬时(Transient)</emphasis> - 由<literal>new</literal>操作符创建,且尚未与Hibernate <literal>Session</literal>
|
||
关联的对象被认定为瞬时(Transient)的。瞬时(Transient)对象不会被持久化到数据库中,也不会被赋予持久化标识(identifier)。
|
||
如果程序中没有保持对瞬时(Transient)对象的引用,它会被垃圾回收器(garbage collector)销毁。
|
||
使用Hibernate <literal>Session</literal>可以将其变为持久(Persistent)状态。(Hibernate会自动执行必要的SQL语句)
|
||
</para>
|
||
|
||
</listitem>
|
||
<listitem>
|
||
|
||
<para>
|
||
<emphasis>持久(Persistent)</emphasis> - 持久(Persistent)的实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)。
|
||
持久(Persistent)的实例可能是刚被保存的,或刚被加载的,无论哪一种,按定义对象都仅在相关联的<literal>Session</literal>生命周期内的保持这种状态。
|
||
Hibernate会检测到处于持久(Persistent)状态的对象的任何改动,在当前操作单元(unit of work)执行完毕时将对象数据(state)与数据库同步(synchronize)。
|
||
开发者不需要手动执行<literal>UPDATE</literal>。将对象从持久(Persistent)状态变成瞬时(Transient)状态同样也不需要手动执行<literal>DELETE</literal>语句。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<emphasis>脱管(Detached)</emphasis> - 与持久(Persistent)对象关联的<literal>Session</literal>被关闭后,对象就变为脱管(Detached)的。
|
||
对脱管(Detached)对象的引用依然有效,对象可继续被修改。脱管(Detached)对象如果重新关联到某个新的<literal>Session</literal>上,
|
||
会再次转变为持久(Persistent)的(Detached其间的改动将被持久化到数据库)。
|
||
这个功能使得一种编程模型,即中间会给用户思考时间(user think-time)的长时间运行的操作单元(unit of work)的编程模型成为可能。
|
||
我们称之为<emphasis>应用程序事务</emphasis>,即从用户观点看是一个操作单元(unit of work)。
|
||
</para>
|
||
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
接下来我们来细致的讨论下状态(states)及状态间的转换(state transitions)(以及触发状态转换的Hibernate方法)。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-makingpersistent" revision="1">
|
||
<title>使对象持久化</title>
|
||
|
||
<para>
|
||
Hibernate认为持久化类(persistent class)新实例化的对象是<emphasis>瞬时(Transient)</emphasis>的。
|
||
我们可将瞬时(Transient)对象与session关联而变为<emphasis>持久(Persistent)</emphasis>的。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
|
||
fritz.setColor(Color.GINGER);
|
||
fritz.setSex('M');
|
||
fritz.setName("Fritz");
|
||
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
|
||
|
||
<para>
|
||
如果<literal>Cat</literal>的持久化标识(identifier)是<literal>generated</literal>类型的,
|
||
那么该标识(identifier)会自动在<literal>save()</literal>被调用时产生并分配给<literal>cat</literal>。
|
||
如果<literal>Cat</literal>的持久化标识(identifier)是<literal>assigned</literal>类型的,或是一个复合主键(composite key),
|
||
那么该标识(identifier)应当在调用<literal>save()</literal>之前手动赋予给<literal>cat</literal>。
|
||
你也可以按照EJB3 early draft中定义的语义,使用<literal>persist()</literal>替代<literal>save()</literal>。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
此外,你可以用一个重载版本的<literal>save()</literal>方法。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[DomesticCat pk = new DomesticCat();
|
||
pk.setColor(Color.TABBY);
|
||
pk.setSex('F');
|
||
pk.setName("PK");
|
||
pk.setKittens( new HashSet() );
|
||
pk.addKitten(fritz);
|
||
sess.save( pk, new Long(1234) );]]></programlisting>
|
||
|
||
|
||
<para>
|
||
如果你持久化的对象有关联的对象(associated objects)(例如上例中的<literal>kittens</literal>集合)
|
||
那么对这些对象(译注:pk和kittens)进行持久化的顺序是任意的(也就是说可以先对kittens进行持久化也可以先对pk进行持久化),
|
||
除非你在外键列上有<literal>NOT NULL</literal>约束。
|
||
Hibernate不会违反外键约束,但是如果你用错误的顺序持久化对象(译注:在pk持久之前持久kitten),那么可能会违反<literal>NOT NULL</literal>约束。
|
||
</para>
|
||
|
||
<para>
|
||
通常你不会为这些细节烦心,因为你很可能会使用Hibernate的
|
||
<emphasis>传播性持久化(transitive persistence)</emphasis>功能自动保存相关联那些对象。
|
||
这样连违反<literal>NOT NULL</literal>约束情况都不会出现了 - Hibernate会管好所有的事情。
|
||
传播性持久化(transitive persistence)将在本章稍后讨论。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-loading">
|
||
<title>装载对象</title>
|
||
<para>
|
||
如果你知道某个实例的持久化标识(identifier),你就可以使用<literal>Session</literal>的<literal>load()</literal>方法
|
||
来获取它。 <literal>load()</literal>的另一个参数是指定类的.class对象。
|
||
本方法会创建指定类的持久化实例,并从数据库加载其数据(state)。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
|
||
|
||
<programlisting><![CDATA[// you need to wrap primitive identifiers
|
||
long pkId = 1234;
|
||
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
|
||
|
||
<para>
|
||
此外, 你可以把数据(state)加载到指定的对象实例上(覆盖掉该实例原来的数据)。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
||
// load pk's state into cat
|
||
sess.load( cat, new Long(pkId) );
|
||
Set kittens = cat.getKittens();]]></programlisting>
|
||
|
||
|
||
<para>
|
||
请注意如果没有匹配的数据库记录,<literal>load()</literal>方法可能抛出无法恢复的异常(unrecoverable exception)。
|
||
如果类的映射使用了代理(proxy),<literal>load()</literal>方法会返回一个未初始化的代理,直到你调用该代理的某方法时才会去访问数据库。
|
||
若你希望在某对象中创建一个指向另一个对象的关联,又不想在从数据库中装载该对象时同时装载相关联的那个对象,那么这种操作方式就用得上的了。
|
||
如果为相应类映射关系设置了<literal>batch-size</literal>,
|
||
那么使用这种操作方式允许多个对象被一批装载(因为返回的是代理,无需从数据库中抓取所有对象的数据)。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
如果你不确定是否有匹配的行存在,应该使用<literal>get()</literal>方法,它会立刻访问数据库,如果没有对应的行,会返回null。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id);
|
||
if (cat==null) {
|
||
cat = new Cat();
|
||
sess.save(cat, id);
|
||
}
|
||
return cat;]]></programlisting>
|
||
|
||
<para>
|
||
你甚至可以选用某个<literal>LockMode</literal>,用SQL的<literal>SELECT ... FOR UPDATE</literal>装载对象。
|
||
请查阅API文档以获取更多信息。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
|
||
|
||
|
||
<para>
|
||
注意,任何关联的对象或者包含的集合都<emphasis>不会</emphasis>被以<literal>FOR UPDATE</literal>方式返回,
|
||
除非你指定了<literal>lock</literal>或者<literal>all</literal>作为关联(association)的级联风格(cascade style)。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
任何时候都可以使用<literal>refresh()</literal>方法强迫装载对象和它的集合。如果你使用数据库触发器功能来处理对象的某些属性,这个方法就很有用了。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[sess.save(cat);
|
||
sess.flush(); //force the SQL INSERT
|
||
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
|
||
|
||
|
||
<para>
|
||
此处通常会出现一个重要问题: Hibernate会从数据库中装载多少东西?会执行多少条相应的SQL<literal>SELECT</literal>语句?
|
||
这取决于<emphasis>抓取策略(fetching strategy)</emphasis>,会在<xref linkend="performance-fetching"/>中解释。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-querying" revision="1">
|
||
<title>查询</title>
|
||
<para>
|
||
如果不知道所要寻找的对象的持久化标识,那么你需要使用查询。Hibernate支持强大且易于使用的面向对象查询语言(HQL)。
|
||
如果希望通过编程的方式创建查询,Hibernate提供了完善的按条件(Query By Criteria, QBC)以及按样例(Query By Example, QBE)进行查询的功能。
|
||
你也可以用原生SQL(native SQL)描述查询,Hibernate提供了将结果集(result set)转化为对象的部分支持。
|
||
</para>
|
||
|
||
<sect2 id="objectstate-querying-executing">
|
||
<title>执行查询</title>
|
||
|
||
<para>
|
||
HQL和原生SQL(native SQL)查询要通过为<literal>org.hibernate.Query</literal>的实例来表达。
|
||
这个接口提供了参数绑定、结果集处理以及运行实际查询的方法。
|
||
你总是可以通过当前<literal>Session</literal>获取一个<literal>Query</literal>对象:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[List cats = session.createQuery(
|
||
"from Cat as cat where cat.birthdate < ?")
|
||
.setDate(0, date)
|
||
.list();
|
||
|
||
List mothers = session.createQuery(
|
||
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
|
||
.setString(0, name)
|
||
.list();
|
||
|
||
List kittens = session.createQuery(
|
||
"from Cat as cat where cat.mother = ?")
|
||
.setEntity(0, pk)
|
||
.list();
|
||
|
||
Cat mother = (Cat) session.createQuery(
|
||
"select cat.mother from Cat as cat where cat = ?")
|
||
.setEntity(0, izi)
|
||
.uniqueResult();]]></programlisting>
|
||
|
||
|
||
<para>
|
||
一个查询通常在调用<literal>list()</literal>时被执行,执行结果会完全装载进内存中的一个集合(collection)。
|
||
查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用<literal>list()</literal>的快捷方式<literal>uniqueResult()</literal>。
|
||
</para>
|
||
|
||
<sect3 id="objectstate-querying-executing-iterate">
|
||
|
||
<title>迭代式获取结果(Iterating results)</title>
|
||
|
||
<para>
|
||
某些情况下,你可以使用<literal>iterate()</literal>方法得到更好的性能。
|
||
这通常是你预期返回的结果在session,或二级缓存(second-level cache)中已经存在时的情况。
|
||
如若不然,<literal>iterate()</literal>会比<literal>list()</literal>慢,而且可能简单查询也需要进行多次数据库访问:
|
||
<literal>iterate()</literal>会首先使用<emphasis>1</emphasis>条语句得到所有对象的持久化标识(identifiers),再根据持久化标识执行<emphasis>n</emphasis>条附加的select语句实例化实际的对象。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[// fetch ids
|
||
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
|
||
while ( iter.hasNext() ) {
|
||
Qux qux = (Qux) iter.next(); // fetch the object
|
||
// something we couldnt express in the query
|
||
if ( qux.calculateComplicatedAlgorithm() ) {
|
||
// delete the current instance
|
||
iter.remove();
|
||
// dont need to process the rest
|
||
break;
|
||
}
|
||
}]]></programlisting>
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-tuples">
|
||
<title>返回元组(tuples)的查询</title>
|
||
|
||
<para>
|
||
(译注:元组(tuples)指一条结果行包含多个对象)
|
||
Hibernate查询有时返回元组(tuples),每个元组(tuples)以数组的形式返回:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
|
||
"select kitten, mother from Cat kitten join kitten.mother mother")
|
||
.list()
|
||
.iterator();
|
||
|
||
while ( kittensAndMothers.hasNext() ) {
|
||
Object[] tuple = (Object[]) kittensAndMothers.next();
|
||
Cat kitten = tuple[0];
|
||
Cat mother = tuple[1];
|
||
....
|
||
}]]></programlisting>
|
||
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-scalar">
|
||
<title>标量(Scalar)结果</title>
|
||
|
||
<para>
|
||
查询可在<literal>select</literal>从句中指定类的属性,甚至可以调用SQL统计(aggregate)函数。
|
||
属性或统计结果被认定为"标量(Scalar)"的结果(而不是持久(persistent state)的实体)。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Iterator results = sess.createQuery(
|
||
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
|
||
"group by cat.color")
|
||
.list()
|
||
.iterator();
|
||
|
||
while ( results.hasNext() ) {
|
||
Object[] row = results.next();
|
||
Color type = (Color) row[0];
|
||
Date oldest = (Date) row[1];
|
||
Integer count = (Integer) row[2];
|
||
.....
|
||
}]]></programlisting>
|
||
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-parameters">
|
||
<title>绑定参数</title>
|
||
|
||
<para>
|
||
接口<literal>Query</literal>提供了对命名参数(named parameters)、JDBC风格的<literal>问号(?)</literal>参数进行绑定的方法。
|
||
<emphasis>不同于JDBC,Hibernate对参数从0开始计数。</emphasis>
|
||
命名参数(named parameters)在查询字符串中是形如<literal>:name</literal>的标识符。
|
||
命名参数(named parameters)的优点是:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
命名参数(named parameters)与其在查询串中出现的顺序无关
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
它们可在同一查询串中多次出现
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
它们本身是自我说明的
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<programlisting><![CDATA[//named parameter (preferred)
|
||
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
|
||
q.setString("name", "Fritz");
|
||
Iterator cats = q.iterate();]]></programlisting>
|
||
|
||
<programlisting><![CDATA[//positional parameter
|
||
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
|
||
q.setString(0, "Izi");
|
||
Iterator cats = q.iterate();]]></programlisting>
|
||
|
||
<programlisting><![CDATA[//named parameter list
|
||
List names = new ArrayList();
|
||
names.add("Izi");
|
||
names.add("Fritz");
|
||
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
|
||
q.setParameterList("namesList", names);
|
||
List cats = q.list();]]></programlisting>
|
||
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-pagination">
|
||
<title>分页</title>
|
||
|
||
<para>
|
||
如果你需要指定结果集的范围(希望返回的最大行数/或开始的行数),应该使用<literal>Query</literal>接口提供的方法:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
|
||
q.setFirstResult(20);
|
||
q.setMaxResults(10);
|
||
List cats = q.list();]]></programlisting>
|
||
|
||
<para>
|
||
Hibernate 知道如何将这个有限定条件的查询转换成你的数据库的原生SQL(native SQL)。
|
||
</para>
|
||
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-scrolling">
|
||
<title>可滚动遍历(Scrollable iteration)</title>
|
||
|
||
|
||
<para>
|
||
如果你的JDBC驱动支持可滚动的<literal>ResuleSet</literal>,<literal>Query</literal>接口可以使用<literal>ScrollableResults</literal>,允许你在查询结果中灵活游走。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
|
||
"order by cat.name");
|
||
ScrollableResults cats = q.scroll();
|
||
if ( cats.first() ) {
|
||
|
||
// find the first name on each page of an alphabetical list of cats by name
|
||
firstNamesOfPages = new ArrayList();
|
||
do {
|
||
String name = cats.getString(0);
|
||
firstNamesOfPages.add(name);
|
||
}
|
||
while ( cats.scroll(PAGE_SIZE) );
|
||
|
||
// Now get the first page of cats
|
||
pageOfCats = new ArrayList();
|
||
cats.beforeFirst();
|
||
int i=0;
|
||
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
|
||
|
||
}
|
||
cats.close()]]></programlisting>
|
||
|
||
<para>
|
||
请注意,使用此功能需要保持数据库连接(以及游标(cursor))处于一直打开状态。
|
||
如果你需要断开连接使用分页功能,请使用<literal>setMaxResult()</literal>/<literal>setFirstResult()</literal>
|
||
</para>
|
||
|
||
</sect3>
|
||
|
||
<sect3 id="objectstate-querying-executing-named">
|
||
<title>外置命名查询(Externalizing named queries)</title>
|
||
|
||
<para>
|
||
你可以在映射文件中定义命名查询(named queries)。
|
||
(如果你的查询串中包含可能被解释为XML标记(markup)的字符,别忘了用<literal>CDATA</literal>包裹起来。)
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
|
||
from eg.DomesticCat as cat
|
||
where cat.name = ?
|
||
and cat.weight > ?
|
||
] ]></query>]]></programlisting>
|
||
|
||
|
||
<para>
|
||
参数绑定及执行以编程方式(programatically)完成:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
|
||
q.setString(0, name);
|
||
q.setInt(1, minWeight);
|
||
List cats = q.list();]]></programlisting>
|
||
|
||
|
||
<para>
|
||
请注意实际的程序代码与所用的查询语言无关,你也可在元数据中定义原生SQL(native SQL)查询,
|
||
或将原有的其他的查询语句放在配置文件中,这样就可以让Hibernate统一管理,达到迁移的目的。
|
||
</para>
|
||
|
||
</sect3>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="objectstate-filtering" revision="1">
|
||
<title>过滤集合</title>
|
||
|
||
|
||
<para>
|
||
集合<emphasis>过滤器(filter)</emphasis>是一种用于一个持久化集合或者数组的特殊的查询。查询字符串中可以使用<literal>"this"</literal>来引用集合中的当前元素。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
|
||
pk.getKittens(),
|
||
"where this.color = ?")
|
||
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
|
||
.list()
|
||
);]]></programlisting>
|
||
|
||
|
||
<para>
|
||
返回的集合可以被认为是一个包(bag, 无顺序可重复的集合(collection)),它是所给集合的副本。
|
||
原来的集合不会被改动(这与“过滤器(filter)”的隐含的含义不符,不过与我们期待的行为一致)。
|
||
</para>
|
||
|
||
<para>
|
||
请注意过滤器(filter)并不需要<literal>from</literal>子句(当然需要的话它们也可以加上)。过滤器(filter)不限定于只能返回集合元素本身。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
|
||
pk.getKittens(),
|
||
"select this.mate where this.color = eg.Color.BLACK.intValue")
|
||
.list();]]></programlisting>
|
||
|
||
<para>
|
||
即使无条件的过滤器(filter)也是有意义的。例如,用于加载一个大集合的子集:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
|
||
mother.getKittens(), "")
|
||
.setFirstResult(0).setMaxResults(10)
|
||
.list();]]></programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="objecstate-querying-criteria" revision="1">
|
||
<title>条件查询(Criteria queries)</title>
|
||
|
||
<para>
|
||
HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了直观的<literal>Criteria</literal>查询API。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
|
||
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
|
||
crit.setMaxResults(10);
|
||
List cats = crit.list();]]></programlisting>
|
||
|
||
|
||
<para>
|
||
<literal>Criteria</literal>以及相关的<literal>样例(Example)</literal>API将会再<xref linkend="querycriteria"/>中详细讨论。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
<sect2 id="objectstate-querying-nativesql" revision="2">
|
||
<title>使用原生SQL的查询</title>
|
||
|
||
|
||
<para>
|
||
你可以使用<literal>createSQLQuery()</literal>方法,用SQL来描述查询,并由Hibernate处理将结果集转换成对象的工作。
|
||
请注意,你可以在任何时候调用<literal>session.connection()</literal>来获得并使用JDBC <literal>Connection</literal>对象。
|
||
如果你选择使用Hibernate的API, 你必须把SQL别名用大括号包围起来:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
||
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
|
||
"cat",
|
||
Cat.class
|
||
).list();]]></programlisting>
|
||
|
||
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
||
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
|
||
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
|
||
"FROM CAT {cat} WHERE ROWNUM<10",
|
||
"cat",
|
||
Cat.class
|
||
).list()]]></programlisting>
|
||
|
||
|
||
<para>
|
||
和Hibernate查询一样,SQL查询也可以包含命名参数和占位参数。
|
||
可以在<xref linkend="querysql"/>找到更多关于Hibernate中原生SQL(native SQL)的信息。
|
||
</para>
|
||
|
||
</sect2>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-modifying" revision="1">
|
||
<title>修改持久对象</title>
|
||
|
||
<para>
|
||
<emphasis>事务中的持久实例</emphasis>(就是通过<literal>session</literal>装载、保存、创建或者查询出的对象)
|
||
被应用程序操作所造成的任何修改都会在<literal>Session</literal>被<emphasis>刷出(flushed)</emphasis>的时候被持久化(本章后面会详细讨论)。
|
||
这里不需要调用某个特定的方法(比如<literal>update()</literal>,设计它的目的是不同的)将你的修改持久化。
|
||
所以最直接的更新一个对象的方法就是在<literal>Session</literal>处于打开状态时<literal>load()</literal>它,然后直接修改即可:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
|
||
cat.setName("PK");
|
||
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
|
||
|
||
<para>
|
||
有时这种程序模型效率低下,因为它在同一Session里需要一条SQL <literal>SELECT</literal>语句(用于加载对象)
|
||
以及一条SQL <literal>UPDATE</literal>语句(持久化更新的状态)。
|
||
为此Hibernate提供了另一种途径,使用脱管(detached)实例。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
<emphasis>请注意Hibernate本身不提供直接执行<literal>UPDATE</literal>或<literal>DELETE</literal>语句的API。
|
||
Hibernate提供的是<emphasis>状态管理(state management)</emphasis>服务,你不必考虑要使用的<emphasis>语句(statements)</emphasis>。
|
||
JDBC是出色的执行SQL语句的API,任何时候调用<literal>session.connection()</literal>你都可以得到一个JDBC <literal>Connection</literal>对象。
|
||
此外,在联机事务处理(OLTP)程序中,大量操作(mass operations)与对象/关系映射的观点是相冲突的。
|
||
Hibernate的将来版本可能会提供专门的进行大量操作(mass operation)的功能。
|
||
参考<xref linkend="batch"/>,寻找一些可用的批量(batch)操作技巧。</emphasis>
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-detached" revision="2">
|
||
<title>修改脱管(Detached)对象</title>
|
||
|
||
<para>
|
||
很多程序需要在某个事务中获取对象,然后将对象发送到界面层去操作,最后在一个新的事务保存所做的修改。
|
||
在高并发访问的环境中使用这种方式,通常使用附带版本信息的数据来保证这些“长“工作单元之间的隔离。
|
||
</para>
|
||
|
||
<para>
|
||
Hibernate通过提供使用<literal>Session.update()</literal>或<literal>Session.merge()</literal>方法
|
||
重新关联脱管实例的办法来支持这种模型。
|
||
</para>
|
||
|
||
|
||
<programlisting><![CDATA[// in the first session
|
||
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
||
Cat potentialMate = new Cat();
|
||
firstSession.save(potentialMate);
|
||
|
||
// in a higher layer of the application
|
||
cat.setMate(potentialMate);
|
||
|
||
// later, in a new session
|
||
secondSession.update(cat); // update cat
|
||
secondSession.update(mate); // update mate]]></programlisting>
|
||
|
||
<para>
|
||
如果具有<literal>catId</literal>持久化标识的<literal>Cat</literal>之前已经被<literal>另一Session(secondSession)</literal>装载了,
|
||
应用程序进行重关联操作(reattach)的时候会抛出一个异常。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
如果你确定当前session没有包含与之具有相同持久化标识的持久实例,使用<literal>update()</literal>。
|
||
如果想随时合并你的的改动而不考虑session的状态,使用<literal>merge()</literal>。
|
||
换句话说,在一个新session中通常第一个调用的是<literal>update()</literal>方法,以便保证重新关联脱管(detached)对象的操作首先被执行。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
希望相关联的脱管对象(通过引用“可到达”的脱管对象)的数据也要更新到数据库时(并且也<emphasis>仅仅</emphasis>在这种情况),
|
||
应用程序需要对该相关联的脱管对象单独调用<literal>update()</literal>
|
||
当然这些可以自动完成,即通过使用<emphasis>传播性持久化(transitive persistence)</emphasis>,请看<xref linkend="objectstate-transitive"/>。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
<literal>lock()</literal>方法也允许程序重新关联某个对象到一个新session上。不过,该脱管(detached)的对象必须是没有修改过的!
|
||
</para>
|
||
|
||
<programlisting><![CDATA[//just reassociate:
|
||
sess.lock(fritz, LockMode.NONE);
|
||
//do a version check, then reassociate:
|
||
sess.lock(izi, LockMode.READ);
|
||
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
|
||
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
|
||
|
||
|
||
<para>
|
||
请注意,<literal>lock()</literal>可以搭配多种<literal>LockMode</literal>,
|
||
更多信息请阅读API文档以及关于事务处理(transaction handling)的章节。重新关联不是<literal>lock()</literal>的唯一用途。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
其他用于长时间工作单元的模型会在<xref linkend="transactions-optimistic"/>中讨论。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-saveorupdate">
|
||
<title>自动状态检测</title>
|
||
|
||
|
||
<para>
|
||
Hibernate的用户曾要求一个既可自动分配新持久化标识(identifier)保存瞬时(transient)对象,又可更新/重新关联脱管(detached)实例的通用方法。
|
||
<literal>saveOrUpdate()</literal>方法实现了这个功能。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[// in the first session
|
||
Cat cat = (Cat) firstSession.load(Cat.class, catID);
|
||
|
||
// in a higher tier of the application
|
||
Cat mate = new Cat();
|
||
cat.setMate(mate);
|
||
|
||
// later, in a new session
|
||
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
|
||
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)]]></programlisting>
|
||
|
||
|
||
<para>
|
||
<literal>saveOrUpdate()</literal>用途和语义可能会使新用户感到迷惑。
|
||
首先,只要你没有尝试在某个session中使用来自另一session的实例,你应该就不需要使用<literal>update()</literal>,
|
||
<literal>saveOrUpdate()</literal>,或<literal>merge()</literal>。有些程序从来不用这些方法。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
通常下面的场景会使用<literal>update()</literal>或<literal>saveOrUpdate()</literal>:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
程序在第一个session中加载对象
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
该对象被传递到表现层
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
对象发生了一些改动
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
该对象被返回到业务逻辑层
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
程序调用第二个session的<literal>update()</literal>方法持久这些改动
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
<literal>saveOrUpdate()</literal>做下面的事:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
如果对象已经在本session中持久化了,不做任何事
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果另一个与本session关联的对象拥有相同的持久化标识(identifier),抛出一个异常
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果对象没有持久化标识(identifier)属性,对其调用<literal>save()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果对象的持久标识(identifier)表明其是一个新实例化的对象,对其调用<literal>save()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果对象是附带版本信息的(通过<literal><version></literal>或<literal><timestamp></literal>)
|
||
并且版本属性的值表明其是一个新实例化的对象,<literal>save()</literal>它。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
否则<literal>update()</literal> 这个对象
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
|
||
<para>
|
||
<literal>merge()</literal>可非常不同:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
如果session中存在相同持久化标识(identifier)的实例,用用户给出的对象的状态覆盖旧有的持久实例
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果session没有相应的持久实例,则尝试从数据库中加载,或创建新的持久化实例
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
最后返回该持久实例
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
用户给出的这个对象没有被关联到session上,它依旧是脱管的
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-deleting" revision="1">
|
||
<title>删除持久对象</title>
|
||
|
||
<para>
|
||
使用<literal>Session.delete()</literal>会把对象的状态从数据库中移除。
|
||
当然,你的应用程序可能仍然持有一个指向已删除对象的引用。所以,最好这样理解:<literal>delete()</literal>的用途是把一个持久实例变成瞬时(transient)实例。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
|
||
|
||
|
||
<para>
|
||
你可以用你喜欢的任何顺序删除对象,不用担心外键约束冲突。当然,如果你搞错了顺序,还是有可能引发在外键字段定义的<literal>NOT NULL</literal>约束冲突。
|
||
例如你删除了父对象,但是忘记删除孩子们。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-replicating" revision="1">
|
||
<title>在两个不同数据库间复制对象</title>
|
||
|
||
|
||
<para>
|
||
偶尔会用到不重新生成持久化标识(identifier),将持久实例以及其关联的实例持久到不同的数据库中的操作。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[//retrieve a cat from one database
|
||
Session session1 = factory1.openSession();
|
||
Transaction tx1 = session1.beginTransaction();
|
||
Cat cat = session1.get(Cat.class, catId);
|
||
tx1.commit();
|
||
session1.close();
|
||
|
||
//reconcile with a second database
|
||
Session session2 = factory2.openSession();
|
||
Transaction tx2 = session2.beginTransaction();
|
||
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
|
||
tx2.commit();
|
||
session2.close();]]></programlisting>
|
||
|
||
|
||
<para>
|
||
<literal>ReplicationMode</literal>决定数据库中已存在相同行时,<literal>replicate()</literal>如何处理。
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
<literal>ReplicationMode.IGNORE</literal> - 忽略它
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>ReplicationMode.OVERWRITE</literal> - 覆盖相同的行
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>ReplicationMode.EXCEPTION</literal> - 抛出异常
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>ReplicationMode.LATEST_VERSION</literal> - 如果当前的版本较新,则覆盖,否则忽略
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
|
||
<para>
|
||
这个功能的用途包括使录入的数据在不同数据库中一致,产品升级时升级系统配置信息,回滚non-ACID事务中的修改等等。
|
||
(译注,non-ACID,非ACID;ACID,Atomic,Consistent,Isolated and Durable的缩写)
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-flushing">
|
||
<title>Session刷出(flush)</title>
|
||
|
||
|
||
<para>
|
||
每间隔一段时间,<literal>Session</literal>会执行一些必需的SQL语句来把内存中的对象的状态同步到JDBC连接中。这个过程被称为<emphasis>刷出(flush)</emphasis>,默认会在下面的时间点执行:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
在某些查询执行之前
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
在调用<literal>org.hibernate.Transaction.commit()</literal>的时候
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
在调用<literal>Session.flush()</literal>的时候
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
涉及的SQL语句会按照下面的顺序发出执行:
|
||
</para>
|
||
|
||
<orderedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
所有对实体进行插入的语句,其顺序按照对象执行<literal>Session.save()</literal>的时间顺序
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
所有对实体进行更新的语句
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
所有进行集合删除的语句
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
所有对集合元素进行删除,更新或者插入的语句
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
所有进行集合插入的语句
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
所有对实体进行删除的语句,其顺序按照对象执行<literal>Session.delete()</literal>的时间顺序
|
||
</para>
|
||
</listitem>
|
||
</orderedlist>
|
||
|
||
<para>
|
||
(有一个例外是,如果对象使用<literal>native</literal>方式来生成ID(持久化标识)的话,它们一执行save就会被插入。)
|
||
</para>
|
||
|
||
<para>
|
||
除非你明确地发出了<literal>flush()</literal>指令,关于Session<emphasis>何时</emphasis>会执行这些JDBC调用是完全无法保证的,只能保证它们执行的前后顺序。
|
||
当然,Hibernate保证,<literal>Query.list(..)</literal>绝对不会返回已经失效的数据,也不会返回错误数据。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
也可以改变默认的设置,来让刷出(flush)操作发生的不那么频繁。
|
||
<literal>FlushMode</literal>类定义了三种不同的方式。
|
||
仅在提交时刷出(仅当Hibernate的<literal>Transaction</literal> API被使用时有效),
|
||
按照刚才说的方式刷出,
|
||
以及除非明确使用<literal>flush()</literal>否则从不刷出。
|
||
最后一种模式对于那些需要长时间保持<literal>Session</literal>为打开或者断线状态的长时间运行的工作单元很有用。
|
||
(参见 <xref linkend="transactions-optimistic-longsession"/>).
|
||
</para>
|
||
|
||
<programlisting><![CDATA[sess = sf.openSession();
|
||
Transaction tx = sess.beginTransaction();
|
||
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
|
||
|
||
Cat izi = (Cat) sess.load(Cat.class, id);
|
||
izi.setName(iznizi);
|
||
|
||
// might return stale data
|
||
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
||
|
||
// change to izi is not flushed!
|
||
...
|
||
tx.commit(); // flush occurs]]></programlisting>
|
||
|
||
<para>
|
||
刷出(flush)期间,可能会抛出异常。(例如一个DML操作违反了约束)
|
||
异常处理涉及到对Hibernate事务性行为的理解,因此我们将在<xref linkend="transactions"/>中讨论。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-transitive">
|
||
<title>传播性持久化(transitive persistence)</title>
|
||
|
||
<para>
|
||
对每一个对象都要执行保存,删除或重关联操作让人感觉有点麻烦,尤其是在处理许多彼此关联的对象的时候。
|
||
一个常见的例子是父子关系。考虑下面的例子:
|
||
</para>
|
||
|
||
|
||
<para>
|
||
如果一个父子关系中的子对象是值类型(value typed)(例如,地址或字符串的集合)的,他们的生命周期会依赖于父对象,可以享受方便的级联操作(Cascading),不需要额外的动作。
|
||
父对象被保存时,这些值类型(value typed)子对象也将被保存;父对象被删除时,子对象也将被删除。
|
||
这对将一个子对象从集合中移除是同样有效:Hibernate会检测到,并且因为值类型(value typed)的对象不可能被其他对象引用,所以Hibernate会在数据库中删除这个子对象。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
现在考虑同样的场景,不过父子对象都是实体(entities)类型,而非值类型(value typed)(例如,类别与个体,或母猫和小猫)。
|
||
实体有自己的生命期,允许共享对其的引用(因此从集合中移除一个实体,不意味着它可以被删除),
|
||
并且实体到其他关联实体之间默认没有级联操作的设置。
|
||
Hibernate默认不实现所谓的<emphasis>可到达即持久化(persistence by reachability)</emphasis>的策略。
|
||
</para>
|
||
|
||
<para>
|
||
每个Hibernate session的基本操作 - 包括 <literal>persist(), merge(),
|
||
saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()</literal> - 都有对应的级联风格(cascade style)。
|
||
这些级联风格(cascade style)风格分别命名为 <literal>create,
|
||
merge, save-update, delete, lock, refresh, evict, replicate</literal>。
|
||
如果你希望一个操作被顺着关联关系级联传播,你必须在映射文件中指出这一点。例如:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<one-to-one name="person" cascade="persist"/>]]></programlisting>
|
||
|
||
|
||
<para>
|
||
级联风格(cascade style)是可组合的:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<one-to-one name="person" cascade="persist,delete,lock"/>]]></programlisting>
|
||
|
||
|
||
<para>
|
||
你可以使用<literal>cascade="all"</literal>来指定<emphasis>全部</emphasis>操作都顺着关联关系级联(cascaded)。
|
||
默认值是<literal>cascade="none"</literal>,即任何操作都不会被级联(cascaded)。
|
||
</para>
|
||
|
||
<para>
|
||
注意有一个特殊的级联风格(cascade style) <literal>delete-orphan</literal>,只应用于one-to-many关联,表明<literal>delete()</literal>操作
|
||
应该被应用于所有从关联中删除的对象。
|
||
</para>
|
||
|
||
|
||
|
||
<para>
|
||
建议:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
通常在<literal><many-to-one></literal>或<literal><many-to-many></literal>关系中应用级联(cascade)没什么意义。
|
||
级联(cascade)通常在 <literal><one-to-one></literal>和<literal><one-to-many></literal>关系中比较有用。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果子对象的寿命限定在父亲对象的寿命之内,可通过指定<literal>cascade="all,delete-orphan"</literal>将其变为<emphasis>自动生命周期管理的对象(lifecycle object)</emphasis>。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
其他情况,你可根本不需要级联(cascade)。但是如果你认为你会经常在某个事务中同时用到父对象与子对象,并且你希望少打点儿字,可以考虑使用<literal>cascade="persist,merge,save-update"</literal>。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
可以使用<literal>cascade="all"</literal>将一个关联关系(无论是对值对象的关联,或者对一个集合的关联)标记为<emphasis>父/子</emphasis>关系的关联。
|
||
这样对父对象进行save/update/delete操作就会导致子对象也进行save/update/delete操作。
|
||
</para>
|
||
|
||
<para>
|
||
此外,一个持久的父对象对子对象的浅引用(mere reference)会导致子对象被同步save/update。
|
||
不过,这个隐喻(metaphor)的说法并不完整。除非关联是<literal><one-to-many></literal>关联并且被标记为<literal>cascade="delete-orphan"</literal>,
|
||
否则父对象失去对某个子对象的引用<emphasis>不会</emphasis>导致该子对象被自动删除。
|
||
父子关系的级联(cascading)操作准确语义如下:
|
||
</para>
|
||
|
||
<itemizedlist spacing="compact">
|
||
<listitem>
|
||
<para>
|
||
如果父对象被<literal>persist()</literal>,那么所有子对象也会被<literal>persist()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果父对象被<literal>merge()</literal>,那么所有子对象也会被<literal>merge()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果父对象被<literal>save()</literal>,<literal>update()</literal>或
|
||
<literal>saveOrUpdate()</literal>,那么所有子对象则会被<literal>saveOrUpdate()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果某个持久的父对象引用了瞬时(transient)或者脱管(detached)的子对象,那么子对象将会被<literal>saveOrUpdate()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
如果父对象被删除,那么所有子对象也会被<literal>delete()</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
除非被标记为<literal>cascade="delete-orphan"</literal>(删除“孤儿”模式,此时不被任何一个父对象引用的子对象会被删除),
|
||
否则子对象失掉父对象对其的引用时,<emphasis>什么事也不会发生</emphasis>。
|
||
如果有特殊需要,应用程序可通过显式调用delete()删除子对象。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-metadata">
|
||
<title>使用元数据</title>
|
||
|
||
<para>
|
||
Hibernate中有一个非常丰富的元级别(meta-level)的模型,含有所有的实体和值类型数据的元数据。
|
||
有时这个模型对应用程序本身也会非常有用。
|
||
比如说,应用程序可能在实现一种“智能”的深度拷贝算法时,
|
||
通过使用Hibernate的元数据来了解哪些对象应该被拷贝(比如,可变的值类型数据),
|
||
那些不应该(不可变的值类型数据,也许还有某些被关联的实体)。
|
||
</para>
|
||
|
||
|
||
<para>
|
||
Hibernate提供了<literal>ClassMetadata</literal>接口,<literal>CollectionMetadata</literal>接口和<literal>Type</literal>层次体系来访问元数据。
|
||
可以通过<literal>SessionFactory</literal>获取元数据接口的实例。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Cat fritz = ......;
|
||
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
|
||
|
||
Object[] propertyValues = catMeta.getPropertyValues(fritz);
|
||
String[] propertyNames = catMeta.getPropertyNames();
|
||
Type[] propertyTypes = catMeta.getPropertyTypes();
|
||
|
||
// get a Map of all properties which are not collections or associations
|
||
Map namedValues = new HashMap();
|
||
for ( int i=0; i<propertyNames.length; i++ ) {
|
||
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
|
||
namedValues.put( propertyNames[i], propertyValues[i] );
|
||
}
|
||
}]]></programlisting>
|
||
|
||
</sect1>
|
||
|
||
</chapter>
|
||
|