After my first post concerning Hibernate Java/Hibernate: generate universally a unique object ID with ORM/Hibernate persistence layer, I propose you a post about the concurrency management with a brief presentation of Hibernate versioning, its limits with a problematic case and a simple solution to resolve it. So, here, an simple solution used in an application with a layer ORM (object-relational mapping) like Hibernate.
Presentation of Hibernate versioning
In most applications, it is necessary to manage the concurrency accesses in persistence layer. A possible technique to manage these concurrent accesses is the ‘managed versioning’ (Hibernate versioning) which consists into the adding of a property ‘version’ in the POJO/entity mapped to a specific technical column in table’s database. This column will contain the technical version of the record which will incremented after each updating/saving. So, the table must contain a column dedicated to the technical version, for example VERSION with a numeric type NUMBER(10,0).
A problematic case
Here, I could present you a problematic case with several requests sent from an unique ACTOR:
– asynchronous minor requests in order to lock a specific record in database,
– synchronous main request (for example, update) this same record,
First, we need an abstract class whose inherits the POJO class used in the ORM layer:
public abstract class AbstractPersistentObject { private String id = IdGenerator.createId(); private Integer version = null; [...] }
More, the POJO/entity mapped to the record of database:
public class MyEntity extends AbstractPersistentObject { private int funcVersion; private String status = null; private Calendar creationDate = null; private boolean lock = false; private String lockBy = null; private Calendar lockDate = null; [...] }
Note: This POJO contains a technical version column named ‘version’ and a functional version column named ‘funcVersion’.
The HBM file could be:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class table="MyEntity" name="com.ho.data.MyEntity" discriminator-value="MyEntity"> <id name="id" column="id" type="string"><generator class="assigned" /></id> <discriminator column="class" type="java.lang.String"/> <version name="version" column="version" unsaved-value="null" /> <property column="funcVersion" type="int" name="funcVersion"/> <property column="status" type="java.lang.String" name="status"/> <property column="creationDate" type="java.util.Calendar" name="creationDate"/> <property column="lock" type="boolean" name="lock"/> <property column="lockBy" type="java.lang.String" name="lockBy"/> <property column="lockDate" type="java.util.Calendar" name="lockDate"/> ....
This HBM file specifies a technical version column named ‘version’ and a functional version column named ‘funcVersion’.
For our example, consider a Web based application wherein, the human actor (user) could consult informations from database and lock the linked record due to an asynchronous client CRON system (Ajax via SENCHA Timer Task). So during the consultation time, the client lock system will lock the record in database (columns ‘lock’, ‘lockBy’ and ‘lockDate’) at every minute. In current configuration, the technical version will be incremented after each lock.
The lock is set via classes of ORM layer:
@Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void setLockOnMyEntity(String docLgId, String userName){ myEntity.setLock(true); myEntity.setLockBy(userName); myEntity.setLockDate(GregorianCalendar.getInstance()); myEntityDao.update(myEntity); }
So, we will focus on a problematic scenario with the following steps:
- -t0- RECORD with VERSION=1,
- -t1- ACTOR1 CLIENT consults the record and locks it in database (VERSION=1),
- -t2- ACTOR1 CLIENT sets asynchronously (Ajax) a lock on consulted record in database (VERSION=2),
- -t3- ACTOR1 CLIENT sets asynchronously (Ajax) a lock on consulted record in database (VERSION=3),
- -t4- ACTOR1 CLIENT sends a synchronous request to server which will load the locked record on server and apply a weighty business (VERSION=3),
- -t5- ACTOR1 CLIENT continues to set asynchronously (Ajax) a lock on consulted record in database (VERSION=4),
- -t6- ACTOR1 SERVER tries to update the locked record with the previous loaded version (VERSION=3),
- -t7- ACTOR1 CLIENT continues to set asynchronously (Ajax) a lock on consulted record in database (VERSION=5),
Results: An error occurs in server side during the step -t6- because the version of record in database (VERSION=4) and the version of POJO to modify on server side is not the same:
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
This behaviour is normal, however, in a scenario similar it is necessary to unblock the update on server side, because the client asynchronous lock requests (-t2-, -t3-, -t5-, -t7-) have less importance than the main update request (-t4-, -t6-).
Note: In this scenario, I have specified an unique ACTOR, however, a more complex scenario could use several ACTORS. The problem doesn’t changed.
Simple solution
A simple solution could be:
- set the properties linked to the lock system (columns ‘lock’, ‘lockBy’ and ‘lockDate’) to read-only in HBM file. These properties are set to READONLY due an SQL expression that defines the value for a computed property. These properties are by definition read-only and their value are computed at load time. More information hibernate-mapping-declaration-property.
... <property name="lock" type="boolean" formula="( SELECT lock FROM MYENTITY docLg WHERE docLg.id=id )"/> <property name="lockBy" type="java.lang.String" formula="( SELECT lockBy FROM MYENTITY docLg WHERE docLg.id=id )"/> <property name="lockDate" type="java.util.Calendar" formula="( SELECT lockDate FROM MYENTITY docLg WHERE docLg.id=id )"/> ...
- modify the method updating the lock informations in persistence layer: use directly SQL request because the updates via Hibernate or HQL don’t update the read-only fields:
import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.SessionFactory; @Transactional(propagation=Propagation.MANDATORY, readOnly=true) public boolean setLockOnMyEntity(String docLgId, String userName) throws DataAccessException { Session currSession = getHibernateTemplate().getSessionFactory().getCurrentSession(); Transaction tx = currSession.beginTransaction(); try { StringBuffer sql = new StringBuffer(); sql.append("UPDATE MYENTITY SET ").append(" LOCKDATE=SYSDATE, LOCK=1, LOCKBY='").append(userName).append("' ").append(" WHERE ID=?"); PreparedStatement setLockOMyEntitySTM = currSession.connection().prepareStatement(sql.toString()); setLockOMyEntitySTM.setString(1, docLgId); setLockOMyEntitySTM.addBatch(); setLockOMyEntitySTM.executeBatch(); } catch (Exception e) { logger.error("An exception occured in the function setLockOnMyEntity", e); tx.rollback(); return false; } finally { currSession.close(); } // end-try tx.commit(); return true; }
That’s all. We have implemented a technique for managing concurrency with Hibernate versioning.
Best regards,
Huseyin OZVEREN