Tuesday, August 9, 2011

Optimistic Locking - Play! Way

I would not go into the details of describing what locking or optimistic locking is. There is a great article on wikipedia for reference. I would stop at saying that here I am trying to describe my method of implementing the same in the context of a database within the boundaries of Play! Framework.

A common strategy to build optimistic locking is to add a version field to the model and update the version field on every update. In fact it is so common that JPA has a special annotation for it.

Here is the code for my model class followed by explanation.

@Entity
@Table(name = "REF_BRANCH")
public class Branch extends Model{
  @Version
  @CheckWith(value=OptimisticLockingCheck.class, message="optimisticLocking.modelHasChanged")
  public int version;
  ...
}
@Version is the JPA annotation that will add an auto-incremented column named version to my table (REF_BRANCH) in this case. As a result when I create a new Branch object and persist it, the version field will take a value of 0. Every subsequent update to the above created object will automatically change the value of the version field to 1, 2, 3 ... and so on. The version field above is also annotated with the @CheckWith annotation. More on that in a minute.

I decided to design my application in such a way that failure to acquire a (pseudo) lock before record is updated will be treated as a validation failure. In Play! Framework, custom validation is done by extending the class play.data.validation.Check. Thus I also mark my version field in the model class with the @CheckWith annotation.

public class OptimisticLockingCheck extends Check {
  public boolean isSatisfied(Object model, Object inputVersionObject) {
    try {
      Model modelObject = (Model) model;
      modelObject.refresh();
      Field versionField = modelObject.getClass().getField("version");
      int dbVersion = versionField.getInt(model);
      int inputVersion = ((Integer)inputVersionObject).intValue();
      return dbVersion == inputVersion;
    } catch (Exception e) {
      return false;
    }
  }
}

@CheckWith(value=OptimisticLockingCheck.class, message="some.message") ensures that the above class is used to validate the model instance. A quick reference to the controller's update method will be helpful.
public static void update(@Valid Branch entity) {
  if (validation.hasErrors()) {
    flash.error(Messages.get("scaffold.validation"));
    entity.refresh();
    render("@edit", entity);
  }               
  entity.auditLogs.add(AuditHelper.onUpdate(Security.connected()));
  entity = entity.merge();
  entity.save();
  flash.success(Messages.get("scaffold.updated", "Branch"));
  index();
}

Here update(@Valid Branch entity) ensures that all validations on the Branch object fires before the update method is executed.
One has to ensure that the version field is included in the edit form albeit hidden.