[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6. Locking

Historically there are many forms of locking mechanisms, and many implementations. Other O/R mapping tools might offer you any of these mechanisms, or they offer none, and let you figure it out with database isolation levels. The problem is, as discussed earlier, that these isolation levels do not provide enough protection for a parallel and/or distributed application, like a webapplication. And they are very hard to understand, and handle.

There are basically two kinds of locks people often encounter. There are read and write locks. Read locks let you lock an object for reading making it practically read-only, so during the transaction (until unlock) you can guarantee that the object in question does not change. When you hold a read lock on an object, obviously many other users can hold read locks too to the same object. A write lock is different, because only one user can hold a write lock on an object, and that user has the right to read or modify the object.

BeanKeeper supports both of these types of locks, but in a bit different fashion. Read locks are usually used for two different purposes: One is, that inside a transaction some data you read must be made to look consistent, so it does not change during the transaction, because that would cause the transaction logic to be inconsistent. This scenario is handled by BeanKeeper as described previously, because inside a transaction the whole database freezes to the state when the transaction begun, so nothing changes. The other reason to use read-locks is too really prevent the object in question to change. The difference is, that the first scenario only does not want to see the change, but really does not care if it changes. The second wants to actively prevent the change.

An example would be: A DVD rental software's report screen first lists the people who have DVDs out, then lists DVD titles, and how many of them are out. If the database would change right after the list of people is queried, but before the list of DVDs is queried, then the screen might become inconsistent. Let's say that at another counter, a DVD is brought back exatcly between the two lists. Then the DVDs list would indicate that the given DVD is in, but the peoples list would indicate, that somebody has that DVD. The report screen logic in reality does not care if somebody brought back the DVD, all it cares about is, that the change should not be visible when it is in the middle of it's calculation. This is the scenario that never going to happen in BeanKeeper, because this is automatically prevented.

An example for the second usage of read locks would be to imagine a credit-card re-activation function in a bank software. Let's suppose the logic of the reactivation is: check the balance, whether it is positive, and re-activate the card if it is. In this case, if the balance is positive, but a withdrawal happens exactly between the checking and the reactivation, then it is possible, that the re-activation occurs on a negative balance. Note however, that if the change is not visisble to the reactivation transaction, the logic still failed, because it activated a negative balance card. The solution is to actively prevent the change from happening while the reactivation runs, and this is what a read lock does.

So the difference between the two is, if you don't hold read locks, modifications can happen to things you are using, you just won't see them if the transaction you are in started before the modifications took place. If you do hold a read lock however, you don't just not see the modifications, you're actually preventing everyone making modifications.

When you hold a write lock in BeanKeeper, that means you intend to modify the given thing (object or objects of a given class). If you hold a write lock, the object still can be read by other threads, however nobody can have any kind (read or write) locks to that object (or object's class, or any super-, or sub-classes). This means, you have exclusive usage of that object, and functions which would require read-locks can not run until you release (unlock) the object. Note however, that this kind of lock is still friendlier than database locks, since it still enables anyone to read the object in question, which is usually prevented with conventional locks.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.1 Object Locking

O/R mapping libraries have usually two kinds of strategies when dealing with multiple usages of the same object, for example when two parallel threads select the same object from the database.

The first strategy is, that all object identities can have at most one instance in the JVM. That is usually achieved by keeping a reference to each loaded object, and when a second transaction tries to load the same object identity from the database, the same instance is returned. This means also, that modifying the object with setter bean methods instantly changes the object for the other transaction also. This solution does not scale to distributed applications well.

The second strategy is, that all queries return new instances of the same object identity, and this is what BeanKeeper uses. With this, modifying either of the objects won't affect the other, which is considered a good thing, but with this we open a whole new can of worms. It is possible for two transaction to try to modify the same object identity (table row), but with different data, which could lead to an inconsistent state when the two transaction interfere with eachother. This is where locking comes into play. Locking is achieved through the LockTracker object available from store. To lock an object, simply call:

 
store.getLockTracker().lock(obj);

The lock is active until the lock tracker receives the approriate unlock() call:

 
store.getLockTracker().unlock(obj);

Note, that locks are embeddable, so that an object is only unlocked, if the LockTracker recevies the same amount of unlock() calls as lock() calls. When an object is locked twice for example (because the object is also locked in a deeper method also), then the first unlock() call will not actually unlock the object, just decrease the depth of the locking.

When an object is locked, no modifications (save() or remove()) can be invoked on any of the objects with the same identity (representing the same row in the database). This means, that only the keeper of the original object may modify that identity. You can also supply a SessionInfo object to the lock method, representing the thread which locks the object. This SessionInfo may contain information about the current caller user, or anything related to the session in which the lock occurs. If another thread (or node) tries to lock an object with the same identity, then it receives an exception that indicates, that the object is already locked, and this SessionInfo object is also contained in the exception. With the help of this SessionInfo object, the other thread might inform it's user who the current owner of that object is (if this information is contained in the SessionInfo).

By default, SessionInfo is created from the actual transaction object, so that all information in the transaction is copied into the SessionInfo. This behaviour can be changed by supplying a SessionInfoProvider to the LockTracker (see apidoc).

Note, that only the owner of a lock can unlock the said object. The owner is defined as the same node and the same thread which locked the object. If there was an explicit Transaction when the lock occured, then the lock can not be undone in another transaction. (It can be unlocked outside the original transaction however, in the same thread).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.2 Class locking

BeanKeeper offers a mechnism to lock whole classes too. This is mostly done to prevent creation of new objects for example, which could not be done just by using object locks. Locking a class means, that no save() or remove() operations can be done which concerns objects with the given class.

You can use the same api as with locking objects:

 
store.getLockTracker().lock(Book.class);

Locking classes however is a bit different, because it is polymorphic. Locking a superclass will lock all of it's subclasses too. So locking a Vehicle type will lock Car and Truck subclasses too. This also means, that locking Object.class will lock the whole database (because everything is a subclass of Object.class). You can lock interface classes too, in which case all classes which implement it will be locked.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.3 Ensuring an object is current

Sometimes, in a statless environment (like in a webapplication), you need to ensure, that an object is still the most recent version. In a webapplication, it is possible to initiate so called "long" transactions, with multiple human interactions. Consider the following application: An application presents the user an object in which she can increment a number. When the user presses the submit button, and the server receives the object, it can not be sure, whether that object is current. If it increments the number anyway, but something modified the number when the object was on screen, then possibly a wrong number will be the result.

In this case, you can not lock the object when you give it to the user, because in a web environment, it's not guaranteed, that the object will come back. The user could just close the browser and then you would have a lock which won't be unlocked. To avoid this, and still offer something lock-like, BeanKeeper offers the chance to ensure that an object is still current when locking. This way, the server could lock the object which comes back, and it could be sure that the object has not changed in the database.

Note, ensuring that an object is current does not mean, that it has the same attributes as the last version. It only means, that since the object in question was selected from the database no new versions were commited.

To ensure that an object is current, you call lockEnsureCurrent:

 
store.getLockTracker().lockEnsureCurrent(obj);

After the call, it is guaranteed, that the object was not modified in the database since it was selected, so it behaves as if it were locked since it's selection. If the object was modified, the usual ConcurrentModificationException is thrown.

Because you can lock classes too, you can also ensure that a class has not been modified:

 
store.getLockTracker().lockEnsureCurrent(Book.class);

A class is ensured to be current from the following date (whichever came first):

If there is no transaction, or no other object to lock with, then the call equals a simple lock.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Robert Brautigam on November, 21 2009 using texi2html 1.78.