Show Table of Contents
11.4. Optimistic Concurrency Control
11.4.1. About Optimistic Concurrency Control
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 three possible approaches to writing application code that uses optimistic concurrency. The use cases we discuss are in the context of long conversations, but version checking also has the benefit of preventing lost updates in single database transactions.
11.4.2. Application Version Checking
In an implementation without much help from Hibernate, each interaction with the database occurs in a new
Sessionand the developer is responsible for reloading all persistent instances from the database before manipulating them. The application is forced 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("Message", foo.getId()); foo.setProperty("bar"); t.commit(); session.close();
versionproperty is mapped using
<version>, and Hibernate will automatically increment it during flush if the entity is dirty.
If you are operating in a low-data-concurrency environment, and do not require version checking, you can use this approach and skip the version check. In this case, last commit wins is the default strategy for long conversations. Be aware 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.
Manual version checking is only feasible in 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
Sessionor detached instances as the design paradigm.
11.4.3. Extended Session and Automatic Versioning
Sessioninstance and its persistent instances that are used for the whole conversation are known as session-per-conversation. Hibernate checks instance versions at flush time, throwing an exception if concurrent modification is detected. It is 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.
Sessionis disconnected from any underlying JDBC connection when waiting for user interaction. This approach is the most efficient in terms of database access. The application does not version check or reattach detached instances, nor does it have to reload instances in every database transaction.
// 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
fooobject knows which
Sessionit 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 are not updating, you can call
LockMode.READon any objects that might have been updated by another transaction. You do not need to lock any data that you are updating. Usually you would set
FlushMode.MANUALon an extended
Session, so that only the last database transaction cycle is allowed to actually persist all modifications made in this conversation. Only this last database transaction will include the
flush()operation, and then
close()the session to end the conversation.
This pattern is problematic if the
Sessionis too big to be stored during user think time (for example, an
HttpSessionshould be kept as small as possible). As the
Sessionis also the first-level cache and contains all loaded objects, we can probably use this strategy only for a few request/response cycles. Use a
Sessiononly for a single conversation as it will soon have stale data.
Earlier versions of Hibernate required explicit disconnection and reconnection of a
Session. These methods are deprecated, as beginning and ending a transaction has the same effect.
Keep the disconnected
Sessionclose to the persistence layer. Use an EJB stateful session bean to hold the
Sessionin a three-tier environment. Do not 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
CurrentSessionContextfor this. See the Hibernate Wiki for examples.
11.4.4. Detached Objects and 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
Sessionand 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 can also call
update(), and use
LockMode.READ(performing a version check and bypassing all caches) if you are sure that the object has not been modified.
11.4.5. Customizing Automatic Versioning
You can disable Hibernate's automatic version increment for particular properties and collections by setting the
optimistic-lockmapping attribute to
false. Hibernate will then no longer increment versions if the property is dirty.
Legacy database schemas are often static and cannot be modified. Or, other applications might access the same database and will not know how to handle version numbers or even timestamps. In both cases, versioning cannot rely on a particular column in a table. To force a version check with a comparison of the state of all fields in a row but without a version or timestamp property mapping, turn on
<class>mapping. This conceptually only works if Hibernate can compare the old and the new state (i.e., if you use a single long
Sessionand not session-per-request-with-detached-objects).
Concurrent modification can be permitted in instances where the changes that have been made do not 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 a full/dirty field comparison, Hibernate uses a single
UPDATEstatement, with an appropriate
WHEREclause, per entity to execute the version check and update the information. If you use transitive persistence to cascade reattachment to associated entities, Hibernate may 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
<class>mapping, forcing Hibernate to
SELECTthe instance to ensure that changes did occur before updating the row.