Uso de JNDI para utilizar un pool de conexiones

Ramiro Lago (Noviembre 2005, Mayo 2007)

Concepto de pool de conexiones

Una aproximación elemental a JDBC implica que se realiza una conexión a la base de datos en cada servlet. Se repite el esquema conexión-operación-desconexión. Esta forma de trabajar es perfectamente válida, pero resulta ineficiente, ya que se están desperdiciando ciclos de ejecución en cada conexión y desconexión.

En vez de esta orientación hay otra alternativa: crear un pool de conexiones, es decir, mantener un conjunto de conexiones. Cuando un servlet/JSP requiere una conexión la solicita al conjunto de conexiones disponibles (desocupadas, "idle") y cuando no la va a usar la devuelve al pool. De esta forma nos ahorramos el consumo de tiempo de conexión y desconexión. La mayor parte de servidores de aplicaciones (Tomcat, WebSphere, etc.) incluyen clases que implementan un pool de conexiones. Nosotros vamos a poner un ejemplo con MySQL 5.0 y Tomcat 5.5, concretamente con DataBase Common Pooling (DBCP) de Tomcat. El pool debe gestionar situaciones anómalas, como el cierre involuntario de una conexión después de cerrar un ResultSet o un Statement.

A continuación le mostraremos cómo implementar el servlet, pero esto no es problemático. Lo que puede llegar a ser una fuente de dolores de cabeza es la correcta configuración de server.xml teniendo en cuenta que dicha configuración es altamente dependiente de las versiones de base de datos y sobre todo de servidor de aplicaciones.

Definir un DataSource

Ya hemos visto anteriormente (ver) que gracias a JNDI podemos acceder a objetos definidos en web.xml. Ahora vamos a mostrar un servlet que usa JNDI para acceder a un pool de conexiones. El tipo de dato que utilizamos en web.xml es un DataSource, una representación genérica de una fuente de datos.


  <env-entry>
    <env-entry-name>ejemplos/conexion</env-entry-name>
    <env-entry-type>javax.sql.DataSource</env-entry-type>
    <env-entry-auth>Container</env-entry-auth>
  </env-entry>

Debemos tener en CATALINA_HOME/commons/lib (o el CLASSPATH) el driver JDBC: por ejemplo, mysql-connector-java-3.1.6-bin.jar (MySQL)

El método init()

Con el servlet lo primero que hacemos es crear una fuente de datos:


import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import javax.sql.*;
import java.io.*;
import javax.naming.*;

public class server_xml01 extends HttpServlet {

   private DataSource fuenteDatos = null;
   String errorInicial = null;

   /********************************************************************************
    * INIT: creo una fuente de datos (DataSource)
    ********************************************************************************/
   public void init(ServletConfig config) throws ServletException {
      try {
	 Context contextoInicial = new InitialContext(); // Equivalente: new InitialContext(null).
	 Context contexto = (Context) contextoInicial.lookup("java:comp/env");
	 fuenteDatos = (DataSource) contexto.lookup( "ejemplos/conexion");
      }
      catch(NameNotFoundException e) {               // Hija de NamingException
	 errorInicial = new String(e.toString());
      }
      catch(Exception e) {
	 errorInicial = new String(e.toString());
      }
   }
   

En doGet()

El servlet en doGet() solicita una conexión a la fuente de datos por medio de getConnection(). Observar que el objeto Connection es  local al método, ya que lo declaramos para recibir una conexión del pool y luego lo devolvemos al pool. Decimos "devolver" ya que con el uso del pool el programador no cierra conexiones, sino que las devuelve al pool.


   public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      Connection con = null;
      String errorPeticion = null;

      try
      {
	 //// Si no hay error en init(): solicito conexión al pool
	 if ( errorInicial == null ) {
       con = fuenteDatos.getConnection();
	 }
      }
      catch( Exception e) {
	 errorPeticion = new String(e.toString());
      }
      finally {
	 //// Escribo página con resultado de la búsqueda
	 out.println("<html>");
	 out.println("<head><title>Ejemplo de Servlet y JNDI</title></head>");
	 out.println("<body bgcolor=\"#FFFF9D\"><font color=\"#000080\" FACE=\"Arial,Helvetica,Times\" SIZE=2>");
	 out.println("<CENTER><H3>Usa JNDI para recuperar propiedades de web.xml</H3></CENTER><HR>");
	 out.println("<P>init:" + ((errorInicial==null) ? "No hay error" : errorInicial) + "</P>");
	 out.println("<P>request:" + ((errorPeticion==null) ? "No hay error" : errorPeticion) + "</P>");
	 out.println("<p>Conexión: " + ((con==null) ? "No" : con.toString()) + "</p>");

	 //// Realizo consulta
	 out.println("<p>Select:</p><ul>");
	 Statement sentencia = null;
	 ResultSet rs = null;
	 try {
	    sentencia = con.createStatement();
	    rs = sentencia.executeQuery("select * from cliente");

	    while ( rs.next() ) {
	       out.println( "<li>" + rs.getString(1) + ", " + rs.getString(2) + "</li>");
	    }
	    rs.close();
	    sentencia.close();
	    con.close();        // Se devuelve la conexión al pool
	 }
	 catch (Exception e) {
	    out.println( "<li>Error en la consulta</li>");
            //// Asegurar que result sets and statements son cerrados,
	    //// y que la conexión retorna al  pool
	    if (rs != null) {
	       try { rs.close(); } catch (SQLException sqle) {}
	    }
	    if (sentencia != null) {
	       try { sentencia.close(); } catch (SQLException sqle) {}
	    }
	    if (con != null) {
		try { con.close(); } catch (SQLException sqle) {}
	    }
	 }

	 out.println("</ul></font></body></html>");
      }
   }

La consulta no tiene nada de particular. Se crea el objeto Statement a partir de la conexión obtenida del pool. Observar que cerramos (close) el ResulSet, Statement y Connection (aunque en realidad ya hemos dicho que no se destruye la conexión, sino que se devuelve al pool). Una forma segura de devolver la conexión al pool:


    public static void cerrarConexion( Connection con ) {
		try {
		    if ( con != null )
		    	if ( !con.isClosed() )    // Si no está cerrada, la cierro
		    		con.close();
		}
		catch (SQLException e) { e.printStackTrace();  }
    }

Acceso al servlet.

Documentación de Tomcat 5.5: JNDI Datasource HOW-TO

Documentación de Tomcat 5.5: JNDI Resources HOW-TO



Configurar server.xml

A continuación los parámetros de configuración de server.xml. Es importante observar que el orden puede ser relevante en función de la versión de servidor de aplicacicones. Observar que name de ResorceParams debe ser el mismo que el de Resource y a su vez debe coincidir con env-entry-name de web.xml. Las expresiones que ve a continuación se deben de colocar dentro de tu contexto (etiqueta Context) del archivo server.xml


		<Context>
					....
				  	<Resource
				  			name="ejemplos/conexion" 
				  			auth="Container"
				  			type="javax.sql.DataSource"
							maxActive="100"
							maxIdle="30"
							maxWait="10000"
							username="usuario"
							password="pwd"
							driverClassName="com.mysql.jdbc.Driver"
							url="jdbc:mysql://localhost:3306/base_datos?autoReconnect=true" />        
		</Context>

Explicación de algunos parámetros:




Volver al índice