doc'd natural-id
updated best practices git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@6901 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
1ccd7b67d8
commit
24f3e76c9a
|
@ -1710,6 +1710,44 @@
|
||||||
|
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="mapping-declaration-naturalid">
|
||||||
|
<title>natural-id</title>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<natural-id mutable="true|false"/>
|
||||||
|
<property ... />
|
||||||
|
<many-to-one ... />
|
||||||
|
......
|
||||||
|
</natural-id>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Even though we recommend the use of surrogate keys as primary keys, you should still try
|
||||||
|
to identify natural keys for all entities. A natural key is a property or combination of
|
||||||
|
properties that is unique and non-null. If it is also immutable, even better. Map the
|
||||||
|
properties of the natural key inside the <literal><natural-id></literal> element.
|
||||||
|
Hibernate will generate the necessary unique key and nullability constraints, and your
|
||||||
|
mapping will be more self-documenting.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We strongly recommend that you implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal> to compare the natural key properties of the entity.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This mapping is not intended for use with entities with natural primary keys.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>mutable</literal> (optional, defaults to <literal>false</literal>):
|
||||||
|
By default, natural identifier properties as assumed to be immutable (constant).
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,17 @@
|
||||||
<para>
|
<para>
|
||||||
Hibernate makes identifier properties optional. There are all sorts of reasons why
|
Hibernate makes identifier properties optional. There are all sorts of reasons why
|
||||||
you should use them. We recommend that identifiers be 'synthetic' (generated, with
|
you should use them. We recommend that identifiers be 'synthetic' (generated, with
|
||||||
no business meaning). It doesn't make a difference if you use <literal>long</literal>
|
no business meaning).
|
||||||
or <literal>java.lang.Long</literal>; primitives might be syntactically easier to handle
|
</para>
|
||||||
though.
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identify natural keys.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Identify natural keys for all entities, and map them using
|
||||||
|
<literal><natural-id></literal>. Implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal> to compare the properties that make up the natural key.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -47,7 +55,8 @@
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
This is a good practice if your queries call non-ANSI-standard SQL functions.
|
This is a good practice if your queries call non-ANSI-standard SQL functions.
|
||||||
Externalising the query strings to mapping files will make the application more portable.
|
Externalising the query strings to mapping files will make the application more
|
||||||
|
portable.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -86,12 +95,11 @@
|
||||||
<term>Use hand-coded JDBC in bottlenecks.</term>
|
<term>Use hand-coded JDBC in bottlenecks.</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
In performance-critical areas of the system, some kinds of operations (eg. mass update /
|
In performance-critical areas of the system, some kinds of operations might benefit from
|
||||||
delete) might benefit from direct JDBC. But please, wait until you <emphasis>know</emphasis>
|
direct JDBC. But please, wait until you <emphasis>know</emphasis> something is a bottleneck.
|
||||||
something is a bottleneck. And don't assume that direct JDBC is necessarily faster. If need to
|
And don't assume that direct JDBC is necessarily faster. If you need to use direct JDBC, it might
|
||||||
use direct JDBC, it might be worth opening a Hibernate <literal>Session</literal> and using that SQL
|
be worth opening a Hibernate <literal>Session</literal> and using that JDBC connection. That
|
||||||
connection. That way you can still use the same transaction strategy and underlying connection
|
way you can still use the same transaction strategy and underlying connection provider.
|
||||||
provider.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -107,27 +115,29 @@
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>In a three tiered architecture, consider using <literal>saveOrUpdate()</literal>.</term>
|
<term>In a three tiered architecture, consider using detached objects.</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
When using a servlet / session bean architecture, you could pass persistent objects loaded in
|
When using a servlet / session bean architecture, you could pass persistent objects loaded in
|
||||||
the session bean to and from the servlet / JSP layer. Use a new session to service each request.
|
the session bean to and from the servlet / JSP layer. Use a new session to service each request.
|
||||||
Use <literal>Session.update()</literal> or <literal>Session.saveOrUpdate()</literal> to update the
|
Use <literal>Session.merge()</literal> or <literal>Session.saveOrUpdate()</literal> to
|
||||||
persistent state of an object.
|
synchronize objects with the database.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>In a two tiered architecture, consider using session disconnection.</term>
|
<term>In a two tiered architecture, consider using long persistence contexts.</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Database Transactions have to be as short as possible for best scalability. However, it is often
|
Database Transactions have to be as short as possible for best scalability. However, it is often
|
||||||
neccessary to implement long running Application Transactions, a single unit-of-work from the
|
neccessary to implement long running <emphasis>application transactions</emphasis>, a single
|
||||||
point of view of a user. This Application Transaction might span several client requests and
|
unit-of-work from the point of view of a user. An application transaction might span several
|
||||||
response cycles. Either use Detached Objects or, in two tiered architectures, simply disconnect
|
client request/response cycles. It is common to use detached objects to implement application
|
||||||
the Hibernate Session from the JDBC connection and reconnect it for each subsequent request. Never
|
transactions. An alternative, extremely appropriate in two tiered architecture, is to maintain
|
||||||
use a single Session for more than one Application Transaction usecase, otherwise, you will run
|
a single open persistence contact (session) for the whole lifecycle of the application transaction
|
||||||
into stale data.
|
and simply disconnect from the JDBC connection at the end of each request and reconnect at the
|
||||||
|
beginning of the subsequent request. Never share a single session across more than one application
|
||||||
|
transaction, or you will be working with stale data.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -139,7 +149,7 @@
|
||||||
the <literal>Transaction</literal> and close the <literal>Session</literal>. If you don't, Hibernate
|
the <literal>Transaction</literal> and close the <literal>Session</literal>. If you don't, Hibernate
|
||||||
can't guarantee that in-memory state accurately represents persistent state. As a special case of this,
|
can't guarantee that in-memory state accurately represents persistent state. As a special case of this,
|
||||||
do not use <literal>Session.load()</literal> to determine if an instance with the given identifier
|
do not use <literal>Session.load()</literal> to determine if an instance with the given identifier
|
||||||
exists on the database; use <literal>find()</literal> instead.
|
exists on the database; use <literal>Session.get()</literal> or a query instead.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -147,11 +157,30 @@
|
||||||
<term>Prefer lazy fetching for associations.</term>
|
<term>Prefer lazy fetching for associations.</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Use eager (outer-join) fetching sparingly. Use proxies and/or lazy collections for most associations
|
Use eager fetching sparingly. Use proxies and lazy collections for most associations to classes that
|
||||||
to classes that are not cached at the JVM-level. For associations to cached classes, where there is
|
are not likely to be completely held in the second-level cache. For associations to cached classes,
|
||||||
a high probability of a cache hit, explicitly disable eager fetching using
|
where there is an a extremely high probability of a cache hit, explicitly disable eager fetching using
|
||||||
<literal>outer-join="false"</literal>. When an outer-join fetch is appropriate to a particular use
|
<literal>lazy="false"</literal>. When an join fetching is appropriate to a particular use
|
||||||
case, use a query with a <literal>left join</literal>.
|
case, use a query with a <literal>left join fetch</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Use the <emphasis>open session in view</emphasis> pattern, or a disciplined
|
||||||
|
<emphasis>assembly phase</emphasis> to avoid problems with unfetched data.
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate frees the developer from writing tedious <emphasis>Data Transfer Objects</emphasis> (DTO).
|
||||||
|
In a traditional EJB architecture, DTOs serve dual purposes: first, they work around the problem
|
||||||
|
that entity beans are not serializable; second, they implicitly define an assembly phase where
|
||||||
|
all data to be used by the view is fetched and marshalled into the DTOs before returning control
|
||||||
|
to the presentation tier. Hibernate eliminates the first purpose. However, you will still need
|
||||||
|
an assembly phase (think of your business methods as having a strict contract with the presentation
|
||||||
|
tier about what data is available in the detached objects) unless you are prepared to hold the
|
||||||
|
persistence context (the session) open across the view rendering process. This is not a limitation
|
||||||
|
of Hibernate! It is a fundamental requirement of safe transactional data access.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -167,23 +196,6 @@
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
|
||||||
<term>Implement <literal>equals()</literal> and <literal>hashCode()</literal> using a unique business key.</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
If you compare objects outside of the Session scope, you have to implement <literal>equals()</literal>
|
|
||||||
and <literal>hashCode()</literal>. Inside the Session scope, Java object identity is guaranteed. If
|
|
||||||
you implement these methods, never ever use the database identifier! A transient object doesn't have
|
|
||||||
an identifier value and Hibernate would assign a value when the object is saved. If the object
|
|
||||||
is in a Set while being saved, the hash code changes, breaking the contract. To implement
|
|
||||||
<literal>equals()</literal> and <literal>hashCode()</literal>, use a unique business key, that is,
|
|
||||||
compare a unique combination of class properties. Remember that this key has to be stable and unique
|
|
||||||
only while the object is in a Set, not for the whole lifetime (not as stable as a database primary
|
|
||||||
key). Never use collections in the <literal>equals()</literal> comparison (lazy loading) and be careful
|
|
||||||
with other associated classes that might be proxied.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>Don't use exotic association mappings.</term>
|
<term>Don't use exotic association mappings.</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
@ -196,6 +208,15 @@
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Prefer bidirectional associations.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Unidirectional associations are more difficult to query. In a large application, almost
|
||||||
|
all associations must be navigable in both directions in queries.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|
|
@ -52,9 +52,9 @@ List cats = crit.list();]]></programlisting>
|
||||||
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
|
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
.add( Restrictions.disjunction()
|
.add( Restrictions.disjunction()
|
||||||
.add( Restrictions.isNull("age") )
|
.add( Restrictions.isNull("age") )
|
||||||
.add( Restrictions.eq("age", new Integer(0) ) )
|
.add( Restrictions.eq("age", new Integer(0) ) )
|
||||||
.add( Restrictions.eq("age", new Integer(1) ) )
|
.add( Restrictions.eq("age", new Integer(1) ) )
|
||||||
.add( Restrictions.eq("age", new Integer(2) ) )
|
.add( Restrictions.eq("age", new Integer(2) ) )
|
||||||
) )
|
) )
|
||||||
.list();]]></programlisting>
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
@ -83,9 +83,9 @@ Property age = Property.forName("age");
|
||||||
List cats = sess.createCriteria(Cat.class)
|
List cats = sess.createCriteria(Cat.class)
|
||||||
.add( Restrictions.disjunction()
|
.add( Restrictions.disjunction()
|
||||||
.add( age.isNull() )
|
.add( age.isNull() )
|
||||||
.add( age.eq( new Integer(0) ) )
|
.add( age.eq( new Integer(0) ) )
|
||||||
.add( age.eq( new Integer(1) ) )
|
.add( age.eq( new Integer(1) ) )
|
||||||
.add( age.eq( new Integer(2) ) )
|
.add( age.eq( new Integer(2) ) )
|
||||||
) )
|
) )
|
||||||
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
|
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
.list();]]></programlisting>
|
.list();]]></programlisting>
|
||||||
|
@ -332,13 +332,13 @@ List results = session.createCriteria(Cat.class)
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
<sect1 id="querycriteria-detachedqueries">
|
<sect1 id="querycriteria-detachedqueries">
|
||||||
<title>Detached queries and subqueries</title>
|
<title>Detached queries and subqueries</title>
|
||||||
<para>
|
<para>
|
||||||
The <literal>DetachedCriteria</literal> class lets you create a query outside the scope
|
The <literal>DetachedCriteria</literal> class lets you create a query outside the scope
|
||||||
of a session, and then later execute it using some arbitrary <literal>Session</literal>.
|
of a session, and then later execute it using some arbitrary <literal>Session</literal>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
|
<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
|
||||||
.add( Property.forName("sex").eq('F') );
|
.add( Property.forName("sex").eq('F') );
|
||||||
|
|
||||||
Session session = ....;
|
Session session = ....;
|
||||||
|
@ -347,38 +347,90 @@ List results = query.getExecutableCriteria(session).setMaxResults(100).list();
|
||||||
txn.commit();
|
txn.commit();
|
||||||
session.close();]]></programlisting>
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
A <literal>DetachedCriteria</literal> may also be used to express a subquery. Criterion
|
A <literal>DetachedCriteria</literal> may also be used to express a subquery. Criterion
|
||||||
instances involving subqueries may be obtained via <literal>Subqueries</literal> or
|
instances involving subqueries may be obtained via <literal>Subqueries</literal> or
|
||||||
<literal>Property</literal>.
|
<literal>Property</literal>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting><![CDATA[DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
|
|
||||||
.setProjection( Property.forName("weight").avg() );
|
|
||||||
session.createCriteria(Cat.class)
|
|
||||||
.add( Property.forName("weight).gt(avgWeight) )
|
|
||||||
.list();]]></programlisting>
|
|
||||||
|
|
||||||
<programlisting><![CDATA[DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
|
|
||||||
.setProjection( Property.forName("weight") );
|
|
||||||
session.createCriteria(Cat.class)
|
|
||||||
.add( Subqueries.geAll("weight", weights) )
|
|
||||||
.list();]]></programlisting>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
Even correlated subqueries are possible:
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<programlisting><![CDATA[DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
|
<programlisting><![CDATA[DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
|
||||||
.setProjection( Property.forName("weight").avg() )
|
.setProjection( Property.forName("weight").avg() );
|
||||||
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Property.forName("weight).gt(avgWeight) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.setProjection( Property.forName("weight") );
|
||||||
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Subqueries.geAll("weight", weights) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Even correlated subqueries are possible:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
|
||||||
|
.setProjection( Property.forName("weight").avg() )
|
||||||
|
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
|
||||||
session.createCriteria(Cat.class, "cat")
|
session.createCriteria(Cat.class, "cat")
|
||||||
.add( Property.forName("weight).gt(avgWeightForSex) )
|
.add( Property.forName("weight).gt(avgWeightForSex) )
|
||||||
.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>
|
||||||
|
For most queries, including criteria queries, the query cache is not very efficient,
|
||||||
|
because query cache invalidation occurs too frequently. However, there is one special
|
||||||
|
kind of query where we can optimize the cache invalidation algorithm: lookups by a
|
||||||
|
constant natural key. In some applications, this kind of query occurs frequently.
|
||||||
|
The criteria API provides special provision for this use case.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
First, you should map the natural key of your entity using
|
||||||
|
<literal><natural-id></literal>, and enable use of the second-level cache.
|
||||||
|
</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>
|
||||||
|
Note that this functionality is not intended for use with entities with
|
||||||
|
<emphasis>mutable</emphasis> natural keys.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Next, enable the Hibernate query cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now, <literal>Restrictions.naturalId()</literal> allows us to make use of
|
||||||
|
the more efficient cache algorithm.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.createCriteria(User.class)
|
||||||
|
.add( Restrictions.naturalId()
|
||||||
|
.set("name", "gavin")
|
||||||
|
.set("org", "hb")
|
||||||
|
).setCacheable(true)
|
||||||
|
.uniqueResult();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|
Loading…
Reference in New Issue