Last week, I taught Intertech’s Complete Hibernate to a group of students that already had a bit of experience with Hibernate (on the job training as they took over an existing application). Because of some of their experiences, they had a number of excellent and advanced questions. Some of my upcoming blog posts are answers to some of these questions, some of which I must admit had me scrambling to the documentation and Hibernate text books for help and background material. So, before beginning, I must thank Tyler, Jeff and Mike for their observations, keen eye, and desire to fill in the knowledge gaps to some of what they already knew. Hopefully some of their questions and the answers I provide can help you in your Hibernate work as well.
HIBERNATE OPTIMISTIC LOCKING
The question I would like to address in this post is about Hibernate optimistic locking, optimistic locking with no special version number or timestamp. While pessimistic locking is an available option in Hibernate to address concurrency control, for most enterprise applications, optimistic locking is the preferred choice. As the Hibernate documentation states:
Hibernate can provide optimistic locking via version number or effectivity timestamp. To learn more about either approach, you can see an earlier post of mine, or check out the Hibernate documentation. Optimistic locking via version number or effectivity timestamp is automatically managed by Hibernate. Simply provide the column to hold the version or timestamp, add the optimistic lock characteristics to your Hibernate mapping (via XML or annotations), and Hibernate does the rest.
OPTIMISTIC LOCKING SANS VERSION OR TIMESTAMP
However, there may be situations whereby adding a version number or timestamp column to your database table is not possible. This might be the case when a legacy database is in place and many applications use the table and cannot or will not be updated to use a new version/timestamp column for optimistic locking. In this case, Hibernate can compare the column values of a table row against the state of the persistent object to check that nothing in a row was modified before updating the row. There are two forms of this state-checking optimistic locking: all-state-check or dirty-state-check.
OPTIMISTIC-LOCK = ‘ALL’
When you set the optimistic-lock attribute on the <class> element in the Hibernate mapping file to the value ‘all’ (as shown on the Vehicle class mapping below), you are informing Hibernate to check that no column in the associated row has changed since the persistent object was retrieved and before updating the row in the database.
1: <hibernate-mapping package="com.intertech.domain">
2: <class name="Vehicle" dynamic-update="true" optimistic-lock="all">
3: <id name="id">
4: <generator class="increment" />
5: </id>
6: <property name="make" />
7: <property name="model" />
8: <property name="vin" />
9: </class>
10: </hibernate-mapping>
For example, say you used Hibernate to obtain an instance of Vehicle and then updated it’s make as shown by the code below.
1: SessionFactory sf = new Configuration().configure()
2: .buildSessionFactory();
3: Session sess = sf.openSession();
4: Transaction trx = sess.beginTransaction();
5: Vehicle v = (Vehicle) sess.get(Vehicle.class, 1L);
6: v.setMake("Kia");
7: sess.saveOrUpdate(v);
8: trx.commit();
9: sess.close();
Hibernate would actually use all the existing persistent object values (the last known state values) to form the SQL where-clause used in the update call. Note that every column is mentioned in the where-clause of the example, even the old value of the changed property (make in this case). If any other concurrent activity on this row changed the data of the row (or deleted the row), Hibernate would detect this by the fact that the where-clause would fail to match the row (resulting in a StaleObjectStateException.
1: Hibernate:
2: update
3: Vehicle
4: set
5: make='Kia'
6: where
7: id=1
8: and make='Ford'
9: and model='SUV'
10: and vin=12345
OPTIMISTIC-LOCK = ‘DIRTY’
As an alternative, you can also allow Hibernate to only use modified or dirty properties in the where-clause of the update. When mapping the persistent class, set the optimistic-lock attribute to a value of ‘dirty’ (as shown below).
1: <hibernate-mapping package="com.intertech.domain">
2: <class name="Vehicle" dynamic-update="true" optimistic-lock="dirty">
3: <id name="id">
4: <generator class="increment" />
5: </id>
6: <property name="make" />
7: <property name="model" />
8: <property name="vin" />
9: </class>
10: </hibernate-mapping>
Hibernate now uses only the last known state values of modified persistent object properties to form the SQL where-clause used in the update call. As an example, assume this code is used to update a Vehicle’s make and model state.
1: SessionFactory sf = new Configuration().configure()
2: .buildSessionFactory();
3: Session sess = sf.openSession();
4: Transaction trx = sess.beginTransaction();
5: Vehicle v = (Vehicle) sess.get(Vehicle.class, 1L);
6: v.setMake("Chevy");
7: v.setModel("sedan");
8: sess.saveOrUpdate(v);
9: trx.commit();
10: sess.close();
The update SQL only includes checks of the existing row’s make and model columns, that is the dirty properties.
1: Hibernate:
2: update
3: Vehicle
4: set
5: make='Chevy',
6: model='sedan'
7: where
8: id=1
9: and make='Kia'
10: and model='SUV'
This alternative allows other concurrent processes to update the same row so long as they do not modify the same columns. For example, another application could be updating the vin number of our vehicle at the same time we are updating the make and model and neither of the applications suffer a concurrency error. The merits of allowing this type of simultaneous update are questionable (at best). From a business perspective, only you can determine if modifications that do not conflict constitute change that should cause a concurrency failure or not. Hibernate provides the options, you must determine whether the technology fits with your definition of protecting against concurrent change issues.
DYNAMIC-UPDATE MUST BE TRUE
It should be noted that in both of these non-version/timestamp optimistic locking options, you must enable dynamic-update (set the dynamic-update=’true’ attribute) in the class mapping as shown above. This is because Hibernate cannot generate the SQL for these update statements at application startup (it wouldn’t know what to include in the update statement where-clauses until a change is actually made).
BIG LIMITATIONS OF NON-VERSION/TIMESTAMP OPTIMISTIC LOCKING
Regardless of whether you use the optimistic-lock=all or optimistic-lock=dirty strategy, there are some big limitations to Hibernate’s optimistic locking that does not use version numbers or timestamps.
NO DETACHED
When you close a session, Hibernate loses its persistence context and ability to track what changes on the objects. Therefore, you cannot use the optimistic-lock=all or optimistic-lock=dirty option unless you retrieve and modify the persistent object in the same session (i.e. persistence context). If you attempt to get a persistent object in one session, detach the object and then update the object in another session, you will actually create a situation of potentially catastrophic last-in-wins updates. For example, try this code with optimistic-lock set to ‘all’ on the Vehicle class mapping.
1: SessionFactory sf = new Configuration().configure()
2: .buildSessionFactory();
3: Session sess = sf.openSession();
4: Transaction trx = sess.beginTransaction();
5: Vehicle v = (Vehicle) sess.get(Vehicle.class, 1L);
6: trx.commit();
7: sess.close();
8: System.out.println("vehicle now detached: " + v);
9:
10: v.setVin(7890);
11:
12: //reattach and update
13: sess = sf.openSession();
14: trx = sess.beginTransaction();
15: sess.saveOrUpdate(v);
16: trx.commit();
17: sess.close();
18: System.out.println("vehicle saved again: " + v);
When Hibernate reattaches the Vehicle object and brings it back into the persistence context in the second session, it actually does the unthinkable, it synchronizes (i.e. updates) every column of the associated row without checking to see if it was updated by some other concurrent process! Last in wins and no concurrency check!!!
1: Hibernate:
2: update
3: Vehicle
4: set
5: make='Chevy',
6: model='sedan',
7: vin=7890
8: where
9: id=1
NOT JPA STANDARD
Wonder why I haven’t shown you the annotation form of this non-version optimistic locking yet? If you are currently using annotations to provide all of your object-database mappings, you will also be disappointed to learn JPA does not support optimistic locking outside of versioning (by number or timestamp). So there are no JPA annotations to perform optimistic locking by field or dirty fields. There is, however, a Hibernate annotations that can be used. When mapping, add a parameter to the org.hibernate.annotations.Entity annotation to specify how to perform optimistic locking (as shown here).
1: @Entity
2: @org.hibernate.annotations.Entity(dynamicUpdate = true,
3: optimisticLock = OptimisticLockType.ALL)
4: public class Vehicle {
5: // ...
6: }
Of course, this annotation requires a Hibernate import and more tightly couples your domain class to Hibernate.
IT’S SLOWER
Additionally, note that using all-column or dirty-column checks as part of the where-clause is a little slower. When updating a single persistent object every so often, the speed may not be an issue. But in systems dealing with large numbers of transactions, every millisecond counts.
WRAP UP
As you can see, Hibernate can perform some real time and code-saving work when it comes to optimistic locking. The several options allow for you to pick a concurrent strategy in-line with your system architecture and business needs. But under most circumstances, Hibernate optimistic locking without a version or timestamp will not work for highly concurrent, highly scalable applications, and doesn’t that pretty much describe most enterprise application needs today? If you need more help on Hibernate, contact Intertech and consider taking our Complete Hibernate class.
Read more: http://www.intertech.com/Blog/hibernate-optimistic-lock-without-a-version-or-timestamp/#ixzz3hM4enqYH
Follow us: @IntertechInc on Twitter | Intertech on Facebook