1216 lines
52 KiB
XML
1216 lines
52 KiB
XML
<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>
|
|
|
|
<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 if
|
|
the application doesn't hold a reference anymore. Use the Hibernate
|
|
<literal>Session</literal> to make an object persistent (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>
|
|
|
|
<para>
|
|
We'll now 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>
|
|
|
|
<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>.
|
|
You may also use <literal>persist()</literal> instead of <literal>save()</literal>,
|
|
with the semantics defined in the EJB3 early draft.
|
|
</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>
|
|
If the object you make persistent has associated objects (e.g. the
|
|
<literal>kittens</literal> collection in the previous example),
|
|
these 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>
|
|
Usually you don't bother with this detail, as you'll very likely use Hibernate's
|
|
<emphasis>transitive persistence</emphasis> feature to save the associated
|
|
objects automatically. Then, even <literal>NOT NULL</literal>
|
|
constraint violations don't occur - Hibernate will take care of everything.
|
|
Transitive persistence is discussed later in this chapter.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-loading">
|
|
<title>Loading an object</title>
|
|
|
|
<para>
|
|
The <literal>load()</literal> methods of <literal>Session</literal> gives 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, in persistent state.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
|
|
|
|
<programlisting><![CDATA[// you need to wrap primitive identifiers
|
|
long pkId = 1234;
|
|
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
|
|
|
|
<para>
|
|
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>,
|
|
using a <literal>LockMode</literal>. See the API documentation for more information.
|
|
</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>, unless you decide
|
|
to specify <literal>lock</literal> or <literal>all</literal> as a
|
|
cascade style for the association.
|
|
</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>
|
|
|
|
<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>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-querying" revision="1">
|
|
<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 (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.
|
|
</para>
|
|
|
|
<sect2 id="objectstate-querying-executing">
|
|
<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>
|
|
|
|
<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>
|
|
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 instances 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>
|
|
|
|
<sect3 id="objectstate-querying-executing-iterate">
|
|
<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();
|
|
while ( iter.hasNext() ) {
|
|
Qux qux = (Qux) iter.next(); // fetch the object
|
|
// something we couldnt express in the query
|
|
if ( qux.calculateComplicatedAlgorithm() ) {
|
|
// delete the current instance
|
|
iter.remove();
|
|
// dont need to process the rest
|
|
break;
|
|
}
|
|
}]]></programlisting>
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-tuples">
|
|
<title>Queries that return tuples</title>
|
|
|
|
<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();
|
|
Cat kitten = tuple[0];
|
|
Cat mother = tuple[1];
|
|
....
|
|
}]]></programlisting>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-scalar">
|
|
<title>Scalar results</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 (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();
|
|
|
|
while ( results.hasNext() ) {
|
|
Object[] row = results.next();
|
|
Color type = (Color) row[0];
|
|
Date oldest = (Date) row[1];
|
|
Integer count = (Integer) row[2];
|
|
.....
|
|
}]]></programlisting>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-parameters">
|
|
<title>Bind parameters</title>
|
|
|
|
<para>
|
|
Methods on <literal>Query</literal> are provided for binding values to
|
|
named parameters or JDBC-style <literal>?</literal> parameters.
|
|
<emphasis>Contrary to JDBC, Hibernate numbers parameters from zero.</emphasis>
|
|
Named parameters are identifiers of the form <literal>:name</literal> in
|
|
the query string. 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>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-pagination">
|
|
<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>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-scrolling">
|
|
<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 " +
|
|
"order by cat.name");
|
|
ScrollableResults cats = q.scroll();
|
|
if ( cats.first() ) {
|
|
|
|
// find the first name on each page of an alphabetical list of cats by name
|
|
firstNamesOfPages = new ArrayList();
|
|
do {
|
|
String name = cats.getString(0);
|
|
firstNamesOfPages.add(name);
|
|
}
|
|
while ( cats.scroll(PAGE_SIZE) );
|
|
|
|
// Now get the first page of cats
|
|
pageOfCats = new ArrayList();
|
|
cats.beforeFirst();
|
|
int i=0;
|
|
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
|
|
|
|
}
|
|
cats.close()]]></programlisting>
|
|
|
|
<para>
|
|
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>
|
|
|
|
<sect3 id="objectstate-querying-executing-named">
|
|
<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>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objectstate-filtering" revision="1">
|
|
<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, and it's 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).
|
|
</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>
|
|
|
|
<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)
|
|
.list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objecstate-querying-criteria" revision="1">
|
|
<title>Criteria queries</title>
|
|
|
|
<para>
|
|
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:
|
|
</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>
|
|
The <literal>Criteria</literal> and the associated <literal>Example</literal>
|
|
API are discussed in more detail in <xref linkend="querycriteria"/>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objectstate-querying-nativesql" revision="2">
|
|
<title>Queries in native SQL</title>
|
|
|
|
<para>
|
|
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:
|
|
</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.
|
|
More information about native SQL queries in Hibernate can be found in
|
|
<xref linkend="querysql"/>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-modifying" revision="1">
|
|
<title>Modifying persistent objects</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). 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>
|
|
|
|
<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, using detached instances.
|
|
</para>
|
|
|
|
<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. See <xref linkend="batch"/>
|
|
for some possible batch operation tricks.</emphasis>
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-detached" revision="2">
|
|
<title>Modifying 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 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
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
|
Cat potentialMate = new Cat();
|
|
firstSession.save(potentialMate);
|
|
|
|
// in a higher layer of the application
|
|
cat.setMate(potentialMate);
|
|
|
|
// later, in a new session
|
|
secondSession.update(cat); // update cat
|
|
secondSession.update(mate); // update mate]]></programlisting>
|
|
|
|
<para>
|
|
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>
|
|
|
|
<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>
|
|
|
|
<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
|
|
persistence</emphasis>, see <xref linkend="objectstate-transitive"/>.
|
|
</para>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<para>
|
|
Other models for long units of work are discussed in <xref linkend="transactions-optimistic"/>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-saveorupdate">
|
|
<title>Automatic state detection</title>
|
|
|
|
<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>
|
|
|
|
<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>,
|
|
<literal>saveOrUpdate()</literal>, or <literal>merge()</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 another object associated with the session has the same identifier,
|
|
throw an exception
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
if the object has no identifier property, <literal>save()</literal> it
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
if the object's identifier has the value assigned to a newly instantiated
|
|
object, <literal>save()</literal> it
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
if the object is versioned (by a <literal><version></literal> or
|
|
<literal><timestamp></literal>), and the version property value
|
|
is the same value assigned to a newly instantiated object,
|
|
<literal>save()</literal> it
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
otherwise <literal>update()</literal> the object
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
and <literal>merge()</literal> is very different:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
if there is a persistent instance with the same identifier currently
|
|
associated with the session, copy the state of the given object onto
|
|
the persistent instance
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
if there is no persistent instance currently associated with the session,
|
|
try to load it from the database, or create a new persistent instance
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
the persistent instance is returned
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
the given instance does not become associated with the session, it
|
|
remains detached
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-deleting" revision="1">
|
|
<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 a deleted object.
|
|
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 delete objects in any order you like, without risk of foreign key
|
|
constraint violations. It is still possible to violate a <literal>NOT
|
|
NULL</literal> constraint on a foreign key column by deleting objects in
|
|
the wrong order, e.g. if you delete the parent, but forget to delete the
|
|
children.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-replicating" revision="1">
|
|
<title>Replicating object between two different datastores</title>
|
|
|
|
<para>
|
|
It is occasionally useful to be able to take a graph of persistent instances
|
|
and make them persistent in a different datastore, without regenerating identifier
|
|
values.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//retrieve a cat from one database
|
|
Session session1 = factory1.openSession();
|
|
Transaction tx1 = session1.beginTransaction();
|
|
Cat cat = session1.get(Cat.class, catId);
|
|
tx1.commit();
|
|
session1.close();
|
|
|
|
//reconcile with a second database
|
|
Session session2 = factory2.openSession();
|
|
Transaction tx2 = session2.beginTransaction();
|
|
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
|
|
tx2.commit();
|
|
session2.close();]]></programlisting>
|
|
|
|
<para>
|
|
The <literal>ReplicationMode</literal> determines how <literal>replicate()</literal>
|
|
will deal with conflicts with existing rows in the database.
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.IGNORE</literal> - ignore the object when there is
|
|
an existing database row with the same identifier
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.OVERWRITE</literal> - overwrite any existing database
|
|
row with the same identifier
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.EXCEPTION</literal> - throw an exception if there is
|
|
an existing database row with the same identifier
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.LATEST_VERSION</literal> - overwrite the row if its
|
|
version number is earlier than the version number of the object, or ignore
|
|
the object otherwise
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Usecases for this feature include reconciling data entered into different database
|
|
instances, upgrading system configuration information during product upgrades,
|
|
rolling back changes made during non-ACID transactions and more.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-flushing">
|
|
<title>Flushing the Session</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>
|
|
before some query executions
|
|
</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>Query.list(..)</literal>
|
|
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: 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 explicitly. 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-optimistic-longsession"/>).
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess = sf.openSession();
|
|
Transaction tx = sess.beginTransaction();
|
|
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
|
|
|
|
Cat izi = (Cat) sess.load(Cat.class, id);
|
|
izi.setName(iznizi);
|
|
|
|
// might return stale data
|
|
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
|
|
|
// change to izi is not flushed!
|
|
...
|
|
tx.commit(); // flush occurs]]></programlisting>
|
|
|
|
<para>
|
|
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"/>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-transitive">
|
|
<title>Transitive persistence</title>
|
|
|
|
<para>
|
|
It is quite cumbersome to save, delete, or reattach individual objects,
|
|
especially if you deal with a graph of associated objects. A common case is
|
|
a parent/child relationship. Consider the following example:
|
|
</para>
|
|
|
|
<para>
|
|
If the children in a parent/child relationship would be value typed (e.g. a collection
|
|
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>
|
|
|
|
<para>
|
|
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
|
|
does not implement <emphasis>persistence by reachability</emphasis> by default.
|
|
</para>
|
|
|
|
<para>
|
|
For each basic operation of the Hibernate session - including <literal>persist(), merge(),
|
|
saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()</literal> - there is a
|
|
corresponding cascade style. Respectively, the cascade styles are named <literal>create,
|
|
merge, save-update, delete, lock, refresh, evict, replicate</literal>. If you want an
|
|
operation to be cascaded along an association, you must indicate that in the mapping
|
|
document. For example:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<one-to-one name="person" cascade="create"/>]]></programlisting>
|
|
|
|
<para>
|
|
Cascade styles my be combined:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<one-to-one name="person" cascade="create,delete,lock"/>]]></programlisting>
|
|
|
|
<para>
|
|
You may even use <literal>cascade="all"</literal> to specify that <emphasis>all</emphasis>
|
|
operations should be cascaded along the association. The default <literal>cascade="none"</literal>
|
|
specifies that no operations are to be cascaded.
|
|
</para>
|
|
|
|
<para>
|
|
A special cascade style, <literal>delete-orphan</literal>, applies only to one-to-many
|
|
associations, and indicates that the <literal>delete()</literal> operation should
|
|
be applied to any child object that is removed from the association.
|
|
</para>
|
|
|
|
|
|
<para>
|
|
Recommendations:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
It doesn't usually make sense to enable cascade on a <literal><many-to-one></literal>
|
|
or <literal><many-to-many></literal> association. Cascade is often useful for
|
|
<literal><one-to-one></literal> and <literal><one-to-many></literal>
|
|
associations.
|
|
</para>
|
|
</listitem>
|
|
<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,delete-orphan"</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Otherwise, you might not need cascade at all. But if you think that you will often be
|
|
working with the parent and children together in the same transaction, and you want to save
|
|
yourself some typing, consider using <literal>cascade="create,merge,save-update"</literal>.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Mapping an association (either a single valued association, or a collection) with
|
|
<literal>cascade="all"</literal> marks the association as a
|
|
<emphasis>parent/child</emphasis> style relationship where save/update/delete of the
|
|
parent results in save/update/delete of the child or children.
|
|
</para>
|
|
<para>
|
|
Futhermore, a mere reference to a child from a persistent parent will result in
|
|
save/update of the child. This 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="delete-orphan"</literal>. The precise semantics of cascading
|
|
operations for a parent/child relationship are as follows:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
If a parent is passed to <literal>persist()</literal>, all children are passed to
|
|
<literal>persist()</literal>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If a parent is passed to <literal>merge()</literal>, all children are passed to
|
|
<literal>merge()</literal>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If a parent is passed to <literal>save()</literal>, <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="delete-orphan"</literal>,
|
|
in which case the "orphaned" child is deleted.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-metadata">
|
|
<title>Using metadata</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 = ......;
|
|
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
|
|
|
|
Object[] propertyValues = catMeta.getPropertyValues(fritz);
|
|
String[] propertyNames = catMeta.getPropertyNames();
|
|
Type[] propertyTypes = catMeta.getPropertyTypes();
|
|
|
|
// get a Map of all properties which are not collections or associations
|
|
Map namedValues = new HashMap();
|
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
|
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
|
|
namedValues.put( propertyNames[i], propertyValues[i] );
|
|
}
|
|
}]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|