2005-02-02 11:45:30 -05:00
|
|
|
<chapter id="objectstate">
|
|
|
|
<title>Working with objects</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate is a full object/relational mapping solution that not only shields
|
|
|
|
the developer from the details of the underlying database management
|
|
|
|
system, but also offers <emphasis>state management</emphasis> of objects. This is,
|
|
|
|
contrary to the management of SQL <literal>statements</literal> in common JDBC/SQL
|
|
|
|
persistence layers, a very natural object-oriented view of persistence in Java
|
|
|
|
applications.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
In other words, Hibernate application developers should always think about the
|
|
|
|
<emphasis>state</emphasis> of their objects, and not necessarily about the
|
|
|
|
execution of SQL statements. This part is taken care of by Hibernate and is only
|
|
|
|
relevant for the application developer when tuning the performance of the system.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<sect1 id="objectstate-overview">
|
|
|
|
<title>Hibernate object states</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Hibernate defines and supports the following object states:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<emphasis>Transient</emphasis> - an object is transient if it has just
|
|
|
|
been instantiated using the <literal>new</literal> operator, and it
|
|
|
|
is not associated with a Hibernate <literal>Session</literal>. It has no
|
|
|
|
persistent representation in the database and no identifier value has been
|
|
|
|
assigned. Transient instances will be destroyed by the garbage collector of
|
|
|
|
the application doesn't hold a reference anymore. Use the Hibernate
|
|
|
|
<literal>Session</literal> to make an object persisent (and let Hibernate
|
|
|
|
take care of the SQL statements that need to be executed for this transition).
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<emphasis>Persistent</emphasis> - a persistent instance has a representation
|
|
|
|
in the database and an identifier value. It might just have been saved or loaded,
|
|
|
|
however, it is by definition in the scope of a <literal>Session</literal>.
|
|
|
|
Hibernate will detect any changes made to an object in persistent state and
|
|
|
|
synchronize the state with the database when the unit of work completes.
|
|
|
|
Developers don't execute manual <literal>UPDATE</literal> statements, or
|
|
|
|
<literal>DELETE</literal> statements when an object should be made transient.
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<emphasis>Detached</emphasis> - a detached instance is an object that has been
|
|
|
|
persistent, but its <literal>Session</literal> has been closed. The reference
|
|
|
|
to the object is still valid, of course, and the detached instance might even
|
|
|
|
be modified in this state. A detached instance can be reattached to a new
|
|
|
|
<literal>Session</literal> at a later point in time, making it (and all the
|
|
|
|
modifications) persistent again. This feature enables a programming model for
|
|
|
|
long running units of work that require user think-time. We call them
|
|
|
|
<emphasis>application transactions</emphasis>, i.e. a unit of work from the
|
|
|
|
point of view of the user.
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
We'll know discuss the states and state transitions (and the Hibernate methods that
|
|
|
|
trigger a transition) in more detail.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
|
|
|
<sect1 id="objectstate-makingpersistent" revision="1">
|
|
|
|
<title>Making objects persistent</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
Newly instantiated instances of a a persistent class are considered
|
|
|
|
<emphasis>transient</emphasis> by Hibernate. We can make a transient
|
|
|
|
instance <emphasis>persistent</emphasis> by associating it with a
|
|
|
|
session:
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
|
|
|
|
fritz.setColor(Color.GINGER);
|
|
|
|
fritz.setSex('M');
|
|
|
|
fritz.setName("Fritz");
|
|
|
|
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
|
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
<para>
|
|
|
|
If <literal>Cat</literal> has a generated identifier, the identifier is
|
|
|
|
generated and assigned to the <literal>cat</literal> when <literal>save()</literal>
|
|
|
|
is called. If <literal>Cat</literal> has an <literal>assigned</literal>
|
|
|
|
identifier, or a composite key, the identifier should be assigned to
|
|
|
|
the <literal>cat</literal> instance before calling <literal>save()</literal>.
|
2005-01-24 09:54:21 -05:00
|
|
|
You may also use <literal>create()</literal> instead of <literal>save()</literal>,
|
|
|
|
with the semantics defined in the EJB3 early draft.
|
2004-08-22 20:56:36 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Alternatively, you may assign the identifier using an overloaded version
|
|
|
|
of <literal>save()</literal>.
|
|
|
|
</para>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<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>
|
2004-08-22 20:56:36 -04:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
If the object you make persistent has associated objects (e.g. the
|
|
|
|
<literal>kittens</literal> collection role) in the previous example),
|
|
|
|
these objects may be made persistent in any order you like unless you
|
2004-06-03 12:31:32 -04:00
|
|
|
have a <literal>NOT NULL</literal> constraint upon a foreign key column.
|
|
|
|
There is never a risk of violating foreign key constraints. However, you
|
|
|
|
might violate a <literal>NOT NULL</literal> constraint if you
|
|
|
|
<literal>save()</literal> the objects in the wrong order.
|
|
|
|
</para>
|
2004-08-22 20:56:36 -04:00
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
Usually you don't bother with this detail, as you'd very likely use Hibernate's
|
|
|
|
<emphasis>transitive persistence</emphasis> feature to save the associated
|
|
|
|
objects automatically. Then, <literal>NOT NULL</literal>
|
2004-08-22 20:56:36 -04:00
|
|
|
constraint violations are impossible - Hibernate will take care of everything.
|
2005-02-02 11:45:30 -05:00
|
|
|
Transitive persistence is discussed later in this chapter.
|
2004-08-22 20:56:36 -04:00
|
|
|
</para>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-loading">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Loading an object</title>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
The <literal>load()</literal> methods of <literal>Session</literal> gives you
|
2004-06-03 12:31:32 -04:00
|
|
|
a way to retrieve a persistent instance if you already know its identifier.
|
2004-08-22 20:56:36 -04:00
|
|
|
<literal>load()</literal> takes a class object and will load the state into
|
2005-02-02 11:45:30 -05:00
|
|
|
a newly instantiated instance of that class, in persistent state.
|
2004-06-03 12:31:32 -04:00
|
|
|
</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>
|
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
<para>
|
|
|
|
Alternatively, you can load state into a given instance:
|
|
|
|
</para>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
|
|
|
// load pk's state into cat
|
|
|
|
sess.load( cat, new Long(pkId) );
|
|
|
|
Set kittens = cat.getKittens();]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
Note that <literal>load()</literal> will throw an unrecoverable exception if
|
|
|
|
there is no matching database row. If the class is mapped with a proxy,
|
|
|
|
<literal>load()</literal> just returns an uninitialized proxy and does not
|
|
|
|
actually hit the database until you invoke a method of the proxy. This
|
|
|
|
behaviour is very useful if you wish to create an association to an object
|
|
|
|
without actually loading it from the database. It also allows multiple
|
|
|
|
instances to be loaded as a batch if <literal>batch-size</literal> is
|
|
|
|
defined for the class mapping.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
If you are not certain that a matching row exists, you should use the
|
|
|
|
<literal>get()</literal> method, which hits the database immediately and
|
|
|
|
returns null if there is no matching row.
|
2004-06-03 12:31:32 -04:00
|
|
|
</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>
|
2005-02-02 11:45:30 -05:00
|
|
|
You may even load an object using an SQL <literal>SELECT ... FOR UPDATE</literal>,
|
|
|
|
using a <literal>LockMode</literal>. See the API documentation for more information.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
Note that any associated instances or contained collections are
|
2005-01-24 09:54:21 -05:00
|
|
|
<emphasis>not</emphasis> selected <literal>FOR UPDATE</literal>, unless you decide
|
|
|
|
to specify <literal>lock</literal> or <literal>all</literal> as a
|
|
|
|
cascade style for the association.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
It is possible to re-load an object and all its collections at any time, using the
|
|
|
|
<literal>refresh()</literal> method. This is useful when database triggers are used to
|
|
|
|
initialize some of the properties of the object.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[sess.save(cat);
|
|
|
|
sess.flush(); //force the SQL INSERT
|
|
|
|
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
|
|
|
|
|
2005-01-24 09:54:21 -05:00
|
|
|
<para>
|
|
|
|
An important question usually appears at this point: How much does Hibernate load
|
|
|
|
from the database and how many SQL <literal>SELECT</literal>s will it use? This
|
|
|
|
depends on the <emphasis>fetching strategy</emphasis> and is explained in
|
|
|
|
<xref linkend="performance-fetching"/>.
|
|
|
|
</para>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-querying" revision="1">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Querying</title>
|
|
|
|
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
If you don't know the identifiers of the objects you are looking for,
|
|
|
|
you need a query. Hibernate supports an easy-to-use but powerful object
|
2005-02-02 11:45:30 -05:00
|
|
|
oriented query language (HQL). For programmatic query creation, Hibernate
|
|
|
|
supports a sophisticated Criteria and Example query feature (QBC and QBE).
|
|
|
|
You may also express your query in the native SQL of your database, with
|
|
|
|
optional support from Hibernate for result set conversion into objects.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect2 id="objectstate-querying-executing">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Executing queries</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
HQL and native SQL queries are represented with an instance of <literal>org.hibernate.Query</literal>.
|
|
|
|
This interface offers methods for parameter binding, result set handling, and for the execution
|
|
|
|
of the actual query. You always obtain a <literal>Query</literal> using the current
|
|
|
|
<literal>Session</literal>:
|
|
|
|
</para>
|
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
<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();
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
List kittens = session.createQuery(
|
|
|
|
"from Cat as cat where cat.mother = ?")
|
|
|
|
.setEntity(0, pk)
|
|
|
|
.list();
|
2005-02-02 11:45:30 -05:00
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
Cat mother = (Cat) session.createQuery(
|
|
|
|
"select cat.mother from Cat as cat where cat = ?")
|
|
|
|
.setEntity(0, izi)
|
|
|
|
.uniqueResult();]]></programlisting>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
A query is usually executed by invoking <literal>list()</literal>, the
|
|
|
|
result of the query will be loaded completely into a collection in memory.
|
|
|
|
Entity instance retrieved by a query are in persistent state. The
|
|
|
|
<literal>uniqueResult()</literal> method offers a shortcut if you
|
|
|
|
know your query will only return a single object.
|
|
|
|
</para>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-iterate">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Iterating results</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Occasionally, you might be able to achieve better performance by
|
|
|
|
executing the query using the <literal>iterate()</literal> method.
|
|
|
|
This will only usually be the case if you expect that the actual
|
|
|
|
entity instances returned by the query will already be in the session
|
|
|
|
or second-level cache. If they are not already cached,
|
|
|
|
<literal>iterate()</literal> will be slower than <literal>list()</literal>
|
|
|
|
and might require many database hits for a simple query, usually
|
|
|
|
<emphasis>1</emphasis> for the initial select which only returns identifiers,
|
|
|
|
and <emphasis>n</emphasis> additional selects to initialize the actual instances.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[// fetch ids
|
|
|
|
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
|
2004-06-03 12:31:32 -04:00
|
|
|
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;
|
|
|
|
}
|
2005-02-02 11:45:30 -05:00
|
|
|
}]]></programlisting>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Hibernate queries sometimes return tuples of objects, in which case each tuple
|
|
|
|
is returned as an array:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
|
|
|
|
"select kitten, mother from Cat kitten join kitten.mother mother")
|
|
|
|
.list()
|
|
|
|
.iterator();
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
while ( kittensAndMothers.hasNext() ) {
|
|
|
|
Object[] tuple = (Object[]) kittensAndMothers.next();
|
2005-02-02 11:45:30 -05:00
|
|
|
Cat kitten = tuple[0];
|
|
|
|
Cat mother = tuple[1];
|
2004-06-03 12:31:32 -04:00
|
|
|
....
|
|
|
|
}]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
</sect3>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-scalar">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Scalar results</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Queries may specify a property of a class in the <literal>select</literal> clause.
|
|
|
|
They may even call SQL aggregate functions. Properties or aggregates are considered
|
|
|
|
"scalar" results (and not entities in 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();
|
2004-08-22 20:56:36 -04:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
while ( results.hasNext() ) {
|
|
|
|
Object[] row = results.next();
|
|
|
|
Color type = (Color) row[0];
|
|
|
|
Date oldest = (Date) row[1];
|
|
|
|
Integer count = (Integer) row[2];
|
|
|
|
.....
|
|
|
|
}]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
</sect3>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-parameters">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Bind parameters</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Methods on <literal>Query</literal> are provided for binding values to
|
|
|
|
named parameters or JDBC-style <literal>?</literal> parameters. Named parameters
|
|
|
|
are identifiers of the form <literal>:name</literal> in the query string.
|
|
|
|
<emphasis>Contrary to JDBC, Hibernate numbers parameters from zero.</emphasis>
|
|
|
|
The advantages of named parameters are:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
named parameters are insensitive to the order they occur in the
|
|
|
|
query string
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
they may occur multiple times in the same query
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
they are self-documenting
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[//named parameter (preferred)
|
2004-06-03 12:31:32 -04:00
|
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
|
|
|
|
q.setString("name", "Fritz");
|
|
|
|
Iterator cats = q.iterate();]]></programlisting>
|
2005-02-02 11:45:30 -05:00
|
|
|
|
|
|
|
<programlisting><![CDATA[//positional parameter
|
2004-06-03 12:31:32 -04:00
|
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
|
|
|
|
q.setString(0, "Izi");
|
|
|
|
Iterator cats = q.iterate();]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<programlisting><![CDATA[//named parameter list
|
2004-06-03 12:31:32 -04:00
|
|
|
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>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
</sect3>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-pagination">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Pagination</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
If you need to specify bounds upon your result set (the maximum number of rows
|
|
|
|
you want to retrieve and / or the first row you want to retrieve) you should
|
|
|
|
use methods of the <literal>Query</literal> interface:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
|
|
|
|
q.setFirstResult(20);
|
|
|
|
q.setMaxResults(10);
|
|
|
|
List cats = q.list();]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate knows how to translate this limit query into the native
|
|
|
|
SQL of your DBMS.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
</sect3>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-scrolling">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Scrollable iteration</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
If your JDBC driver supports scrollable <literal>ResultSet</literal>s, the
|
|
|
|
<literal>Query</literal> interface may be used to obtain a
|
|
|
|
<literal>ScrollableResults</literal> object, which allows flexible
|
|
|
|
navigation of the query results.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
|
2004-06-03 12:31:32 -04:00
|
|
|
"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) );
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
}
|
|
|
|
cats.close()]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Note that an open database connection (and cursor) is required for this
|
|
|
|
functionality, use <literal>setMaxResult()</literal>/<literal>setFirstResult()</literal>
|
|
|
|
if you need offline pagination functionality.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect3>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect3 id="objectstate-querying-executing-named">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Externalizing named queries</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
You may also define named queries in the mapping document. (Remember to use a
|
|
|
|
<literal>CDATA</literal> section if your query contains characters that could
|
|
|
|
be interpreted as markup.)
|
|
|
|
</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>
|
|
|
|
Parameter binding and executing is done 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>
|
|
|
|
Note that the actual program code is independent of the query language that
|
|
|
|
is used, you may also define native SQL queries in metadata, or migrate
|
|
|
|
existing queries to Hibernate by placing them in mapping files.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect3>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
</sect2>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect2 id="objectstate-filtering" revision="1">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Filtering collections</title>
|
|
|
|
<para>
|
|
|
|
A collection <emphasis>filter</emphasis> is a special type of query that may be applied to
|
|
|
|
a persistent collection or array. The query string may refer to <literal>this</literal>,
|
|
|
|
meaning the current collection element.
|
|
|
|
</para>
|
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
|
|
|
|
pk.getKittens(),
|
|
|
|
"where this.color = ?")
|
|
|
|
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
|
|
|
|
.list()
|
2004-06-03 12:31:32 -04:00
|
|
|
);]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
The returned collection is considered a bag, and its a copy of the given
|
|
|
|
collection. The original collection is not modified (this is contrary to
|
|
|
|
the implication of the name "filter", but consistent with expected behavior).
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Observe that filters do not require a <literal>from</literal> clause (though they may have
|
|
|
|
one if required). Filters are not limited to returning the collection elements themselves.
|
|
|
|
</para>
|
|
|
|
|
2004-08-22 20:56:36 -04:00
|
|
|
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
|
|
|
|
pk.getKittens(),
|
|
|
|
"select this.mate where this.color = eg.Color.BLACK.intValue")
|
2005-02-02 11:45:30 -05:00
|
|
|
.list();]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Even an empty filter query is useful, e.g. to load a subset of elements in a
|
|
|
|
huge collection:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
|
|
|
|
mother.getKittens(), "")
|
|
|
|
.setFirstResult(0).setMaxResults(10)
|
2004-08-22 20:56:36 -04:00
|
|
|
.list();]]></programlisting>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
</sect2>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect2 id="objecstate-querying-criteria" revision="1">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Criteria queries</title>
|
2005-02-02 11:45:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
HQL is extremely powerful but some developers prefer to build queries dynamically,
|
|
|
|
using an object-oriented API, rather than building query strings. Hibernate provides
|
|
|
|
an intuitive <literal>Criteria</literal> query API for these cases:
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
|
2004-08-22 20:56:36 -04:00
|
|
|
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
|
2004-06-03 12:31:32 -04:00
|
|
|
crit.setMaxResults(10);
|
|
|
|
List cats = crit.list();]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
The <literal>Criteria</literal> and the associated <literal>Example</literal>
|
|
|
|
API are discussed in more detail in <xref linkend="querycriteria"/>.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
2005-02-02 11:45:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
</sect2>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect2 id="objectstate-querying-nativesql" revision="2">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Queries in native SQL</title>
|
2005-02-02 11:45:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
You may express a query in SQL, using <literal>createSQLQuery()</literal> and
|
|
|
|
let Hibernate take care of the mapping from result sets to objects. Note
|
|
|
|
that you may at any time call <literal>session.connection()</literal> and
|
|
|
|
use the JDBC <literal>Connection</literal> directly. If you chose to use the
|
|
|
|
Hibernate API, you must enclose SQL aliases in braces:
|
2004-06-03 12:31:32 -04:00
|
|
|
</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>
|
|
|
|
SQL queries may contain named and positional parameters, just like Hibernate queries.
|
2005-02-02 11:45:30 -05:00
|
|
|
More information about native SQL queries in Hibernate can be found in
|
|
|
|
<xref linkend="querysql"/>.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect2>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-modifying" revision="1">
|
|
|
|
<title>Modifying persistent objects</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
<emphasis>Transactional persistent instances</emphasis> (ie. objects loaded, saved, created or
|
|
|
|
queried by the <literal>Session</literal>) may be manipulated by the application
|
|
|
|
and any changes to persistent state will be persisted when the <literal>Session</literal>
|
|
|
|
is <emphasis>flushed</emphasis> (discussed later in this chapter). There is no need
|
|
|
|
to call a particular method (like <literal>update()</literal>, which has a different
|
|
|
|
purpose) to make your modifications persistent. So the most straightforward way to update
|
|
|
|
the state of an object is to <literal>load()</literal> it,
|
|
|
|
and then manipulate it directly, while the <literal>Session</literal> is open:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
|
2004-06-03 12:31:32 -04:00
|
|
|
cat.setName("PK");
|
|
|
|
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Sometimes this programming model is inefficient since it would require both an SQL
|
|
|
|
<literal>SELECT</literal> (to load an object) and an SQL <literal>UPDATE</literal>
|
|
|
|
(to persist its updated state) in the same session. Therefore Hibernate offers an
|
|
|
|
alternate approach, using detached instances.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
<emphasis>Note that Hibernate does not offer its own API for direct execution of
|
|
|
|
<literal>UPDATE</literal> or <literal>DELETE</literal> statements. Hibernate is a
|
|
|
|
<emphasis>state management</emphasis> service, you don't have to think in
|
|
|
|
<emphasis>statements</emphasis> to use it. JDBC is a perfect API for executing
|
|
|
|
SQL statements, you can get a JDBC <literal>Connection</literal> at any time
|
|
|
|
by calling <literal>session.connection()</literal>. Furthermore, the notion
|
|
|
|
of mass operations conflicts with object/relational mapping for online
|
|
|
|
transaction processing-oriented applications. Future versions of Hibernate
|
|
|
|
may however provide special mass operation functions.</emphasis>
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
</sect1>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-detached" revision="2">
|
|
|
|
<title>Modifying detached objects</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Many applications need to retrieve an object in one transaction, send it to the
|
|
|
|
UI layer for manipulation, then save the changes in a new transaction.
|
|
|
|
Applications that use this kind of approach in a high-concurrency environment
|
|
|
|
usually use versioned data to ensure isolation for the "long" unit of work.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate supports this model by providing for reattachment of detached instances
|
|
|
|
using the <literal>Session.update()</literal> or <literal>Session.merge()</literal>
|
|
|
|
methods:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[// in the first session
|
2004-06-03 12:31:32 -04:00
|
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
|
|
|
Cat potentialMate = new Cat();
|
|
|
|
firstSession.save(potentialMate);
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
// in a higher layer of the application
|
2004-06-03 12:31:32 -04:00
|
|
|
cat.setMate(potentialMate);
|
|
|
|
|
|
|
|
// later, in a new session
|
|
|
|
secondSession.update(cat); // update cat
|
|
|
|
secondSession.update(mate); // update mate]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
If the <literal>Cat</literal> with identifier <literal>catId</literal> had already
|
|
|
|
been loaded by <literal>secondSession</literal> when the application tried to
|
|
|
|
reattach it, an exception would have been thrown.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Use <literal>update()</literal> if you are sure that the session does
|
|
|
|
not contain an already persistent instance with the same identifier, and
|
|
|
|
<literal>merge()</literal> if you want to merge your modifications at any time
|
|
|
|
without consideration of the state of the session. In other words, <literal>update()</literal>
|
|
|
|
is usually the first method you would call in a fresh session, ensuring that
|
|
|
|
reattachment of your detached instances is the first operation that is executed.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
The application should individually <literal>update()</literal> detached instances
|
|
|
|
reachable from the given detached instance if and <emphasis>only</emphasis> if it wants
|
|
|
|
their state also updated. This can be automated of course, using <emphasis>transitive
|
2005-02-02 14:38:30 -05:00
|
|
|
persistence</emphasis>, see <xref linkend="objectstate-transitive"/>.
|
2005-02-02 11:45:30 -05:00
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
The <literal>lock()</literal> method also allows an application to reassociate
|
|
|
|
an object with a new session. However, the detached instance has to be unmodified!
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<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>
|
|
|
|
Note that <literal>lock()</literal> can be used with various
|
|
|
|
<literal>LockMode</literal>s, see the API documentation and the
|
|
|
|
chapter on transaction handling for more information. Reattachment is not
|
|
|
|
the only usecase for <literal>lock()</literal>.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
|
|
|
<sect1 id="objectstate-saveorupdate">
|
|
|
|
<title>Automatic state detection</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
TODO: discuss new unsaved check defaults, the rest of this section is probably obsolete
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate users have requested a general purpose method that either saves a
|
|
|
|
transient instance by generating a new identifier or updates/reattaches
|
|
|
|
the detached instances associated with its current identifier.
|
|
|
|
The <literal>saveOrUpdate()</literal> method implements this functionality.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate distinguishes "new" transient instances from detached instances by the
|
|
|
|
value of the identifier (or version, or timestamp) property. The <literal>unsaved-value</literal>
|
|
|
|
attribute of the <literal><id></literal> (or <literal><version></literal>,
|
|
|
|
or <literal><timestamp></literal>) mapping specifies which values should
|
|
|
|
be interpreted as representing a new transient instance.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<id name="id" type="long" column="uid" unsaved-value="null">
|
2004-06-03 12:31:32 -04:00
|
|
|
<generator class="hilo"/>
|
|
|
|
</id>]]></programlisting>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
The allowed values of <literal>unsaved-value</literal> are:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>any</literal> - always save
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>none</literal> - always update
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>null</literal> - save when identifier is null (this is the default)
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
valid identifier value - save when identifier is null or the given value
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>undefined</literal> - the default for <literal>version</literal> or
|
|
|
|
<literal>timestamp</literal>, then identifier check is used
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[// in the first session
|
2004-06-03 12:31:32 -04:00
|
|
|
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>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
The usage and semantics of <literal>saveOrUpdate()</literal> seems to be confusing
|
|
|
|
for new users. Firstly, so long as you are not trying to use instances from one session
|
|
|
|
in another new session, you should not need to use <literal>update()</literal>,
|
|
|
|
<literal>saveOrUpdate()</literal>, or <literal>merge()</literal>. Some whole
|
|
|
|
applications will never use either of these methods.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
Usually <literal>update()</literal> or <literal>saveOrUpdate()</literal> are used in
|
|
|
|
the following scenario:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
the application loads an object in the first session
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
the object is passed up to the UI tier
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
some modifications are made to the object
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
the object is passed back down to the business logic tier
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
the application persists these modifications by calling
|
|
|
|
<literal>update()</literal> in a second session
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
2004-08-17 05:11:10 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
<literal>saveOrUpdate()</literal> does the following:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if the object is already persistent in this session, do nothing
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if the object has no identifier property, <literal>save()</literal> it
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if the object's identifier matches the criteria specified by
|
|
|
|
<literal>unsaved-value</literal>, <literal>save()</literal> it
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if the object is versioned (<literal>version</literal> or
|
|
|
|
<literal>timestamp</literal>), then the version will take precedence
|
|
|
|
to identifier check, unless the versions
|
|
|
|
<literal>unsaved-value="undefined"</literal> (default value)
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if another object associated with the session has the same
|
|
|
|
identifier, throw an exception
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
|
|
|
and <literal>merge()</literal> is very different:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
copies the state of the given object onto the already persistent
|
|
|
|
object with the same identifier
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
if there is no persistent instance currently associated with
|
|
|
|
the session, it will be loaded
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
returns the persistent instance
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
the given instance does not become associated with the session, it
|
|
|
|
stays detached
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect1 id="objectstate-deleting" revision="1">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Deleting persistent objects</title>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
<literal>Session.delete()</literal> will remove an object's state from the database.
|
|
|
|
Of course, your application might still hold a reference to a deleted object.
|
|
|
|
It's best to think of <literal>delete()</literal> as making a persistent instance
|
|
|
|
transient.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
You may also delete many objects at once by passing a Hibernate query string to
|
2005-02-02 11:45:30 -05:00
|
|
|
<literal>delete()</literal>. Note that Hibernate will always load the objects
|
|
|
|
before deletion, to execute lifecycle and any kind of user-defined interception
|
|
|
|
on them (use direct JDBC for mass operations).
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
You may delete objects in any order you like, without risk of foreign key
|
2004-06-03 12:31:32 -04:00
|
|
|
constraint violations. Of course, it is still possible to violate a <literal>NOT
|
|
|
|
NULL</literal> constraint on a foreign key column by deleting objects in
|
2005-02-02 11:45:30 -05:00
|
|
|
the wrong order, e.g. if you delete the parent, but forget to delete the
|
|
|
|
children.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
2005-02-02 11:45:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect1 id="objectstate-flushing">
|
2005-02-02 11:45:30 -05:00
|
|
|
<title>Flushing the Session</title>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
<para>
|
|
|
|
From time to time the <literal>Session</literal> will execute the SQL statements
|
|
|
|
needed to synchronize the JDBC connection's state with the state of objects held in
|
|
|
|
memory. This process, <emphasis>flush</emphasis>, occurs by default at the following
|
|
|
|
points
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
from some invocations of <literal>find()</literal> or <literal>iterate()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
2004-08-09 23:16:42 -04:00
|
|
|
from <literal>org.hibernate.Transaction.commit()</literal>
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
from <literal>Session.flush()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The SQL statements are issued in the following order
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<orderedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all entity insertions, in the same order the corresponding objects
|
|
|
|
were saved using <literal>Session.save()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all entity updates
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all collection deletions
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all collection element deletions, updates and insertions
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all collection insertions
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
all entity deletions, in the same order the corresponding objects
|
|
|
|
were deleted using <literal>Session.delete()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</orderedlist>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
(An exception is that objects using <literal>native</literal> ID generation are
|
|
|
|
inserted when they are saved.)
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Except when you explicity <literal>flush()</literal>, there are absolutely no
|
|
|
|
guarantees about <emphasis>when</emphasis> the <literal>Session</literal> executes
|
|
|
|
the JDBC calls, only the <emphasis>order</emphasis> in which they are executed.
|
|
|
|
However, Hibernate does guarantee that the <literal>Session.find(..)</literal>
|
|
|
|
methods will never return stale data; nor will they return the wrong data.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
It is possible to change the default behavior so that flush occurs less frequently.
|
2005-02-02 11:45:30 -05:00
|
|
|
The <literal>FlushMode</literal> class defines three different modes: only flush
|
|
|
|
at commit time (and only when the Hibernate <literal>Transaction</literal> API
|
|
|
|
is used), flush automatically using the explained routine, or never flush unless
|
|
|
|
<literal>flush()</literal> is called explicitely. The last mode is useful for long running
|
|
|
|
units of work, where a <literal>Session</literal> is kept open and disconnected for
|
|
|
|
a long time (see <xref linkend="transactions-disconnection"/>).
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[sess = sf.openSession();
|
|
|
|
Transaction tx = sess.beginTransaction();
|
2005-02-02 11:45:30 -05:00
|
|
|
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
Cat izi = (Cat) sess.load(Cat.class, id);
|
|
|
|
izi.setName(iznizi);
|
2005-02-02 11:45:30 -05:00
|
|
|
|
|
|
|
// might return stale data
|
2004-06-03 12:31:32 -04:00
|
|
|
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
2005-02-02 11:45:30 -05:00
|
|
|
|
|
|
|
// change to izi is not flushed!
|
2004-06-03 12:31:32 -04:00
|
|
|
...
|
2005-02-02 11:45:30 -05:00
|
|
|
tx.commit(); // flush occurs]]></programlisting>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<para>
|
2005-02-02 17:23:43 -05:00
|
|
|
During flush, an exception might occur (e.g. if a DML operation violates a constraint).
|
|
|
|
Since handling exceptions involves some understanding of Hibernate's transactional behavior,
|
|
|
|
we discuss it in <xref linkend="transactions"/>.
|
2005-02-02 11:45:30 -05:00
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-transitive">
|
|
|
|
<title>Transitive persistence</title>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 17:23:43 -05:00
|
|
|
It is quite cumbersome to save, delete, or reattach individual objects,
|
2005-02-02 11:45:30 -05:00
|
|
|
especially if you deal with a graph of associated objects. A common case is
|
|
|
|
a parent/child relationship. Consider the following example:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 17:23:43 -05:00
|
|
|
If the children in a parent/child relationship would be value typed (e.g. a collection
|
2005-02-02 11:45:30 -05:00
|
|
|
of addresses or strings), their lifecycle would depend on the parent and no
|
|
|
|
further action would be required for convenient "cascading" of state changes.
|
|
|
|
When the parent is saved, the value-typed child objects are saved as
|
|
|
|
well, when the parent is deleted, the children will be deleted, etc. This
|
|
|
|
even works for operations such as the removal of a child from the collection;
|
|
|
|
Hibernate will detect this and, since value-typed objects can't have shared
|
|
|
|
references, delete the child from the database.
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
Now consider the same scenario with parent and child objects being entities,
|
|
|
|
not value-types (e.g. categories and items, or parent and child cats). Entities
|
|
|
|
have their own lifecycle, support shared references (so removing an entity from
|
|
|
|
the collection does not mean it can be deleted), and there is by default no
|
|
|
|
cascading of state from one entity to any other associated entities. Hibernate
|
2005-02-02 17:23:43 -05:00
|
|
|
does not implement <emphasis>persistence by reachability</emphasis> by default.
|
2005-02-02 11:45:30 -05:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
To save or update all objects in a graph of associated entities, you must either
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
TODO: Document the new cascading styles
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>save()</literal>, <literal>saveOrUpdate()</literal> or
|
|
|
|
<literal>update()</literal> each individual object OR
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
map associated objects using or <literal>cascade="save-update"</literal>,
|
|
|
|
<literal>cascade="all"</literal> or <literal>cascade="all-delete-orphan"</literal>.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Likewise, to delete all objects in a graph, either
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal>delete()</literal> each individual object OR
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
map associated objects using <literal>cascade="all"</literal>,
|
|
|
|
<literal>cascade="all-delete-orphan"</literal> or
|
|
|
|
<literal>cascade="delete"</literal>.
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Recommendation:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
If the child object's lifespan is bounded by the lifespan of the of the parent
|
|
|
|
object make it a <emphasis>lifecycle object</emphasis> by specifying
|
|
|
|
<literal>cascade="all"</literal>.
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
Otherwise, <literal>save()</literal> and <literal>delete()</literal> it
|
|
|
|
explicitly from application code. If you really want to save yourself some
|
|
|
|
extra typing, use <literal>cascade="save-update"</literal> and explicit
|
|
|
|
<literal>delete()</literal>.
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Mapping an association (many-to-one, or collection) with <literal>cascade="all"</literal>
|
|
|
|
marks the association as a <emphasis>parent/child</emphasis> style relationship where
|
|
|
|
save/update/deletion of the parent results in save/update/deletion of the child(ren).
|
|
|
|
Futhermore, a mere reference to a child from a persistent parent will result in save / update
|
|
|
|
of the child. The metaphor is incomplete, however. A child which becomes unreferenced by its
|
|
|
|
parent is <emphasis>not</emphasis> automatically deleted, except in the case of a
|
|
|
|
<literal><one-to-many></literal> association mapped with
|
|
|
|
<literal>cascade="all-delete-orphan"</literal>. The precise semantics of cascading operations
|
|
|
|
are as follows:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<itemizedlist spacing="compact">
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
If a parent is saved, all children are passed to <literal>saveOrUpdate()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
If a parent is passed to <literal>update()</literal> or <literal>saveOrUpdate()</literal>,
|
|
|
|
all children are passed to <literal>saveOrUpdate()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
If a transient or detached child becomes referenced by a persistent parent,
|
|
|
|
it is passed to <literal>saveOrUpdate()</literal>
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
If a parent is deleted, all children are passed to <literal>delete()</literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
2004-08-22 20:56:36 -04:00
|
|
|
If a child is dereferenced by a persistent parent, <emphasis>nothing
|
|
|
|
special happens</emphasis> (the application should explicitly delete
|
|
|
|
the child if necessary) unless <literal>cascade="all-delete-orphan"</literal>,
|
|
|
|
in which case the "orphaned" child is deleted.
|
2004-06-03 12:31:32 -04:00
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>
|
2005-02-02 11:45:30 -05:00
|
|
|
Hibernate does, even if you enable all cascading options, not fully implement "persistence
|
|
|
|
by reachability", which would imply (inefficient) persistent garbage collection (i.e.
|
|
|
|
detected references by scanning all tables in the database).
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
TODO: The following doesn't make much sense. Better we don't talk about persistence by
|
|
|
|
reachability anymore, its a bad idea from ODBMS vendors anyway...
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
However, due to popular demand,
|
2004-06-03 12:31:32 -04:00
|
|
|
Hibernate does support the notion of entities becoming persistent when referenced
|
|
|
|
by another persistent object. Associations marked
|
|
|
|
<literal>cascade="save-update"</literal> behave in this way. If you wish to use this
|
|
|
|
approach throughout your application, its easier to specify the
|
|
|
|
<literal>default-cascade</literal> attribute of the
|
|
|
|
<literal><hibernate-mapping></literal> element.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 11:45:30 -05:00
|
|
|
<sect1 id="objectstate-filters">
|
2005-02-02 14:38:30 -05:00
|
|
|
<title>Application filters</title>
|
2004-08-17 12:03:26 -04:00
|
|
|
|
|
|
|
<para>
|
|
|
|
Hibernate3 adds the ability to pre-define filter criteria and attach those filters at both
|
|
|
|
a class and a collection level. A filter criteria is the ability to define a restriction clause
|
|
|
|
very similiar to the existing "where" attribute available on the class and various collection
|
|
|
|
elements. Except these filter conditions can be parameterized. The application can then make
|
|
|
|
the decision at runtime whether given filters should be enabled and what their parameter
|
|
|
|
values should be. Filters can be used like database views, but parameterized inside the
|
|
|
|
application.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
In order to use filters, they must first be defined and then attached to the appropriate
|
|
|
|
mapping elements. To define a filter, use the <literal><filter-def/></literal> element
|
|
|
|
within a <literal><hibernate-mapping/></literal> element:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<filter-def name="myFilter">
|
|
|
|
<filter-param name="myFilterParam" type="string"/>
|
|
|
|
</filter-def>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Then, this filter can be attached to a class:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<class name="myClass" ...>
|
|
|
|
...
|
|
|
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
|
|
|
</class>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
or, to a collection:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<set ...>
|
|
|
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
|
|
|
</set>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
or, even to both (or multiples of each) at the same time.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The methods on <literal>Session</literal> are: <literal>enableFilter(String filterName)</literal>,
|
|
|
|
<literal>getEnabledFilter(String filterName)</literal>, and <literal>disableFilter(String filterName)</literal>.
|
|
|
|
By default, filters are <emphasis>not</emphasis> enabled for a given session; they must be explcitly
|
|
|
|
enabled through use of the <literal>Session.enabledFilter()</literal> method, which returns an
|
|
|
|
instance of the <literal>Filter</literal> interface. Using the simple filter defined above, this
|
|
|
|
would look like:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Note that methods on the org.hibernate.Filter interface do allow the method-chaining common to much of Hibernate.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
A full example, using temporal data with an effective record date pattern:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<filter-def name="effectiveDate">
|
|
|
|
<filter-param name="asOfDate" type="date"/>
|
|
|
|
</filter-def>
|
|
|
|
|
|
|
|
<class name="Employee" ...>
|
|
|
|
...
|
|
|
|
<many-to-one name="department" column="dept_id" class="Department"/>
|
|
|
|
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
|
|
|
|
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
|
|
|
|
...
|
|
|
|
<!--
|
|
|
|
Note that this assumes non-terminal records have an eff_end_dt set to
|
|
|
|
a max db date for simplicity-sake
|
|
|
|
-->
|
|
|
|
<filter name="effectiveDate"
|
|
|
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
|
|
|
</class>
|
|
|
|
|
|
|
|
<class name="Department" ...>
|
|
|
|
...
|
|
|
|
<set name="employees" lazy="true">
|
|
|
|
<key column="dept_id"/>
|
|
|
|
<one-to-many class="Employee"/>
|
|
|
|
<filter name="effectiveDate"
|
|
|
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
|
|
|
</set>
|
|
|
|
</class>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Then, in order to ensure that you always get back currently effective records, simply
|
|
|
|
enable the filter on the session prior to retrieving employee data:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Session session = ...;
|
|
|
|
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
|
|
|
|
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
|
|
|
|
.setLong("targetSalary", new Long(1000000))
|
|
|
|
.list();
|
|
|
|
]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
In the HQL above, even though we only explicitly mentioned a salary constraint on the results,
|
|
|
|
because of the enabled filter the query will return only currently active employees who have
|
|
|
|
a salary greater than a million dollars.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Note: if you plan on using filters with outer joining (either through HQL or load fetching) be
|
|
|
|
careful of the direction of the condition expression. Its safest to set this up for left
|
|
|
|
outer joining; in general, place the parameter first followed by the column name(s) after
|
|
|
|
the operator.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect1 id="objectstate-interceptors" revision="1">
|
2004-06-03 12:31:32 -04:00
|
|
|
<title>Interceptors</title>
|
2005-02-02 14:38:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<para>
|
|
|
|
The <literal>Interceptor</literal> interface provides callbacks from the session to the
|
2005-02-02 14:38:30 -05:00
|
|
|
application allowing the application to inspect and/or manipulate properties of a
|
2004-06-03 12:31:32 -04:00
|
|
|
persistent object before it is saved, updated, deleted or loaded. One
|
|
|
|
possible use for this is to track auditing information. For example, the following
|
|
|
|
<literal>Interceptor</literal> automatically sets the <literal>createTimestamp</literal>
|
|
|
|
when an <literal>Auditable</literal> is created and updates the
|
|
|
|
<literal>lastUpdateTimestamp</literal> property when an <literal>Auditable</literal> is
|
|
|
|
updated.
|
|
|
|
</para>
|
|
|
|
|
2004-08-09 23:16:42 -04:00
|
|
|
<programlisting><![CDATA[package org.hibernate.test;
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
import java.io.Serializable;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
2004-08-09 23:16:42 -04:00
|
|
|
import org.hibernate.Interceptor;
|
|
|
|
import org.hibernate.type.Type;
|
2004-06-03 12:31:32 -04:00
|
|
|
|
|
|
|
public class AuditInterceptor implements Interceptor, Serializable {
|
|
|
|
|
|
|
|
private int updates;
|
|
|
|
private int creates;
|
|
|
|
|
|
|
|
public void onDelete(Object entity,
|
|
|
|
Serializable id,
|
|
|
|
Object[] state,
|
|
|
|
String[] propertyNames,
|
|
|
|
Type[] types) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
public boolean onFlushDirty(Object entity,
|
|
|
|
Serializable id,
|
2004-06-03 12:31:32 -04:00
|
|
|
Object[] currentState,
|
|
|
|
Object[] previousState,
|
|
|
|
String[] propertyNames,
|
|
|
|
Type[] types) {
|
|
|
|
|
|
|
|
if ( entity instanceof Auditable ) {
|
|
|
|
updates++;
|
|
|
|
for ( int i=0; i < propertyNames.length; i++ ) {
|
|
|
|
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
|
|
|
currentState[i] = new Date();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
public boolean onLoad(Object entity,
|
2004-06-03 12:31:32 -04:00
|
|
|
Serializable id,
|
|
|
|
Object[] state,
|
|
|
|
String[] propertyNames,
|
|
|
|
Type[] types) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onSave(Object entity,
|
|
|
|
Serializable id,
|
|
|
|
Object[] state,
|
|
|
|
String[] propertyNames,
|
|
|
|
Type[] types) {
|
2005-02-02 14:38:30 -05:00
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
if ( entity instanceof Auditable ) {
|
|
|
|
creates++;
|
|
|
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
|
|
|
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
|
|
|
state[i] = new Date();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void postFlush(Iterator entities) {
|
|
|
|
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void preFlush(Iterator entities) {
|
|
|
|
updates=0;
|
|
|
|
creates=0;
|
|
|
|
}
|
2005-02-02 14:38:30 -05:00
|
|
|
|
|
|
|
...
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
}]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The interceptor would be specified when a session is created.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
2004-08-09 23:27:34 -04:00
|
|
|
|
|
|
|
<para>
|
|
|
|
You may also set an interceptor on a global level, using the <literal>Configuration</literal>:
|
|
|
|
</para>
|
2004-06-03 12:31:32 -04:00
|
|
|
|
2004-08-09 23:27:34 -04:00
|
|
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
</sect1>
|
2004-08-17 12:03:26 -04:00
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect1 id="objectstate-events">
|
2004-08-17 12:03:26 -04:00
|
|
|
<title>Event system</title>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
If you have to react to particular events in your persistence layer, you may
|
|
|
|
also use the Hibernate3 <emphasis>event</emphasis> architecture. The event
|
|
|
|
system can be used in addition or as a replacement for interceptors.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Essentially all of the methods of the <literal>Session</literal> interface correlate
|
|
|
|
to an event. You have a <literal>LoadEvent</literal>, a <literal>FlushEvent</literal>, etc
|
|
|
|
(consult the XML configuration-file DTD or the <literal>org.hibernate.event</literal>
|
|
|
|
package for the full list of defined event types). When a request is made of one of
|
|
|
|
these methods, the Hibernate <literal>Session</literal> generates an appropriate
|
|
|
|
event and passes it to the configured event listener for that type. Out-of-the-box,
|
|
|
|
these listeners implement the same processing in which those methods always resulted.
|
|
|
|
However, you are free to implement a customization of one of the listener interfaces
|
|
|
|
(i.e., the <literal>LoadEvent</literal> is processed by the registered implemenation
|
|
|
|
of the <literal>LoadEventListener</literal> interface), in which case their
|
|
|
|
implementation would be responsible for processing any <literal>load()</literal> requests
|
|
|
|
made of the <literal>Session</literal>.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
The listeners should be considered effectively singletons; meaning, they are shared between
|
|
|
|
requests, and thus should not save any state as instance variables. The event objects
|
|
|
|
themselves, however, do hold a lot of the context needed for processing as they are unique
|
|
|
|
to each request. Custom event listeners may also make use of the event's context for storage
|
|
|
|
of any needed processing variables. The context is a simple map, but the default listeners
|
|
|
|
don't use the context map at all, so don't worry about over-writing internally required
|
|
|
|
context variables.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
A custom listener should implement the appropriate interface for the event it wants to
|
|
|
|
process and/or extend one of the convenience base classes (or even the default event
|
|
|
|
listeners used by Hibernate out-of-the-box as these are declared non-final for this
|
|
|
|
purpose). Custom listeners can either be registered programatically through the
|
|
|
|
<literal>Configuration</literal> object, or specified in the Hibernate configuration
|
|
|
|
XML (declarative configuration through the properties file is not supported). Here's an
|
|
|
|
example of a custom load event listener:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[public class MyLoadListener extends DefaultLoadEventListener {
|
|
|
|
// this is the single method defined by the LoadEventListener interface
|
|
|
|
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
|
|
|
throws HibernateException {
|
2005-02-02 14:38:30 -05:00
|
|
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
2004-08-17 12:03:26 -04:00
|
|
|
throw MySecurityException("Unauthorized access");
|
|
|
|
}
|
|
|
|
return super.onLoad(event, loadType);
|
|
|
|
}
|
|
|
|
}]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
You also need a configuration entry telling Hibernate to use the listener instead
|
|
|
|
of the default listener:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[<hibernate-configuration>
|
|
|
|
<session-factory>
|
|
|
|
...
|
|
|
|
<listener type="load" class="MyLoadListener"/>
|
|
|
|
</session-factory>
|
|
|
|
</hibernate-configuration>]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Instead, you may register it programatically:
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
|
|
|
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );]]></programlisting>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Listeners registered declaratively cannot share instances. If the same class name is
|
|
|
|
used in multiple <literal><listener/></literal> elements, each reference will
|
|
|
|
result in a seperate instance of that class. If you need the capability to share
|
|
|
|
listener instances between listener types you must use the programatic registration
|
|
|
|
approach.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<para>
|
|
|
|
Why implement an interface and define the specific type during configuration? Well, a
|
|
|
|
listener implementation could implement multiple event listener interfaces. Having the
|
|
|
|
type additionally defined during registration makes it easier to turn custom listeners on
|
|
|
|
or off during configuration.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
</sect1>
|
|
|
|
|
2005-02-02 14:38:30 -05:00
|
|
|
<sect1 id="objectstate-metadata">
|
|
|
|
<title>Using metadata</title>
|
|
|
|
|
2004-06-03 12:31:32 -04:00
|
|
|
<para>
|
|
|
|
Hibernate requires a very rich meta-level model of all entity and value types. From time
|
|
|
|
to time, this model is very useful to the application itself. For example, the application
|
|
|
|
might use Hibernate's metadata to implement a "smart" deep-copy algorithm that understands
|
|
|
|
which objects should be copied (eg. mutable value types) and which should not (eg.
|
|
|
|
immutable value types and, possibly, associated entities).
|
|
|
|
</para>
|
|
|
|
<para>
|
|
|
|
Hibernate exposes metadata via the <literal>ClassMetadata</literal> and
|
|
|
|
<literal>CollectionMetadata</literal> interfaces and the <literal>Type</literal>
|
|
|
|
hierarchy. Instances of the metadata interfaces may be obtained from the
|
|
|
|
<literal>SessionFactory</literal>.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<programlisting><![CDATA[Cat fritz = ......;
|
|
|
|
Long id = (Long) catMeta.getIdentifier(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
|
|
|
|
// TODO: what about components?
|
|
|
|
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>
|
|
|
|
|