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:
Gavin King 2005-05-25 02:56:10 +00:00
parent 1ccd7b67d8
commit 24f3e76c9a
3 changed files with 199 additions and 88 deletions

View File

@ -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>&lt;natural-id&gt;</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>

View File

@ -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>&lt;natural-id&gt;</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>

View File

@ -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>&lt;natural-id&gt;</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>