7.1 Grupo de conexiones

Hoy aprenderemos a trabajar con la base de datos de manera aún más profesional. Y ahora hablaremos del thread pool, o pool de conexiones en inglés.

La conexión a la base de datos lleva algún tiempo. Especialmente si la base de datos es remota. Si hacemos una conexión a la base de datos para cada solicitud, la respuesta de nuestra aplicación será increíblemente lenta. Sin mencionar los recursos que consumirá.

Como solución a tales problemas, se propuso almacenar las conexiones a la base en algún conjunto, que se denomina conjunto de subprocesos.

Cuando solicitamos una nueva conexión, el pool de conexiones la crea, y cuando se cierra, no la cierra, sino que la guarda en el pool de conexiones. Y si volvemos a solicitar una conexión del grupo de conexiones, nos dará una de las antiguas en lugar de crear una nueva.

De hecho, la aplicación funciona a través de un controlador especial, que es un envoltorio para un controlador JDBC normal y que tiene una funcionalidad adicional para trabajar con el grupo. Esta situación se puede representar de la siguiente manera:

grupo de conexiones

El programador puede configurar manualmente la configuración del conjunto de conexiones: el número de conexiones activas, el tiempo de espera, etc.

Para situaciones especialmente extremas, puede escribir su propio conjunto de conexiones: una clase que tendrá una lista de conexiones. Anulará la función de cierre, lo que devolverá la conexión a la lista, y habrá muchas otras ventajas, como el temporizador de conexión abierta. Cuando no hay conexión durante mucho tiempo, la conexión se cierra.

7.2* Interfaz DataSource y ConnectionPoolDataSource

El grupo de subprocesos suele funcionar junto con el objeto DataSource. Este objeto se puede considerar como una abstracción de una base de datos remota. El mismo nombre Fuente de datos se puede traducir literalmente como Fuente de datos. Lo cual es una especie de insinuación.

Los objetos DataSource se utilizan para obtener una conexión física a una base de datos y son una alternativa a DriverManager. No es necesario registrar el controlador JDBC. Solo necesita configurar los parámetros apropiados para establecer una conexión yejecutar el método getConnection().

Al crear un objeto del tipo DataSource localmente (directamente en la aplicación), los parámetros de conexión se establecen mediante los métodos adecuados proporcionados por el proveedor del controlador JDBC. Estos métodos no están definidos por la interfaz DataSource, ya que los parámetros para conectarse a DBMS de diferentes proveedores pueden diferir tanto en tipo como en número (por ejemplo, no todos los DBMS requieren que se especifique el tipo de controlador o protocolo de red).

Así, cuando se trabaja con el DBMS de Oracle, para utilizar los métodos set y get correspondientes, es necesario obtener un objeto del tipo OracleDataSource, que es una instancia de la clase del mismo nombre que implementa la interfaz DataSource. Por lo tanto, esta forma de crear objetos del tipo DataSource hace que su código sea menos portátil y menos dependiente de la implementación específica del controlador.

El siguiente es un ejemplo de código que ilustra el uso local de objetos de tipo DataSource.

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;

public class DataSource {
    public static void main(String[] args) {
    	try {
        	OracleDataSource ods = new OracleDataSource();

        	ods.setUser("root");
        	ods.setPassword("secret");
        	ods.setDriverType("thin");
        	ods.setDatabaseName("test");
        	ods.setServerName("localhost");
        	ods.setPortNumber(1521);

        	Connection connection = ods.getConnection();
        	System.out.println("Connection successful!!!");

    	} catch (SQLException se) {
        	se.printStackTrace();
    	}
    	System.out.println("Goodbye!");
	}
}

7.3* Interfaz JNDI

Las capacidades completas de los objetos de tipo DataSource se manifiestan en combinación con el uso de la interfaz JNDI. JNDI es un servicio especial (algo así como un directorio) para aplicaciones de servidores grandes que permite que un servicio establezca una conexión con otro.

El uso del servicio de nombres y directorios le permite almacenar objetos del tipo DataSource creados previamente por el administrador del sistema con parámetros de conexión predefinidos. Los siguientes son algunos de los nombres de propiedades estándar (parámetros) desarrollados por Sun:

Nombre de la propiedad Tipo de datos Java Objetivo
nombre de la base de datos Cadena Nombre de la base de datos
nombre del servidor Cadena Nombre del servidor
usuario Cadena Nombre de usuario / ID)
contraseña Cadena Contraseña de usuario
número de puerto En t Número de puerto del servidor DBMS

La combinación de interfaces DataSource y JNDI juega un papel clave en el desarrollo de aplicaciones empresariales de varios niveles basadas en la tecnología de componentes J2EE.

Si usa una combinación de las interfaces DataSource y JNDI en el código de su aplicación, solo necesita solicitar un objeto de tipo DataSource del servicio de nombres y directorios. En este caso, los detalles de la conexión y el código del programa que dependen de una implementación particular del controlador se ocultan de la aplicación en el objeto almacenado en el servicio de nombre y directorio.

Así, la compartición de objetos de tipo DataSource y JNDI implica dos pasos que se realizan de forma independiente:

  1. Debe almacenar el objeto con nombre de tipo DataSource en el servicio de nombres y directorios mediante Context.bind()la extensión javax.naming.
  2. Solicite un objeto de tipo DataSource del servicio de nombres y directorios en la aplicación utilizando el Context.lookup(). Después de eso, puede usar su método DataSource.getConnection()para obtener una conexión a la base de datos.

El siguiente es un ejemplo del uso de la interfaz JNDI y un objeto de tipo OracleDataSource juntos.

// Create a key JNDI object
Hashtable env = new Hashtable();
env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
Context ctx = new InitialContext(env);

// Get the DataSource object by its name
DataSource ods = (DataSource) ctx.lookup("myDatabase");

// Get the Connection from the DataSource object
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");

No analizaremos el trabajo de JNDI. Esto está fuera del alcance de este curso. Solo quiero que sepa que este enfoque es muy común en aplicaciones grandes. No se pierda si ve un código como este en el futuro.

No importa cómo funciona. Solo necesita saber que con un servicio JNDI puede obtener el objeto proxy de cualquier servicio registrado en el directorio de servicios. Así es como obtiene el objeto DataSource y lo usa para trabajar con la base de datos.

7.4 Ejemplos de conjuntos de conexiones

Volvamos a donde empezamos: grupos de conexiones.

Los programadores de Java en sus programas a menudo usan bibliotecas que contienen varias implementaciones de grupos de conexiones. Entre ellos hay tres más populares:

  • Apache Commons DBCP
  • C3PO
  • HikariCP

El proyecto Apache fue el primero en crear un buen grupo de conexiones, también se usa dentro del servidor web Tomcat. Un ejemplo de trabajo con él:

public class DBCPDataSource {

    private static BasicDataSource ds = new BasicDataSource();
    static {
    	ds.setUrl("jdbc:h2:mem:test");
    	ds.setUsername("user");
    	ds.setPassword("password");
    	ds.setMinIdle(5);
    	ds.setMaxIdle(10);
    	ds.setMaxOpenPreparedStatements(100);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private DBCPDataSource(){ }
}

El segundo pool muy popular es C3PO . Nombres extraños, estoy de acuerdo. El nombre C3PO es una referencia al robot c3po de Star Wars. Y también CP es la abreviatura de Connection Pool.

public class C3p0DataSource {

    private static ComboPooledDataSource cpds = new ComboPooledDataSource();
    static {
    	try {
        	cpds.setDriverClass("org.h2.Driver");
        	cpds.setJdbcUrl("jdbc:h2:mem:test");
        	cpds.setUser("user");
        	cpds.setPassword("password");
    	} catch (PropertyVetoException e) {
            // handle the exception
    	}
    }

    public static Connection getConnection() throws SQLException {
    	return cpds.getConnection();
    }

    private C3p0DataSource(){}
}

La documentación para ello se puede encontrar en la página oficial .

Y, por último, el grupo de conexiones más popular en nuestro tiempo es HikariCP :

public class HikariCPDataSource {

    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;

    static {
    	config.setJdbcUrl("jdbc:h2:mem:test");
    	config.setUsername("user");
    	config.setPassword("password");
    	config.addDataSourceProperty("cachePrepStmts", "true");
    	config.addDataSourceProperty("prepStmtCacheSize", "250");
    	config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    	ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private HikariCPDataSource(){}
}

Aquí está su página oficial de GitHub .

Y un buen artículo sobre configuración.