Transactions And Concurrency
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
Session are atomically part of the wider JTA
transaction. Hibernate can be seen as a thin adapter to JDBC, adding object-
oriented semantics.
Configurations, Sessions and Factories
A SessionFactory is an expensive-to-create, threadsafe object
intended to be shared by all application threads. A Session
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 SessionFactory
using
Each call to a service method could create a new Session,
flush() it, commit() its connection,
close() it and finally discard it. (The SessionFactory
may also be kept in JNDI or in a static Singleton helper variable.)
In a stateless session bean, a similar approach could be used. The bean would
obtain a SessionFactory in setSessionContext().
Then each business method would create a Session,
flush() it and close() it. Of course, the
application should not commit() the connection. (Leave that to
JTA, the database connection participates automatically in container-managed
transactions.)
We use the Hibernate Transaction API as discussed previously,
a single commit() of a Hibernate Transaction
flushes the state and commits any underlying database connection (with special
handling of JTA transactions).
Ensure you understand the semantics of flush().
Flushing synchronizes the persistent store with in-memory changes but
not vice-versa. Note that for all Hibernate JDBC
connections/transactions, the transaction isolation level for that connection
applies to all operations executed by Hibernate!
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.
Threads and connections
You should observe the following practices when creating Hibernate Sessions:
Never create more than one concurrent Session or
Transaction instance per database connection.
Be extremely careful when creating more than one Session
per database per transaction. The Session itself keeps
track of updates made to loaded objects, so a different Session
might see stale data.
The Session is not threadsafe!
Never access the same Session in two concurrent threads.
A Session is usually only a single unit-of-work!
Considering object identity
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 Session instances. Hence there are
two different notions of identity:
Database Identity
foo.getId().equals( bar.getId() )
JVM Identity
foo==bar
Then for objects attached to a particular Session,
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).
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 Session or object identity (within a
Session the application may safely use == to
compare objects).
Optimistic concurrency control
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.
Maintaining isolation of business processes becomes the partial responsibility
of the application tier, hence we call this process a long running
application transaction. 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.
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.
Long session with automatic versioning
A single Session instance and its persistent instances are
used for the whole application transaction.
The Session uses optimistic locking with versioning to
ensure that many database transactions appear to the application as a single
logical application transaction. The Session 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.
The foo object still knows which Session
it was loaded it. As soon as the Session has a JDBC connection,
we commit the changes to the object.
This pattern is problematic if our Session is too big to
be stored during user think time, e.g. an HttpSession should
be kept as small as possible. As the Session 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 Session will soon also have stale data.
Many sessions with automatic versioning
Each interaction with the persistent store occurs in a new Session.
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
Session and then "reassociates" them using
Session.update() or Session.saveOrUpdate().
You may also call lock() instead of update()
and use LockMode.READ (performing a version check, bypassing all
caches) if you are sure that the object has not been modified.
Application version checking
Each interaction with the database occurs in a new Session
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 update
version numbers for you.) This approach is the least efficient in terms of database access.
It is the approach most similar to entity EJBs.
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.
Session disconnection
The first approach described above is to maintain a single Session
for a whole business process thats spans user think time. (For example, a servlet might
keep a Session in the user's HttpSession.) For
performance reasons you should
commit the Transaction (or JDBC connection) and then
disconnect the Session from the JDBC connection
before waiting for user activity. The method Session.disconnect()
will disconnect the session from the JDBC connection and return the connection to
the pool (unless you provided the connection).
Session.reconnect() 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 Session.lock() on any objects that might have
been updated by another transaction. You don't need to lock any data that you
are updating.
Heres an example:
Later on:
You can see from this how the relationship between Transactions and
Sessions is many-to-one, A Session represents a
conversation between the application and the database. The
Transaction breaks that conversation up into atomic units of work
at the database level.
Pessimistic Locking
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.
Hibernate will always use the locking mechanism of the database, never lock objects
in memory!
The LockMode class defines the different lock levels that may be acquired
by Hibernate. A lock is obtained by the following mechanisms:
LockMode.WRITE is acquired automatically when Hibernate updates or inserts
a row.
LockMode.UPGRADE may be acquired upon explicit user request using
SELECT ... FOR UPDATE on databases which support that syntax.
LockMode.UPGRADE_NOWAIT may be acquired upon explicit user request using a
SELECT ... FOR UPDATE NOWAIT under Oracle.
LockMode.READ is acquired automatically when Hibernate reads data
under Repeatable Read or Serializable isolation level. May be re-acquired by explicit user
request.
LockMode.NONE represents the absence of a lock. All objects switch to this
lock mode at the end of a Transaction. Objects associated with the session
via a call to update() or saveOrUpdate() also start out
in this lock mode.
The "explicit user request" is expressed in one of the following ways:
A call to Session.load(), specifying a LockMode.
A call to Session.lock().
A call to Query.setLockMode().
If Session.load() is called with UPGRADE or
UPGRADE_NOWAIT, and the requested object was not yet loaded by
the session, the object is loaded using SELECT ... FOR UPDATE.
If load() is called for an object that is already loaded with
a less restrictive lock than the one requested, Hibernate calls
lock() for that object.
Session.lock() performs a version number check if the specified lock
mode is READ, UPGRADE or
UPGRADE_NOWAIT. (In the case of UPGRADE or
UPGRADE_NOWAIT, SELECT ... FOR UPDATE is used.)
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.