21 Jan 2015

Managing Savepoints with ADF BC

While working with ADF BC we usually rely on the framework for performing DML operations in the database. The framework correctly makes all necessary updates in the database during the DBTransaction commit cycle. The cool thing is that the database transaction is going to be managed automatically in that case. So, if something went wrong, if some entities failed to post to the database, the framework is going to rollback the current transaction to the savepoint at the very beginning of the commit process. Furthermore, the state of the root Application Module is going to be restored to the same point as well. The framework does all that stuff for us and we don't need to care about it.

However, there is a very common use-case when it's needed to perform some DML in the database in order to implement some business service method. Let's consider a method in the AM implementation class:

public void someBusinessMethod() {
  invokePLSQLProcedure1();
  modifySomeAttributes();
  invokePLSQLProcedure2();       
  getDBTransaction().commit();               
}

The method invokes a PL/SQL procedure, modifying some data in the database, modifies some attributes in the entity cache, invokes another PL/SQL procedure and performs commit. Imagine what happens if the second PL/SQL procedure call failed, or if for some reason the framework failed to commit the transaction. Obviously, there is a lock in the database, since the transaction is neither committed nor rollbacked. Moreover, the entity cache contains the data modified by the modifySomeAttributes() method despite the fact that someBusinessMethod failed. In order to prevent all those bad things we have to manage this transaction manually. Let's have in the AM implementation class a couple of utility methods:

//Passivates the AM's state in the passivation storage
private String passivateStateForUndo() {
    String savePoint =
        super.passivateStateForUndo(null, null, PASSIVATE_UNDO_FLAG);
    return savePoint;
  }


//Rollbacks the transaction and restores the AM's state
private void activateStateForUndo(String savePointId) {
      super.activateStateForUndo(savePointId,  ACTIVATE_UNDO_FLAG);   
   }

Let's make use of these helper methods in someBusinessMethod() method:

    public void someBusinessMethod() {
        String spid = passivateStateForUndo();
        try {           
            invokePLSQLProcedure1();            
            modifySomeAttributes();           
            invokePLSQLProcedure2();       
            getDBTransaction().commit();               
        } catch (RuntimeException e) {
            activateStateForUndo(spid);
            throw new JboException(e);
        }
    }

Note, that passivateStateForUndo and activateStateForUndo methods work with savepoints in terms of AM state management only and they don't really work with transaction savepoints in the database. The activateStateForUndo method performs a real rollback in the database, but the AM state (including dirty entity cache) is going to be restored as for the moment when the snapshot has been taken by the passivateStateForUndo method.

That's it!