PriDE Design Patterns: Optimistic Locking


Optimistic Locking is a typical concept for the management of concurrent update access from multiple applications on one database. An update of an existing record is only performed if it has not been modified by someone else since the current application has read the record of interest the last time. If it was modified, the caller is informed about a concurrent access conflict. This concept can easily be implemented with PriDE making use of the patterns introduced so far. All entity types which optimistic locking should be applied to, inherit from a base class, providing the required locking information. This is usually just a simple version counter for every record, e.g.
 
class OptimisticLock extends MappedObject {
    protected static RecordDescriptor red = new RecordDescriptor
        (OptimisticLock.class, null, null, new String[][] {
            { "version", "getVersion", "setVersion" },
        });
    protected RecordDescriptor getDescriptor() { return red; }

    private long version;
    public long getVersion() { return version; }
    public void setVersion(long val) { version = val; }

    protected OptimisticLock() { version = 0; }

    public int update() throws SQLException {

        // Lock check goes here...

        super.update();
    }
}

For the detection of access conflicts there are two different approaches explained below. The more efficient one concerning the number of database operations increments the version counter with every update call. The non-incremented value is added to the where-clause causing the update to work only if the record's version counter wasn't modified by another application in the meanwhile. A simple implementation is an extension for the where-clause which PriDE uses for a database update:
 
class OptimisticLock extends MappedObject {
    //...

    public int update() throws SQLException {
        setVersion(getVersion() + 1);
        int numRows = update
            (constraint() + " AND version=" + (getVersion()-1));
        if (numRows == 0)
            throw new SQLException("optimistic lock error");
        return numRows;
    }
}

The application assumes a conflict, if the update returns 0, i.e. there was no record found matching the restriction by primary key and version number.
Another concept is to read the version counter immediatly before update in a way that the corresponding record is locked for concurrent write access by different applications (select-for-update). The actual update is performed only if the entity's state and the record's state in the database are identical. This concept takes additional database operations and is therefore less efficient. On the other hand it is much safer as it can also become aware of a concurrent record deletion (both physical and logical). As a difference to the standard read method, the entity's current data must not be overridden in the select-for-update to keep the state which is required for later update. Reading only the version counter reduces the data transfer between application and database. The following class provides the corresponding base functionality:
 
public class OptimisticLockClone extends OptimisticLock {
    // The object to update and thus the one to take
    // the primary key data for a version number query from
    private OptimisticLock source;

    // Specialized record descriptor, which takes data for a query from member
    // 'source' above, queries only the 'version' field and stores the
    // result in the clone.
    private class CloneDescriptor extends RecordDescriptor {
        public CloneDescriptor() {
            super(OptimisticLock.class, null, OptimisticLock.red, null);
        }

        // Always take the table name from the 'source' object above
        public String getTableName() { return source.getDesc().getTableName(); }

        // Always build constraints from the 'source' object above
        public String getConstraint(Object obj, String[] dbfields, boolean byLike)
            throws IllegalAccessException, InvocationTargetException {
            return source.getDesc().getConstraint(source, dbfields, byLike);
        }

        // Always query for the 'version' field only
        public String getResultFields() { return "version"; }
    }

    private RecordDescriptor red = new CloneDescriptor();
    public RecordDescriptor getDescriptor() { return red; }
    public String[] getKeyFields() { return source.getKeyFields(); }

    public OptimisticLockClone(OptimisticLock source) { this.source = source; }

    public void findAndLock() throws SQLException {
        find(constraint() + " FOR UPDATE");
    }
}

By means of class OptimisticLockClone the function OptimisticLock.update() can now be extended by the version check as follows:
 
class OptimisticLock extends MappedObject {
    //...

    public int update() throws SQLException {
        OptimisticLockClone clone = new OptimisticLockClone(this);
        clone.findAndLock();
        if (clone.getVersion() > getVersion())
            throw new SQLException("optimistic lock error");
        if (clone.getVersion() < getVersion())
            throw new SQLException("consistency error");
        setVersion(getVersion() + 1);
        return super.update();
    }
}

The complete source code of the examples above can be found in examples/locking.


Home Introduction Javadoc