1477 lines
58 KiB
XML
1477 lines
58 KiB
XML
<chapter id="manipulatingdata">
|
|
|
|
<title>Working with Persistent Data</title>
|
|
|
|
<sect1 id="manipulatingdata-creating">
|
|
<title>Creating a persistent object</title>
|
|
|
|
<para>
|
|
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:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
|
|
fritz.setColor(Color.GINGER);
|
|
fritz.setSex('M');
|
|
fritz.setName("Fritz");
|
|
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
|
|
|
|
<para>
|
|
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>.
|
|
</para>
|
|
|
|
<para>
|
|
Alternatively, you may assign the identifier using an overloaded version
|
|
of <literal>save()</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[DomesticCat pk = new DomesticCat();
|
|
pk.setColor(Color.TABBY);
|
|
pk.setSex('F');
|
|
pk.setName("PK");
|
|
pk.setKittens( new HashSet() );
|
|
pk.addKitten(fritz);
|
|
sess.save( pk, new Long(1234) );]]></programlisting>
|
|
|
|
<para>
|
|
Associated objects may be made persistent in any order you like unless you
|
|
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>
|
|
|
|
<para>
|
|
If you enable cascade save on your associations, even <literal>NOT NULL</literal>
|
|
constraint violations are impossible - Hibernate will take care of everything.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-loading">
|
|
<title>Loading an object</title>
|
|
|
|
<para>
|
|
The <literal>load()</literal> methods of <literal>Session</literal> give you
|
|
a way to retrieve a persistent instance if you already know its identifier.
|
|
<literal>load()</literal> takes a class object and will load the state into
|
|
a newly instantiated instance of that class.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
|
|
|
|
<programlisting><![CDATA[// you need to wrap primitive identifiers
|
|
long pkId = 1234;
|
|
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
|
|
|
|
<para>
|
|
Alternatively, you can load state into a given instance:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
|
// load pk's state into cat
|
|
sess.load( cat, new Long(pkId) );
|
|
Set kittens = cat.getKittens();]]></programlisting>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
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.
|
|
</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>
|
|
You may even load an object using an SQL <literal>SELECT ... FOR UPDATE</literal>.
|
|
See the next section for a discussion of Hibernate <literal>LockMode</literal>s.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
|
|
|
|
<para>
|
|
Note that any associated instances or contained collections are
|
|
<emphasis>not</emphasis> selected <literal>FOR UPDATE</literal>.
|
|
</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>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-querying">
|
|
<title>Querying</title>
|
|
|
|
<para>
|
|
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
|
|
oriented query language.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[List cats = session.createQuery(
|
|
"from Cat as cat where cat.birthdate < ?")
|
|
.setDate(0, date)
|
|
.list();
|
|
|
|
List mothers = session.createQuery(
|
|
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
|
|
.setString(0, name)
|
|
.list();
|
|
|
|
List kittens = session.createQuery(
|
|
"from Cat as cat where cat.mother = ?")
|
|
.setEntity(0, pk)
|
|
.list();
|
|
|
|
Cat mother = (Cat) session.createQuery(
|
|
"select cat.mother from Cat as cat where cat = ?")
|
|
.setEntity(0, izi)
|
|
.uniqueResult();]]></programlisting>
|
|
|
|
<para>
|
|
The call to <literal>createQuery()</literal> returns an instance of
|
|
<literal>org.hibernate.Query</literal> which may be used to bind arguments
|
|
to the <literal>?</literal> parameter placeholders. (which map to IN
|
|
parameters of a JDBC <literal>PreparedStatement</literal>). Just as
|
|
in JDBC, you should always use this binding mechanism in preference
|
|
to string manipulation.
|
|
</para>
|
|
|
|
<para>
|
|
A query is usually executed by invoking <literal>list()</literal>.
|
|
</para>
|
|
|
|
<!--
|
|
The <literal>Hibernate</literal> class defines a number of static methods
|
|
and constants, providing access to most of the built-in types, as instances
|
|
of <literal>org.hibernate.type.Type</literal>.
|
|
-->
|
|
|
|
<!--
|
|
If you expect your query to return a very large number of objects, but you
|
|
don't expect to use them all, you might get better performance from the
|
|
<literal>iterate()</literal> methods, which return a
|
|
<literal>java.util.Iterator</literal>. The iterator will load objects on
|
|
demand, using the identifiers returned by an initial SQL query (n+1 selects
|
|
total).
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// fetch ids
|
|
Iterator iter = sess.iterate("from eg.Qux q order by q.likeliness");
|
|
while ( iter.hasNext() ) {
|
|
Qux qux = (Qux) iter.next(); // fetch the object
|
|
// something we couldnt express in the query
|
|
if ( qux.calculateComplicatedAlgorithm() ) {
|
|
// delete the current instance
|
|
iter.remove();
|
|
// dont need to process the rest
|
|
break;
|
|
}
|
|
}]]></programlisting-->
|
|
|
|
<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>find()</literal>
|
|
and might require many database hits for a simple query.
|
|
</para>
|
|
|
|
<!-- Heres an example of a query that should be
|
|
called using <literal>iterate()</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
Iterator iter = sess.iterate(
|
|
"select customer, product " +
|
|
"from Customer customer, " +
|
|
"Product product " +
|
|
"join customer.purchases purchase " +
|
|
"where product = purchase.product"
|
|
);]]></programlisting>
|
|
|
|
<para>
|
|
Calling the previous query using <literal>find()</literal> would return a very
|
|
large JDBC <literal>ResultSet</literal> containing the same data many times.
|
|
</para-->
|
|
|
|
<para>
|
|
Hibernate queries sometimes return tuples of objects, in which case each tuple
|
|
is returned as an array:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
|
|
"select kitten, mother from Cat kitten join kitten.mother mother")
|
|
.list()
|
|
.iterator();
|
|
|
|
while ( kittensAndMothers.hasNext() ) {
|
|
Object[] tuple = (Object[]) kittensAndMothers.next();
|
|
Cate kittem = tuple[0]; Cat mother = tuple[1];
|
|
....
|
|
}]]></programlisting>
|
|
|
|
<sect2 id="manipulatingdata-scalarqueries">
|
|
<title>Scalar queries</title>
|
|
|
|
<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.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Iterator results = sess.createQuery(
|
|
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
|
|
"group by cat.color")
|
|
.list()
|
|
.iterator();
|
|
|
|
while ( results.hasNext() ) {
|
|
Object[] row = results.next();
|
|
Color type = (Color) row[0];
|
|
Date oldest = (Date) row[1];
|
|
Integer count = (Integer) row[2];
|
|
.....
|
|
}]]></programlisting>
|
|
|
|
<programlisting><![CDATA[List results = sess.createQuery(
|
|
"select cat.type, cat.birthdate, cat.name from DomesticCat cat")
|
|
.list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-queryinterface">
|
|
<title>The Query interface</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>
|
|
You may even define a named query 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>
|
|
|
|
<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>
|
|
The query interface supports the use of named parameters. Named parameters
|
|
are identifiers of the form <literal>:name</literal> in the query string.
|
|
There are methods on <literal>Query</literal> for binding values to named
|
|
parameters or JDBC-style <literal>?</literal> parameters. <emphasis>
|
|
Contrary to JDBC, Hibernate numbers parameters from zero.</emphasis> The
|
|
advantages of named parameters are:
|
|
</para>
|
|
|
|
<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)
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
|
|
q.setString("name", "Fritz");
|
|
Iterator cats = q.iterate();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[//positional parameter
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
|
|
q.setString(0, "Izi");
|
|
Iterator cats = q.iterate();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[//named parameter list
|
|
List names = new ArrayList();
|
|
names.add("Izi");
|
|
names.add("Fritz");
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
|
|
q.setParameterList("namesList", names);
|
|
List cats = q.list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-scrolling" revision="1">
|
|
<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> which allows more flexible
|
|
navigation of the query results.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
|
|
"order by cat.name");
|
|
ScrollableResults cats = q.scroll();
|
|
if ( cats.first() ) {
|
|
|
|
// find the first name on each page of an alphabetical list of cats by name
|
|
firstNamesOfPages = new ArrayList();
|
|
do {
|
|
String name = cats.getString(0);
|
|
firstNamesOfPages.add(name);
|
|
}
|
|
while ( cats.scroll(PAGE_SIZE) );
|
|
|
|
// Now get the first page of cats
|
|
pageOfCats = new ArrayList();
|
|
cats.beforeFirst();
|
|
int i=0;
|
|
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
|
|
|
|
}]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-filtering">
|
|
<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>
|
|
|
|
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
|
|
pk.getKittens(),
|
|
"where this.color = ?")
|
|
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
|
|
.list()
|
|
);]]></programlisting>
|
|
|
|
<para>
|
|
The returned collection is considered a bag.
|
|
</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>
|
|
|
|
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
|
|
pk.getKittens(),
|
|
"select this.mate where this.color = eg.Color.BLACK.intValue")
|
|
.list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-criteria">
|
|
<title>Criteria queries</title>
|
|
<para>
|
|
HQL is extremely powerful but some people prefer to build queries dynamically, using an
|
|
object oriented API, rather than embedding strings in their Java code. For these people,
|
|
Hibernate provides an intuitive <literal>Criteria</literal> query API.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
|
|
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
|
|
crit.setMaxResults(10);
|
|
List cats = crit.list();]]></programlisting>
|
|
|
|
<para>
|
|
If you are uncomfortable with SQL-like syntax, this is perhaps the easiest way to get started
|
|
with Hibernate. This API is also more extensible than HQL. Applications might provide their
|
|
own implementations of the <literal>Criterion</literal> interface.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-nativesql" revision="1">
|
|
<title>Queries in native SQL</title>
|
|
<para>
|
|
You may express a query in SQL, using <literal>createSQLQuery()</literal>. You must enclose
|
|
SQL aliases in braces.
|
|
</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.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-updating">
|
|
<title>Updating objects</title>
|
|
|
|
|
|
<sect2 id="manipulatingdata-updating-insession">
|
|
<title>Updating in the same Session</title>
|
|
|
|
<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). 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>
|
|
|
|
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
|
|
cat.setName("PK");
|
|
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-updating-detached" revision="1">
|
|
<title>Updating detached objects</title>
|
|
|
|
<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 transaction isolation.) This approach
|
|
requires a slightly different programming model to the one described in the
|
|
last section. Hibernate supports this model by providing for reattachment of
|
|
detached instances using the the method <literal>Session.update()</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// in the first session
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
|
Cat potentialMate = new Cat();
|
|
firstSession.save(potentialMate);
|
|
|
|
// in a higher tier of the application
|
|
cat.setMate(potentialMate);
|
|
|
|
// later, in a new session
|
|
secondSession.update(cat); // update cat
|
|
secondSession.update(mate); // update mate]]></programlisting>
|
|
|
|
<para>
|
|
If the <literal>Cat</literal> with identifier <literal>catId</literal> had already
|
|
been loaded by <literal>secondSession</literal> when the application tried to
|
|
update it, an exception would have been thrown.
|
|
</para>
|
|
|
|
<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. (Except for lifecycle objects, discussed later.)
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate users have requested a general purpose method that either saves a
|
|
transient instance by generating a new identifier or update the persistent
|
|
state associated with its current identifier. The <literal>saveOrUpdate()</literal>
|
|
method now 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">
|
|
<generator class="hilo"/>
|
|
</id>]]></programlisting>
|
|
|
|
<para>
|
|
The allowed values of <literal>unsaved-value</literal> are:
|
|
</para>
|
|
|
|
<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
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catID);
|
|
|
|
// in a higher tier of the application
|
|
Cat mate = new Cat();
|
|
cat.setMate(mate);
|
|
|
|
// later, in a new session
|
|
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
|
|
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)]]></programlisting>
|
|
|
|
<para>
|
|
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> or
|
|
<literal>saveOrUpdate()</literal>. Some whole applications will never use either of
|
|
these methods.
|
|
</para>
|
|
|
|
<para>
|
|
Usually <literal>update()</literal> or <literal>saveOrUpdate()</literal> are used in
|
|
the following scenario:
|
|
</para>
|
|
|
|
<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>
|
|
|
|
<para>
|
|
<literal>saveOrUpdate()</literal> does the following:
|
|
</para>
|
|
|
|
<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>
|
|
|
|
<para>
|
|
The last case can be avoided by using <literal>saveOrUpdateCopy(Object o)</literal>. This method
|
|
copies the state of the given object onto the persistent object with the same identifier. If
|
|
there is no persistent instance currently associated with the session, it will be loaded.
|
|
The method return the persistent instance. If the given instance is unsaved or does not
|
|
exist in the database, Hibernate will save it and return it as a newly persistent instance.
|
|
Otherwise, the given instance does not become associated with the session. In most
|
|
applications with detached objects, you need both methods, <literal>saveOrUpdate()</literal>
|
|
and <literal>saveOrUpdateCopy()</literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-update-lock">
|
|
<title>Reattaching detached objects</title>
|
|
|
|
<para>
|
|
The <literal>lock()</literal> method allows the application to reassociate
|
|
an unmodified object with a new session.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//just reassociate:
|
|
sess.lock(fritz, LockMode.NONE);
|
|
//do a version check, then reassociate:
|
|
sess.lock(izi, LockMode.READ);
|
|
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
|
|
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-deleting">
|
|
<title>Deleting persistent objects</title>
|
|
|
|
<para>
|
|
<literal>Session.delete()</literal> will remove an object's state from the database.
|
|
Of course, your application might still hold a reference to it. So it's best to think
|
|
of <literal>delete()</literal> as making a persistent instance transient.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
|
|
|
|
<para>
|
|
You may also delete many objects at once by passing a Hibernate query string to
|
|
<literal>delete()</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
You may now delete objects in any order you like, without risk of foreign key
|
|
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
|
|
the wrong order.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-flushing">
|
|
<title>Flush</title>
|
|
|
|
<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>
|
|
from <literal>org.hibernate.Transaction.commit()</literal>
|
|
</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.
|
|
The <literal>FlushMode</literal> class defines three different modes. This is most
|
|
useful in the case of "readonly" transactions, where it might be used to achieve a
|
|
(very) slight performance increase.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess = sf.openSession();
|
|
Transaction tx = sess.beginTransaction();
|
|
sess.setFlushMode(FlushMode.COMMIT); //allow queries to return stale state
|
|
Cat izi = (Cat) sess.load(Cat.class, id);
|
|
izi.setName(iznizi);
|
|
// execute some queries....
|
|
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
|
//change to izi is not flushed!
|
|
...
|
|
tx.commit(); //flush occurs]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-endingsession">
|
|
<title>Ending a Session</title>
|
|
|
|
<para>
|
|
Ending a session involves four distinct phases:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
flush the session
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
commit the transaction
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
close the session
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
handle exceptions
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<sect2 id="manipulatingdata-endingsession-flushing">
|
|
<title>Flushing the Session</title>
|
|
<para>
|
|
If you happen to be using the <literal>Transaction</literal> API, you don't
|
|
need to worry about this step. It will be performed implicitly when the
|
|
transaction is committed. Otherwise you should call
|
|
<literal>Session.flush()</literal> to ensure that all changes are synchronized
|
|
with the database.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-endingsession-commit">
|
|
<title>Committing the database transaction</title>
|
|
|
|
<para>
|
|
If you are using the Hibernate <literal>Transaction</literal> API, this looks like:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[tx.commit(); // flush the Session and commit the transaction]]></programlisting>
|
|
|
|
<para>
|
|
If you are managing JDBC transactions yourself you should manually
|
|
<literal>commit()</literal> the JDBC connection.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess.flush();
|
|
sess.connection().commit(); // not necessary for JTA datasource]]></programlisting>
|
|
|
|
<para>
|
|
If you decide <emphasis>not</emphasis> to commit your changes:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[tx.rollback(); // rollback the transaction]]></programlisting>
|
|
|
|
<para>
|
|
or:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// not necessary for JTA datasource, important otherwise
|
|
sess.connection().rollback();]]></programlisting>
|
|
|
|
<para>
|
|
If you rollback the transaction you should immediately close and discard the current
|
|
session to ensure that Hibernate's internal state is consistent.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-endingsession-close">
|
|
<title>Closing the Session</title>
|
|
|
|
<para>
|
|
A call to <literal>Session.close()</literal> marks the end of a session. The main implication
|
|
of <literal>close()</literal> is that the JDBC connection will be relinquished by the session.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[tx.commit();
|
|
sess.close();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[sess.flush();
|
|
sess.connection().commit(); // not necessary for JTA datasource
|
|
sess.close();]]></programlisting>
|
|
|
|
<para>
|
|
If you provided your own connection, <literal>close()</literal> returns a reference
|
|
to it, so you can manually close it or return it to the pool. Otherwise <literal>close()
|
|
</literal> returns it to the pool.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="manipulatingdata-endingsession-exceptions">
|
|
<title>Exception handling</title>
|
|
|
|
<para>
|
|
If the <literal>Session</literal> throws an exception (including
|
|
any <literal>SQLException</literal>), you should immediately
|
|
rollback the transaction, call <literal>Session.close()</literal>
|
|
and discard the <literal>Session</literal> instance. Certain
|
|
methods of <literal>Session</literal> will <emphasis>not</emphasis>
|
|
leave the session in a consistent state.
|
|
</para>
|
|
|
|
|
|
<para>
|
|
The following exception handling idiom is recommended:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session sess = factory.openSession();
|
|
Transaction tx = null;
|
|
try {
|
|
tx = sess.beginTransaction();
|
|
// do some work
|
|
...
|
|
tx.commit();
|
|
}
|
|
catch (Exception e) {
|
|
if (tx!=null) tx.rollback();
|
|
throw e;
|
|
}
|
|
finally {
|
|
sess.close();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Or, when manually managing JDBC transactions:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session sess = factory.openSession();
|
|
try {
|
|
// do some work
|
|
...
|
|
sess.flush();
|
|
sess.connection().commit();
|
|
}
|
|
catch (Exception e) {
|
|
sess.connection().rollback();
|
|
throw e;
|
|
}
|
|
finally {
|
|
sess.close();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Or, when using a datasource enlisted with JTA:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[UserTransaction ut = .... ;
|
|
Session sess = factory.openSession();
|
|
try {
|
|
// do some work
|
|
...
|
|
sess.flush();
|
|
}
|
|
catch (Exception e) {
|
|
ut.setRollbackOnly();
|
|
throw e;
|
|
}
|
|
finally {
|
|
sess.close();
|
|
}]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-graphs">
|
|
<title>Lifecyles and object graphs</title>
|
|
|
|
<para>
|
|
To save or update all objects in a graph of associated objects, you must either
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>save()</literal>, <literal>saveOrUpdate()</literal> or
|
|
<literal>update()</literal> each individual object OR
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
map associated objects using or <literal>cascade="save-update"</literal>,
|
|
<literal>cascade="all"</literal> or <literal>cascade="all-delete-orphan"</literal>.
|
|
</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>
|
|
If a transient or detached child becomes referenced by a persistent parent,
|
|
it is passed to <literal>saveOrUpdate()</literal>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If a parent is deleted, all children are passed to <literal>delete()</literal>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Hibernate does not fully implement "persistence by reachability", which would imply
|
|
(inefficient) persistent garbage collection. However, due to popular demand,
|
|
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>
|
|
|
|
<sect1 id="manipulatingdata-filters">
|
|
<title>Parameterized application views with filters</title>
|
|
|
|
<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>
|
|
|
|
<sect1 id="manipulatingdata-interceptors" revision="1">
|
|
<title>Interceptors</title>
|
|
<para>
|
|
The <literal>Interceptor</literal> interface provides callbacks from the session to the
|
|
application allowing the application to inspect and / or manipulate properties of a
|
|
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>
|
|
|
|
<programlisting><![CDATA[package org.hibernate.test;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.Date;
|
|
import java.util.Iterator;
|
|
|
|
import org.hibernate.Interceptor;
|
|
import org.hibernate.type.Type;
|
|
|
|
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
|
|
}
|
|
|
|
public boolean onFlushDirty(Object entity,
|
|
Serializable id,
|
|
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;
|
|
}
|
|
|
|
public boolean onLoad(Object entity,
|
|
Serializable id,
|
|
Object[] state,
|
|
String[] propertyNames,
|
|
Type[] types) {
|
|
return false;
|
|
}
|
|
|
|
public boolean onSave(Object entity,
|
|
Serializable id,
|
|
Object[] state,
|
|
String[] propertyNames,
|
|
Type[] types) {
|
|
|
|
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;
|
|
}
|
|
|
|
......
|
|
......
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
The interceptor would be specified when a session is created.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
|
|
|
<para>
|
|
You may also set an interceptor on a global level, using the <literal>Configuration</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="manipulatingdata-events">
|
|
<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 {
|
|
if ( !MySecurity.isAuthorized( event.getEntityName(), event.getEntityId() ) ) {
|
|
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>
|
|
|
|
<sect1 id="manipulatingdata-metadata">
|
|
<title>Metadata API</title>
|
|
<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>
|
|
|