hibernate-orm/reference/en/modules/transactions.xml

473 lines
20 KiB
XML

<chapter id="transactions">
<title>Transactions And Concurrency</title>
<para>
Hibernate is not itself a database. It is a lightweight object-relational
mapping tool. Transaction management is delegated to the underlying database
connection. If the connection is enlisted with JTA, operations performed by the
<literal>Session</literal> are atomically part of the wider JTA
transaction. Hibernate can be seen as a thin adapter to JDBC, adding object-
oriented semantics.
</para>
<sect1 id="transactions-basics">
<title>Configurations, Sessions and Factories</title>
<para>
A <literal>SessionFactory</literal> is an expensive-to-create, threadsafe object
intended to be shared by all application threads. A <literal>Session</literal>
is an inexpensive, non-threadsafe object that should be used once, for a single
business process, and then discarded. For example, when using Hibernate in a
servlet-based application, servlets could obtain a <literal>SessionFactory</literal>
using
</para>
<programlisting><![CDATA[SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");]]></programlisting>
<para>
Each call to a service method could create a new <literal>Session</literal>,
<literal>flush()</literal> it, <literal>commit()</literal> its connection,
<literal>close()</literal> it and finally discard it. (The <literal>SessionFactory</literal>
may also be kept in JNDI or in a static <emphasis>Singleton</emphasis> helper variable.)
</para>
<para>
In a stateless session bean, a similar approach could be used. The bean would
obtain a <literal>SessionFactory</literal> in <literal>setSessionContext()</literal>.
Then each business method would create a <literal>Session</literal>,
<literal>flush()</literal> it and <literal>close()</literal> it. Of course, the
application should not <literal>commit()</literal> the connection. (Leave that to
JTA, the database connection participates automatically in container-managed
transactions.)
</para>
<para>
We use the Hibernate <literal>Transaction</literal> API as discussed previously,
a single <literal>commit()</literal> of a Hibernate <literal>Transaction</literal>
flushes the state and commits any underlying database connection (with special
handling of JTA transactions).
</para>
<para>
Ensure you understand the semantics of <literal>flush()</literal>.
Flushing synchronizes the persistent store with in-memory changes but
<emphasis>not</emphasis> vice-versa. Note that for all Hibernate JDBC
connections/transactions, the transaction isolation level for that connection
applies to all operations executed by Hibernate!
</para>
<para>
The next few sections will discuss alternative approaches that utilize versioning
to ensure transaction atomicity. These are considered "advanced" approaches to
be used with care.
</para>
</sect1>
<sect1 id="transactions-threads">
<title>Threads and connections</title>
<para>
You should observe the following practices when creating Hibernate Sessions:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Never create more than one concurrent <literal>Session</literal> or
<literal>Transaction</literal> instance per database connection.
</para>
</listitem>
<listitem>
<para>
Be extremely careful when creating more than one <literal>Session</literal>
per database per transaction. The <literal>Session</literal> itself keeps
track of updates made to loaded objects, so a different <literal>Session</literal>
might see stale data.
</para>
</listitem>
<listitem>
<para>
The <literal>Session</literal> is <emphasis>not</emphasis> threadsafe!
Never access the same <literal>Session</literal> in two concurrent threads.
A <literal>Session</literal> is usually only a single unit-of-work!
</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="transactions-identity">
<title>Considering object identity</title>
<para>
The application may concurrently access the same persistent state in two
different units-of-work. However, an instance of a persistent class is never shared
between two <literal>Session</literal> instances. Hence there are
two different notions of identity:
</para>
<variablelist spacing="compact">
<varlistentry>
<term>Database Identity</term>
<listitem>
<para>
<literal>foo.getId().equals( bar.getId() )</literal>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>JVM Identity</term>
<listitem>
<para>
<literal>foo==bar</literal>
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Then for objects attached to a <emphasis>particular</emphasis> <literal>Session</literal>,
the two notions are equivalent. However, while the application might concurrently access
the "same" (persistent identity) business object in two different sessions, the two
instances will actually be "different" (JVM identity).
</para>
<para>
This approach leaves Hibernate and the database to worry about concurrency. The
application never needs to synchronize on any business object, as long as it sticks to a
single thread per <literal>Session</literal> or object identity (within a
<literal>Session</literal> the application may safely use <literal>==</literal> to
compare objects).
</para>
</sect1>
<sect1 id="transactions-optimistic">
<title>Optimistic concurrency control</title>
<para>
Many business processes require a whole series of interactions with the user
interleaved with database accesses. In web and enterprise applications it is
not acceptable for a database transaction to span a user interaction.
</para>
<para>
Maintaining isolation of business processes becomes the partial responsibility
of the application tier, hence we call this process a long running
<emphasis>application transaction</emphasis>. A single application transaction
usually spans several database transactions. It will be atomar if only one of
these database transactions (the last one) stores the updated data, all others
simply read data.
</para>
<para>
The only approach that is consistent with high concurrency and high
scalability is optimistic concurrency control with versioning. Hibernate
provides for three possible approaches to writing application code that
uses optimistic concurrency.
</para>
<sect2 id="transactions-optimistic-longsession">
<title>Long session with automatic versioning</title>
<para>
A single <literal>Session</literal> instance and its persistent instances are
used for the whole application transaction.
</para>
<para>
The <literal>Session</literal> uses optimistic locking with versioning to
ensure that many database transactions appear to the application as a single
logical application transaction. The <literal>Session</literal> is disconnected
from any underlying JDBC connection when waiting for user interaction. This
approach is the most efficient in terms of database access. The application
need not concern itself with version checking or with reattaching detached
instances.
</para>
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session
session.reconnect();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.disconnect();]]></programlisting>
<para>
The <literal>foo</literal> object still knows which <literal>Session</literal>
it was loaded it. As soon as the <literal>Session</literal> has a JDBC connection,
we commit the changes to the object.
</para>
<para>
This pattern is problematic if our <literal>Session</literal> is too big to
be stored during user think time, e.g. an <literal>HttpSession</literal> should
be kept as small as possible. As the <literal>Session</literal> is also the
(mandatory) first-level cache and contains all loaded objects, we can propably
use this strategy only for a few request/response cycles. This is indeed
recommended, as the <literal>Session</literal> will soon also have stale data.
</para>
</sect2>
<sect2 id="transactions-optimistic-detached">
<title>Many sessions with automatic versioning</title>
<para>
Each interaction with the persistent store occurs in a new <literal>Session</literal>.
However, the same persistent instances are reused for each interaction with the database.
The application manipulates the state of detached instances originally loaded in another
<literal>Session</literal> and then "reassociates" them using
<literal>Session.update()</literal> or <literal>Session.saveOrUpdate()</literal>.
</para>
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
session.saveOrUpdate(foo);
session.flush();
session.connection().commit();
session.close();]]></programlisting>
<para>
You may also call <literal>lock()</literal> instead of <literal>update()</literal>
and use <literal>LockMode.READ</literal> (performing a version check, bypassing all
caches) if you are sure that the object has not been modified.
</para>
</sect2>
<sect2 id="transactions-optimistic-manual">
<title>Application version checking</title>
<para>
Each interaction with the database occurs in a new <literal>Session</literal>
that reloads all persistent instances from the database before manipulating them.
This approach forces the application to carry out its own version checking to ensure
application transaction isolation. (Of course, Hibernate will still <emphasis>update</emphasis>
version numbers for you.) This approach is the least efficient in terms of database access.
It is the approach most similar to entity EJBs.
</para>
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
session = factory.openSession();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() );
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.close();]]></programlisting>
<para>
Of course, if you are operating in a low-data-concurrency environment and don't
require version checking, you may use this approach and just skip the version
check.
</para>
</sect2>
</sect1>
<sect1 id="transactions-disconnection">
<title>Session disconnection</title>
<para>
The first approach described above is to maintain a single <literal>Session</literal>
for a whole business process thats spans user think time. (For example, a servlet might
keep a <literal>Session</literal> in the user's <literal>HttpSession</literal>.) For
performance reasons you should
</para>
<orderedlist spacing="compact">
<listitem>
<para>
commit the <literal>Transaction</literal> (or JDBC connection) and then
</para>
</listitem>
<listitem>
<para>
disconnect the <literal>Session</literal> from the JDBC connection
</para>
</listitem>
</orderedlist>
<para>
before waiting for user activity. The method <literal>Session.disconnect()</literal>
will disconnect the session from the JDBC connection and return the connection to
the pool (unless you provided the connection).
</para>
<para>
<literal>Session.reconnect()</literal> obtains a new connection (or you may supply one)
and restarts the session. After reconnection, to force a version check on data you aren't
updating, you may call <literal>Session.lock()</literal> on any objects that might have
been updated by another transaction. You don't need to lock any data that you
<emphasis>are</emphasis> updating.
</para>
<para>
Heres an example:
</para>
<programlisting><![CDATA[SessionFactory sessions;
List fooList;
Bar bar;
....
Session s = sessions.openSession();
Transaction tx = null;
try {
tx = s.beginTransaction();
fooList = s.find(
"select foo from eg.Foo foo where foo.Date = current date"
// uses db2 date function
);
bar = (Bar) s.create(Bar.class);
tx.commit();
}
catch (Exception e) {
if (tx!=null) tx.rollback();
s.close();
throw e;
}
s.disconnect();]]></programlisting>
<para>
Later on:
</para>
<programlisting><![CDATA[s.reconnect();
try {
tx = s.beginTransaction();
bar.setFooTable( new HashMap() );
Iterator iter = fooList.iterator();
while ( iter.hasNext() ) {
Foo foo = (Foo) iter.next();
s.lock(foo, LockMode.READ); //check that foo isn't stale
bar.getFooTable().put( foo.getName(), foo );
}
tx.commit();
}
catch (Exception e) {
if (tx!=null) tx.rollback();
throw e;
}
finally {
s.close();
}]]></programlisting>
<para>
You can see from this how the relationship between <literal>Transaction</literal>s and
<literal>Session</literal>s is many-to-one, A <literal>Session</literal> represents a
conversation between the application and the database. The
<literal>Transaction</literal> breaks that conversation up into atomic units of work
at the database level.
</para>
</sect1>
<sect1 id="transactions-locking">
<title>Pessimistic Locking</title>
<para>
It is not intended that users spend much time worring about locking strategies. Its usually
enough to specify an isolation level for the JDBC connections and then simply let the
database do all the work. However, advanced users may sometimes wish to obtain
exclusive pessimistic locks, or re-obtain locks at the start of a new transaction.
</para>
<para>
Hibernate will always use the locking mechanism of the database, never lock objects
in memory!
</para>
<para>
The <literal>LockMode</literal> class defines the different lock levels that may be acquired
by Hibernate. A lock is obtained by the following mechanisms:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>LockMode.WRITE</literal> is acquired automatically when Hibernate updates or inserts
a row.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.UPGRADE</literal> may be acquired upon explicit user request using
<literal>SELECT ... FOR UPDATE</literal> on databases which support that syntax.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.UPGRADE_NOWAIT</literal> may be acquired upon explicit user request using a
<literal>SELECT ... FOR UPDATE NOWAIT</literal> under Oracle.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.READ</literal> is acquired automatically when Hibernate reads data
under Repeatable Read or Serializable isolation level. May be re-acquired by explicit user
request.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.NONE</literal> represents the absence of a lock. All objects switch to this
lock mode at the end of a <literal>Transaction</literal>. Objects associated with the session
via a call to <literal>update()</literal> or <literal>saveOrUpdate()</literal> also start out
in this lock mode.
</para>
</listitem>
</itemizedlist>
<para>
The "explicit user request" is expressed in one of the following ways:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
A call to <literal>Session.load()</literal>, specifying a <literal>LockMode</literal>.
</para>
</listitem>
<listitem>
<para>
A call to <literal>Session.lock()</literal>.
</para>
</listitem>
<listitem>
<para>
A call to <literal>Query.setLockMode()</literal>.
</para>
</listitem>
</itemizedlist>
<para>
If <literal>Session.load()</literal> is called with <literal>UPGRADE</literal> or
<literal>UPGRADE_NOWAIT</literal>, and the requested object was not yet loaded by
the session, the object is loaded using <literal>SELECT ... FOR UPDATE</literal>.
If <literal>load()</literal> is called for an object that is already loaded with
a less restrictive lock than the one requested, Hibernate calls
<literal>lock()</literal> for that object.
</para>
<para>
<literal>Session.lock()</literal> performs a version number check if the specified lock
mode is <literal>READ</literal>, <literal>UPGRADE</literal> or
<literal>UPGRADE_NOWAIT</literal>. (In the case of <literal>UPGRADE</literal> or
<literal>UPGRADE_NOWAIT</literal>, <literal>SELECT ... FOR UPDATE</literal> is used.)
</para>
<para>
If the database does not support the requested lock mode, Hibernate will use an appropriate
alternate mode (instead of throwing an exception). This ensures that applications will
be portable.
</para>
</sect1>
</chapter>