3.3. JDBC y servlets: pooling de conexiones

JDBC proporciona una librería para acceso a bases de datos relacionales, de forma que podemos acceder a diferentes tipos de bases de datos con la misma sintaxis, proporcionada por Java. En este apartado se asume que el alumno ya conoce JDBC, y sabe cómo conectar con una base de datos (MySQL) y ejecutar sentencias SQL sobre ella y obtener resultados.

Abrir una conexión con una base de datos consume un tiempo que, para consultas cortas, incluso puede ser mayor que el tiempo empleado luego en realizar la consulta en sí. Con esto, puede resultar útil reutilizar un objeto Connection que ya esté previamente abierto, para realizar diversas operaciones secuenciales sobre una base de datos. Sobre este esquema añadimos una funcionalidad más: la eficiencia del sistema puede aumentar mucho si mantenemos varios objetos Connection abiertos, y vamos asignando objetos libres ante nuevas peticiones, y liberando los que ya hayan concluido. Eso es lo que se conoce como un pooling de conexiones.

La siguiente clase, PoolConexiones, gestiona un conjunto de objetos Connection, controlando qué operaciones van terminando para liberar objetos, y reasignarlos ante nuevas peticiones.

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

public class PoolConexiones
{
   // Maximo numero de conexiones permitidas
   int maxConexiones;		
   // Nombre de la base de datos a la que se conecta
   String nombreBD;		
   // Conjunto de conexiones
   Connection[] conexiones;	
   // Conexiones disponibles a true, y ocupadas a false
   boolean[] disponibles;		
	
   // Constructor
	
   public PoolConexiones(int maxConexiones, String nombreBD) 
   throws SQLException
   {
	this.maxConexiones = maxConexiones;
	this.nombreBD = nombreBD;
	conexiones = new Connection[maxConexiones];
	disponibles = new boolean[maxConexiones];

	for (int i = 0; i < maxConexiones; i++)
	{
		conexiones[i] = nuevaConexion();
		disponibles[i] = true;
	}
   }
	
   // Crea una nueva conexion
	
   private Connection nuevaConexion() throws SQLException
   {
	try
	{
		Class.forName("org.gjt.mm.mysql.Driver");
		Connection con = 
			DriverManager.getConnection(
			"jdbc:mysql://localhost/" + 
			nombreBD, "root", "mysql");
		return con;
	} catch (Exception e) {
		throw new SQLException ("Error con driver");
	}
   } 
	
   // Obtiene una conexion libre, 
   // o devuelve null si no hay ninguna
	
   public synchronized Connection conexionLibre()
   {
	for (int i = 0; i < maxConexiones; i++)
		if (disponibles[i])
		{
			disponibles[i] = false;
			return conexiones[i];
		}
	return null;
   }
	
   // Libera una conexion
	
   public synchronized void liberaConexion(Connection conexion)
   {
	for (int i = 0; i < maxConexiones; i++)
		if (conexiones[i].equals(conexion))
		{
			try
			{
			   if (conexiones[i].isClosed())
			      conexiones[i] = nuevaConexion();
			   disponibles[i] = true;
			} catch (Exception ex) {}
			return;
		}
   }
	
   // Cierra una conexion
	
   public void cierraConexion(int indice)
   {
	Connection con = conexiones[indice];
	try
	{
		if (!con.isClosed())
			con.close();
	} catch (Exception ex) {}
   }	
	
   // Obtiene el maximo numero de conexiones
	
   public int getMaxConexiones()
   {
	return maxConexiones;
   }
}

 

 

El siguiente servlet utiliza un objeto de tipo PoolConexiones, creándolo en su método init(). Con él asigna conexiones vacías ante nuevas peticiones de consultas de usuarios.

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

public class ServletPooling extends HttpServlet
{
	PoolConexiones pc;
	
	// Metodo de inicializacion
	
	public void init()
	{
		try
		{
			pc = new PoolConexiones (30, "prueba");
		} catch (Exception ex) {
			pc = null;
		}
	}
	
	// Metodo para GET
	
	public void doGet(HttpServletRequest request, 
			  HttpServletResponse response) 
	throws ServletException, IOException
	{
		response.setContentType("text/html");
		
		PrintWriter out = response.getWriter();
		
		if (pc == null)
		{
			out.println ("Error con el pooling");
			return;
		}

		try
		{	
			// Obtener una conexión libre
	
			Connection con = null;
				
			do
			{
				con = pc.conexionLibre();
			} while (con == null);
	
			// Realizar una sentencia SQL y obtener 
			// los resultados

			Statement stmt = con.createStatement();

			ResultSet rs = 
			 stmt.executeQuery("SELECT * FROM datos");

			out.println ("<HTML>");
			out.println ("<BODY>");
			out.println ("Listado de nombres:");
			out.println ("<BR><BR>");
			
			while (rs.next())
			{
			   out.println (rs.getString("nombre"));
			   out.println ("<BR>");
			}

			out.println ("</BODY>");
			out.println ("</HTML>");

			pc.liberaConexion(con);		
			
		} catch (Exception ex) {
			out.println ("ERROR: " + ex.getMessage());
		}		
	}
	
	// Metodo de finalizacion
	
	public void destroy()
	{
		for (int i = 0; i < pc.getMaxConexiones(); i++)
		{
			pc.cierraConexion(i);
		}
	}
}

Ante cada petición, el servlet obtiene una conexión libre del pooling (espera hasta encontrarla en el bucle do...while), y con ella obtiene todos los campos nombre de la tabla datos, mostrándolos en la página.

Aquí se tiene un WAR con una aplicación que utiliza este servlet, y aquí un ZIP con la base de datos de ejemplo.

La base de datos se llama prueba, y tiene una sola tabla datos, con un campo nombre (que es el que se busca en el servlet) y uno descripcion. Se tienen 2000 registros insertados

En la aplicación, se tiene el servlet ServletPooling (que crea un pooling de 30 conexiones), y una variante, ServletPooling2, que crea un pooling con una sola conexión. La página index.html utiliza el primer servlet para realizar 200 peticiones, y la página index2.html utiliza el segundo para lo mismo. La diferencia de tiempos que se estimó es muy importante:

30 conexiones 65 segundos
1 conexión 185 segundos

Podéis probar el ejemplo, siguiendo estos pasos:

mysql -uroot -pmysql < prueba.sql
mysql -uroot -pmysql
GRANT ALL PRIVILEGES ON prueba.* to 
'root'@'localhost.localdomain' 
identified by 'mysql';

FLUSH PRIVILEGES;
http://localhost:8080/ejemplopool/index.html
http://localhost:8080/ejemplopool/index2.html