Pool de conexiones

De ChuWiki
Saltar a: navegación, buscar

Viene de Establecer conexión con base de datos desde java

Problema de las conexiones a base de datos

Cuando trabajamos con java contra una base de datos, es normal encontrar en todos los ejemplos esta forma de obtener un conexión con la base de datos:

import java.sql.Connection;
import java.sql.DriverManager;
...
try
{
   Class.forName("com.mysql.jdbc.Driver");
   Connection conexion = DriverManager.getConnection("jdbc:mysql://localhost/agenda", "root", "LA_PASSWORD");
   ...

Para una aplicación sencilla y más o menos controlada, puede ser adecuado, pero en una aplicación más compleja es mejor utilizar otras formas. Veamos los problemas de este tipo de conexión:

  • Abrir una conexión, realizar cualquier operación con la base de datos y cerrar la conexión puede ser lento. La apertura de las conexiones puede tardar más que la operación que queremos realizar. Es mejor, por tanto, abrir una o más conexiones y mantenerlas abiertas.
  • Mantener una única conexión abierta compartida puede traernos problemas de concurrencia de hilos. Si varios hilos intentan hacer una operación con la conexión sin sincronizarse entre ellos, puede haber conflictos.


Pedirle a alguien las conexiones : javax.sql.DataSource

Para evitar estos problemas, lo mejor es no abrir nosotros directamente la conexión con el DriverManager, sino delegar esta tarea en una clase que implemente la interface javax.sql.DataSource y, por supuesto, elegir una implementación adecuada para nuestros propósitos.

En la API de java no hay ninguna implementación concreta de este DataSource, así que tendremos que buscarla fuera. Normalmente los .jar con los conectores a base de datos, como ojdbc14.jar para Oracle o java-mysql-connector-5.0.5-bin.jar para Mysql suelen tener varias implementaciones disponibles. Es cuestión de revisar la API de ellos y elegir la adecuada.


Pool de conexiones

Una implementación interesante de estos DataSource son los "Pool" de conexiones. Basicamente, estos "pool" nos facilitan conexiones según se las vamos pidiendo, pero las "reaprovechan" de una petición a otra. La idea es la siguiente: Le pedimos al pool una conexión. Este busca una que esté libre y nos la da, apuntando que la tenemos nosotros y que deja de estar libre. Nosotros realizamos nuestra operación (consulta, inserción, borrado, ...) y cerramos la conexión. El pool recibe esta petición de cierre y NO cierra la conexión, sino que la deja abierta y la vuelve a marcar libre para el siguiente que la pida.

Con esta forma de trabajo, el pool mantiene varias conexiones abiertas que va sirviendo y marcando como usadas según se le piden. Cuando desde fuera se cierran esas conexiones, el pool no las cierra y las marca como libres, para poder reaprovecharlas.

apache commons-bdcp: BasicDataSource

Un pool de estas características es BasicDataSource de Apache Commons BDCP. Este pool es además es configurable para que compruebe si la conexión es correcta antes de servirla al que se la pida, para que las verifique cada cierto tiempo, etc.

Vamos a ver un ejemplo con este pool. En primer lugar, necesitamos descargarnos los jar de :

o bien, si trabajamos con maven, nos bastará con añadir la depedencia en el pom.xml y maven se encargará de "tirar del hilo" y bajarse el resto de los jar.

   ...
   <dependency>
      <groupId>commons-dbcp</groupId>
      <artifacId>commons-dbcp</artifactId>
      <version>1.2.2</version>
      <scope>compile</scope>
   </dependency>
   ...


Uso por defecto de BasicDataSource

El código java es también sencillo. Debemos, en primer lugar, crear el pool, que es lo que deberemos pasar al resto del código, como un DataSource.

import javax.sql.DataSource; // Este es propio de java
import org.apache.commons.dbcp.BasicDataSource;  // Este es específico de Apache
...
BasicDataSource basicDataSource = new BasicDataSource();
// Ejemplo con base de datos MySQL
basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
basicDataSource.setUrl("jdbc:mysql://localhost:3306/nombre_bd");
basicDataSource.setUsername("usuario");
basicDataSource.setPassword("password");

// Pasamos el DataSource a las clases que lo necesiten.
claseQueHaceLasConsultas.setDataSource(basicDataSource);

La clase que haga las consultas debería tener un método setDataSource(DataSource), para recibir este pool de conexiones. DataSource es una interface propia de java, por lo que no estamos metiendo en esa clase ninguna depedencia de Apache commons. Un código simple para esa clase podría ser este

import javax.sql.DataSource;

public class ClaseQueHaceLasConsultas() {

   /** El DataSource */
   private DataSource dataSource=null;

   /** Recibe y guarda el DataSource */
   public void setDataSource (DataSource dataSource) {
      this.dataSource = dataSource;
   }

   /** Pide la Connection al DataSource, hace la consulta y
     * cierra la conexión */
   public void hazConsulta () {
      
      Connection conexion = null;
      try {
         conexion = dataSource.getConnection();
         // realización de la consulta
      } catch (Exception e) {
         // tratamiento de error
      } finally {
         if (null != conexion)
            conexion.close();
      }
   }
}

Es importante acordarse de cerrar la conexión después de haberla usado porque si no, el pool pensará que todavía la necesitamos y la mantendrá reservada todo el tiempo. Por ello, es buena costumbre poner el close() en el finally del try-catch, de forma que se cierre siempre, vaya bien o mal la consulta.


Configuración adicional

En la API de BasicDataSource vemos que hay infinidad de métodos que nos permiten configurar el comportamiento del BasicDataSource. Algunas de ellas son las siguientes:

Número de conexiones reales

  • setMaxActive() : Número máximo de conexiones que se pueden abrir simultáneamente.
  • setMinIdle() : Número mínimo de conexiones inactivas que queremos que haya. Si el número de conexiones baja de este número, se abriran más.
  • setMaxIdle() : Número máximo de conexiones inactivas que queremos que haya. Si hay más, se irán cerrando.
  • setInitialSize() : Número de conexiones que se quiere que se abran en cuanto el pool comienza a trabajar (se llama por primera vez a getConnection(), setLogwriter(), setLoginTimeout(), getLoginTimeout() o getLogWriter(). Debe llamarse a setInitialSize() antes de llamar a cualquiera de estos métodos y después no puede cambiarse el valor.

Verificación automática de la conexión

El pool puede verificar que la conexión funciona correctamente cuando se la pasa a alguien, cuando se la devuelven y mientras está inactiva, de forma que intentará la reconexión en caso de fallo. Algunos de los métodos implicados son:

  • setValidationQuery() : SQL a usar con la validación. En MySQL suele ser SELECT 1, en Oracle SELECT 1 FROM DUAL.
  • setTestOnBorrow() : Indica si se debe testear la conexión antes de pasársela a alguien.
  • setTestOnReturn() : Indica si se debe testear la conexión cuando ese alguien la libera.
  • setTextWhileIdle() : Indica si se debe testear la conexión mientras está inactiva. El tiempo entre tests se puede fijar con setTimeBetweenEvictionRunsMillis()

Continúa Transacciones con base de datos

Enlaces externos