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
- Lanzad el cliente MySQL (si no estáis ya dentro)
mysql -uroot -pmysql
- Ejecutad los siguientes comandos, para dar permiso al usuario root sobre la base de datos prueba:
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