Template method

Template methods: Example

TxTemplate is an abstract base class which defines a template method for executing multiple database operations within a transaction. It's useful to define these steps in one place. The alternative is to repeat the same structure every time a transaction is required. As usual, such code repetition should always be aggressively eliminated.

The executeTx method is the template method. It's final, and defines the general outline of how to execute a database transaction. The specific database actions to be taken are implemented by calling the abstract method executeMultipleSqls.


import java.sql.*;
import java.util.logging.*;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.Consts;

/** 
* Template for executing a local, non-distributed transaction versus a 
* single database, using a single connection.
*
* <P>This abstract base class implements the template method design pattern.
*/
public abstract class TxTemplate implements Tx {
  
  //..elided
  
  /**
  * <b>Template</b> method calls the abstract method {@link #executeMultipleSqls}.
  * <P>Returns the same value as <tt>executeMultipleSqls</tt>.
  *
  * <P>A <tt>rollback</tt> is performed if <tt>executeMultipleSqls</tt> fails.
  */
  public final int executeTx() throws DAOException {
    int result = 0;
    fLogger.fine(
      "Editing within a local transaction, with isolation level : " + fTxIsolationLevel
    );
    ConnectionSource connSource = BuildImpl.forConnectionSource();
    if(Util.textHasContent(fDatabaseName)){
      fConnection = connSource.getConnection(fDatabaseName);
    }
    else {
      fConnection = connSource.getConnection();
    }
    
    try {
      TxIsolationLevel.set(fTxIsolationLevel, fConnection);
      startTx();
      result = executeMultipleSqls(fConnection);
      endTx(result);
    }
    catch(SQLException rootCause){
      fLogger.fine("Transaction throws SQLException.");
      rollbackTx();
      String message = 
        "Cannot execute edit. ErrorId code : " +  rootCause.getErrorCode() + 
        Consts.SPACE + rootCause
      ;
      if (rootCause.getErrorCode() == DbConfig.getErrorCodeForDuplicateKey().intValue()){
        throw new DuplicateException(message, rootCause);
      }
      throw new DAOException(message, rootCause);
    }
    catch (DAOException ex){
      fLogger.fine("Transaction throws DAOException.");
      rollbackTx();
      throw ex;
    }
    finally {
      DbUtil.logWarnings(fConnection);
      DbUtil.close(fConnection);
    }
    fLogger.fine("Total number of edited records: " + result);
    return result;
  }

  /**
  * Execute multiple SQL operations in a single local transaction.
  *
  * <P>This method returns the number of records edited. 
  */
  public abstract int executeMultipleSqls(
    Connection aConnection
  ) throws SQLException, DAOException;
  
  // PRIVATE
  
  private Connection fConnection;
  private String fDatabaseName;
  private final TxIsolationLevel fTxIsolationLevel;
  
  private static final boolean fOFF = false;
  private static final boolean fON = true;
  
  private static final Logger fLogger = Util.getLogger(TxTemplate.class);  

  private void startTx() throws SQLException {
    fConnection.setAutoCommit(fOFF);
  }
  
  private void endTx(int aNumEdits) throws SQLException, DAOException {
    if ( BUSINESS_RULE_FAILURE == aNumEdits ) {
      fLogger.severe("Business rule failure occured. Cannot commit transaction.");
      rollbackTx();
    }
    else {
      fLogger.fine("Commiting transaction.");
      fConnection.commit();
      fConnection.setAutoCommit(fON);
    }
  }
  
  private void rollbackTx() throws DAOException {
    fLogger.severe("ROLLING BACK TRANSACTION.");
    try {
      fConnection.rollback();
    }
    catch(SQLException ex){
      throw new DAOException("Cannot rollback transaction", ex);
    }
  }
}
 


Here's an example of using TxTemplate. It alters the set of roles attached to an end user, first by deleting all existing roles, and then by adding the new roles one at a time.

final class RoleDAO {

  //..elided  

  /**
  * Update all roles attached to a user.
  * 
  * <P>This implementation will treat all edits to user roles as 
  * '<tt>DELETE-ALL</tt>, then <tt>ADD-ALL</tt>' operations. 
  */
  boolean change(UserRole aUserRole) throws DAOException {
    Tx update = new UpdateTransaction(aUserRole);
    return Util.isSuccess(update.executeTx());
  }
  
  // PRIVATE //
  
  /** Cannot be a {@link hirondelle.web4j.database.TxSimple}, since there is looping. */
  private static final class UpdateTransaction extends TxTemplate {
    UpdateTransaction(UserRole aUserRole){
      super(ConnectionSrc.ACCESS_CONTROL);
      fUserRole = aUserRole;
    }
    public int executeMultipleSqls(
      Connection aConnection
    ) throws SQLException, DAOException {
      int result = 0;
      //perform edits using a shared connection
      result = result + DbTx.edit(aConnection, ROLES_DELETE, fUserRole.getUserName());
      for(Id roleId : fUserRole.getRoles()){
        result = result + DbTx.edit(aConnection,ROLES_ADD,fUserRole.getUserName(),roleId);
      }
      return result;
    }
    private UserRole fUserRole;
  }
}
 



See Also :
Consider composition instead of subclassing
Command objects
Wrapper (Decorator)
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -