Encapsulate connections

A database connection can be obtained in different ways. One should hide this design decision in a utility class, such that changes to this decision will have minimal ripple effects. Another important benefit is elimination of code repetition.

Example

This is taken from the WEB4J example application. Here, a Connection is obtained from a JNDI DataSource configured on the server. An alternate implementation might obtain a Connection directly from the JDBC driver. (Some modern drivers include a built-in connection pool.)

This implementation is slightly unusual in that it can return a Connection for more than one database.

package hirondelle.web4j.config;

import java.util.*;
import java.util.logging.*;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import hirondelle.web4j.database.DAOException;
import hirondelle.web4j.database.ConnectionSource;
import hirondelle.web4j.util.Util;
import javax.servlet.ServletConfig;

/** 
 Implementation of {@link ConnectionSource}, required by WEB4J. 
 
 <P>This implementation uses a <tt>Connection</tt> pool managed by the container.
 This class is non-final only since it is convenient for 
 {@link hirondelle.fish.test.doubles.FakeConnectionSrc}.
 Only one method can be overridden - {@link #getConnectionByName(String)}. 
*/
public class ConnectionSrc implements ConnectionSource  {

  /** Read in connection strings from <tt>web.xml</tt>. */
  public final void init(ServletConfig aConfig){
    fDefaultDbConnString = aConfig.getInitParameter(DEFAULT_CONN_STRING);
    fAccessControlDbConnectionString = 
      aConfig.getInitParameter(ACCESS_CONTROL_CONN_STRING)
    ;
    fTranslationDbConnectionString = aConfig.getInitParameter(TRANSLATION_CONN_STRING);
    ensureAllSettingsPresent();
    
    fMapNameToConnectionString = new LinkedHashMap<String, String>();
    fMapNameToConnectionString.put(DEFAULT, fDefaultDbConnString);
    fMapNameToConnectionString.put(ACCESS_CONTROL, fAccessControlDbConnectionString);
    fMapNameToConnectionString.put(TRANSLATION, fTranslationDbConnectionString);
    fLogger.config(
      "Connection strings : " + Util.logOnePerLine(fMapNameToConnectionString)
    );
  }

  /**
   Return value contains only {@link #DEFAULT}, {@link #ACCESS_CONTROL}, 
   and {@link #TRANSLATION}.
  */
  public final Set<String> getDatabaseNames(){
    return Collections.unmodifiableSet(fMapNameToConnectionString.keySet()); 
  }
  
  /**
   Return a {@link Connection} for the default database.
  */
  public final Connection getConnection() throws DAOException {
    return getConnectionByName(DEFAULT);
  }

  /**
   Return a {@link Connection} for the identified database.
   
   @param aDatabaseName one of the values {@link #DEFAULT}, 
   {@link #TRANSLATION}, or {@link #ACCESS_CONTROL}.
  */
  public final Connection getConnection(String aDatabaseName) throws DAOException {
    return getConnectionByName(aDatabaseName);
  }
  
  /** 
   Name used to identify the default database. The default database is 
   the main database, carrying core business data. It is the data that is 
   most often accessed. 
  */
  public static final String DEFAULT = "DEFAULT";
  
  /** Name used to identify the access control database (users, roles, etc.).*/
  public static final String ACCESS_CONTROL = "ACCESS_CONTROL";
  
  /** Name used to identify the translation database.  */
  public static final String TRANSLATION = "TRANSLATION";

  /** 
   This method can be overridden by a subclass.
   Such overrides are intended for testing. 
  */ 
  protected Connection getConnectionByName(String aDbName) throws DAOException {
    Connection result = null;
    String dbConnString = getConnectionString(aDbName);  
    if( ! Util.textHasContent(dbConnString) ){
      throw new IllegalArgumentException(
        "Unknown database name : " + Util.quote(aDbName)
      );      
    }
    try {
      Context initialContext = new InitialContext();
      if ( initialContext == null ) {
        fLogger.severe(
          "DataSource problem. InitialContext is null. Db : " + Util.quote(dbConnString)
        );
      }
      DataSource datasource = (DataSource)initialContext.lookup(dbConnString);
      if ( datasource == null ){
        fLogger.severe("Datasource is null for : " + dbConnString);
      }
      result = datasource.getConnection();
    }
    catch (NamingException ex){
      throw new DAOException(
        "Config error with JNDI and datasource, for db " + Util.quote(dbConnString), ex
      );
    }
    catch (SQLException ex ){
      throw new DAOException(
        "Cannot get JNDI connection from datasource, for db " + Util.quote(dbConnString), 
        ex
      );
    }
    return result;
  }
  
  /**
   This item is protected, in order to make it visible to any subclasses created 
   for testing outside of the normal runtime environment.
  */
  protected String getConnectionString(String aDbName){
    return fMapNameToConnectionString.get(aDbName);
  }
  
  // PRIVATE
  
  /**
   Maps the database name passed to {@link #getConnection(String)} to the 
   actual connection string.
  */
  private static Map<String, String> fMapNameToConnectionString;

  private static final String DEFAULT_CONN_STRING = "DefaultDbConnectionString";
  private static String fDefaultDbConnString;
  
  private static final String ACCESS_CONTROL_CONN_STRING =  
    "AccessControlDbConnectionString"
  ;
  private static String fAccessControlDbConnectionString;
  
  private static final String TRANSLATION_CONN_STRING = "TranslationDbConnectionString";
  private static String fTranslationDbConnectionString;
  
  private static final Logger fLogger = Util.getLogger(ConnectionSrc.class);
  
  private static void ensureAllSettingsPresent(){
    if( ! Util.textHasContent(fDefaultDbConnString) ) {
      logError(DEFAULT_CONN_STRING);
    }
    if( ! Util.textHasContent(fTranslationDbConnectionString) ) {
      logError(TRANSLATION_CONN_STRING);
    }
    if ( ! Util.textHasContent(fAccessControlDbConnectionString) ) {
      logError(ACCESS_CONTROL_CONN_STRING);
    }
  }
  
  private static void logError(String aSettingName){
    fLogger.severe("Web.xml missing init-param setting for " + aSettingName);
  }
}
  
 

See Also :
Connection pools
A Web App Framework WEB4J