I'd like to add optimistic locking using checksums to Hibernate, so I
was hoping to get a few of you to look over my shoulder at the rationale
and proposed changes to see if I have any fundamental flaws.
These are the changes I think I need to make to implement this:
- Add isChecksummed() to EntityClass and AbstractEntityPersister
using the same model as isVersioned().
- Modify SessionImpl.initializeEntity to compute the checksum of an object
when it is loaded and attach it to the object using the checksum
PropertyAccessor.
- Modify AbstractEntityPersister.getCurrentPersistentState() to
use a select ... for update if isChecksummable(). (Could also
include wait/nowait modifiers.) Note that if the DB doesn't support
select ... for update, you'd only get the isolation level set on
the connection. It's early here--before dirtiness checks--but it
would fit cleanly with "select-before-update".
- Modify SessionImpl.flushEntity() to compute the checksum and compare
it to the object being updated, throwing a staleness exception if
they don't match.
- Modify the DTD to add a new property roughly equivalent to <version>.
- Modify Binder to account for the new XML in the mapping.
- Modify/create appropriate test cases.
One open issue is whether or not there should be a new value for
"optimistic-lock." I didn't dig into why, but there is a test to see
if the value of the optimistic lock mode is greater than versioning,
if so, dynamic-update is required. Checksumming wouldn't require
dynamic-update. There is another, similar check in
EntityPersister.generateUpdateString(). This could be solved by just
making the checksumming mode int value -2 (none is -1). Maybe it's
time that the optimistic lock mode changed from an int to a typesafe
enum? It seems odd to find it in Versioning also as ALL and DIRTY
modes don't have anything to do with versioning or timestamps.
Another open issue has to do with computing the checksums of
collections, but I'll save that for another thread.
If you haven't seen it before the algorithm for locking with checksums
is this:
1. When you pull an object out of a data store, compute the checksum
and store it somewhere--local map or just attach it to the object.
2. When the object is returned for update, load the current values from
the data store and lock them.
3. Compute the checksum for the current values and compare it to the
checksum on the object that was just submitted for update.
4. If the checksums are the same, update the object and release the lock,
otherwise throw a staleness exception.
So, why create something so obviously problematic? Well, there are two
environmental constraints on this project that make the checksum
approach necessary:
- Hibernate does not have exclusive access to the data.
- There are too many legacy apps (Cobol and Uniface) to retrofit them
with version awareness.
Optimistic locking with checksums is (sort of) the next step up from
optimistic-lock-"all" as the checksum approach can account for changes
to collections.
And why don't I just piggyback on the versioning model? Unfortunately,
I don't think you can fit the checksum approach into the model used
for versioning as there are two key differences:
- Checksums are derived properties of the objects and they would never
be part of the where clause.
- It requires a fresh read from the DB with a lock to protect between
the read and the subsequent update.
You could probably make it work that way, but it seems like it would
be an unpleasant munge of two different models.
Checksumming won't be as solid as version or timestamp and it'll
certainly be more costly to performance, but sometimes it's a
necessary evil.
-robert
|