The only approach that is consistent with high concurrency and high scalability is optimistic concurrency control with versioning. Version checking uses version numbers, or timestamps, to detect conflicting updates (and to prevent lost updates). Hibernate provides for three possible approaches to writing application code that uses optimistic concurrency. The use cases we show are in the context of long conversations, but version checking also has the benefit of preventing lost updates in single database transactions.
In an implementation without much help from Hibernate, each interaction with the
database occurs in a new
Session and the developer is responsible
for reloading all persistent instances from the database before manipulating them.
This approach forces the application to carry out its own version checking to ensure
conversation transaction isolation. This approach is the least efficient in terms of
database access. It is the approach most similar to entity EJBs.
// foo is an instance loaded by a previous Session session = factory.openSession(); Transaction t = session.beginTransaction(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); // load the current state if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException(); foo.setProperty("bar"); t.commit(); session.close();
version property is mapped using
and Hibernate will automatically increment it during flush if the entity is
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. In that case, last commit wins will be the default strategy for your long conversations. Keep in mind that this might confuse the users of the application, as they might experience lost updates without error messages or a chance to merge conflicting changes.
Clearly, manual version checking is only feasible in very trivial circumstances
and not practical for most applications. Often not only single instances, but
complete graphs of modified objects have to be checked. Hibernate offers automatic
version checking with either an extended
Session or detached instances
as the design paradigm.
Session instance and its persistent instances are
used for the whole conversation, known as session-per-conversation.
Hibernate checks instance versions at flush time, throwing an exception if concurrent
modification is detected. It's up to the developer to catch and handle this exception
(common options are the opportunity for the user to merge changes or to restart the
business conversation with non-stale data).
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, nor does it have to reload instances in every
// foo is an instance loaded earlier by the old session Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction foo.setProperty("bar"); session.flush(); // Only for last transaction in conversation t.commit(); // Also return JDBC connection session.close(); // Only for last transaction in conversation
foo object still knows which
Session it was
loaded in. Beginning a new database transaction on an old session obtains a new connection
and resumes the session. Committing a database transaction disconnects a session
from the JDBC connection and returns the connection to the pool. After reconnection, to
force a version check on data you aren't updating, you may call
LockMode.READ on any objects that might have been updated by another
transaction. You don't need to lock any data that you are updating.
Usually you would set
FlushMode.MANUAL on an extended
so that only the last database transaction cycle is allowed to actually persist all
modifications made in this conversation. Hence, only this last database transaction
would include the
flush() operation, and then also
close() the session to end the conversation.
This pattern is problematic if the
Session is too big to
be stored during user think time, e.g. an
be kept as small as possible. As the
Session is also the
(mandatory) first-level cache and contains all loaded objects, we can probably
use this strategy only for a few request/response cycles. You should use a
Session only for a single conversation, as it will soon also
have stale data.
(Note that earlier Hibernate versions required explicit disconnection and reconnection
Session. These methods are deprecated, as beginning and
ending a transaction has the same effect.)
Also note that you should keep the disconnected
to the persistence layer. In other words, use an EJB stateful session bean to
Session in a three-tier environment, and don't transfer
it to the web layer (or even serialize it to a separate tier) to store it in the
The extended session pattern, or session-per-conversation, is
more difficult to implement with automatic current session context management.
You need to supply your own implementation of the
for this, see the Hibernate Wiki for examples.
Each interaction with the persistent store occurs in a new
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 reattaches them using
// foo is an instance loaded by a previous Session foo.setProperty("bar"); session = factory.openSession(); Transaction t = session.beginTransaction(); session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already t.commit(); session.close();
Again, Hibernate will check instance versions during flush, throwing an exception if conflicting updates occurred.
You may also call
lock() instead of
LockMode.READ (performing a version check, bypassing all
caches) if you are sure that the object has not been modified.
You may disable Hibernate's automatic version increment for particular properties and
collections by setting the
optimistic-lock mapping attribute to
false. Hibernate will then no longer increment versions if the
property is dirty.
Legacy database schemas are often static and can't be modified. Or, other applications
might also access the same database and don't know how to handle version numbers or
even timestamps. In both cases, versioning can't rely on a particular column in a table.
To force a version check without a version or timestamp property mapping, with a
comparison of the state of all fields in a row, turn on
<class> mapping. Note that this conceptually only works
if Hibernate can compare the old and new state, i.e. if you use a single long
Session and not session-per-request-with-detached-objects.
Sometimes concurrent modification can be permitted as long as the changes that have been
made don't overlap. If you set
optimistic-lock="dirty" when mapping the
<class>, Hibernate will only compare dirty fields during flush.
In both cases, with dedicated version/timestamp columns or with full/dirty field
comparison, Hibernate uses a single
UPDATE statement (with an
WHERE clause) per entity to execute the version check
and update the information. If you use transitive persistence to cascade reattachment
to associated entities, Hibernate might execute unnecessary updates. This is usually
not a problem, but on update triggers in the database might be
executed even when no changes have been made to detached instances. You can customize
this behavior by setting
select-before-update="true" in the
<class> mapping, forcing Hibernate to
the instance to ensure that changes did actually occur, before updating the row.