Al comienzo de este curso de JDBC, te contaba que mediante el método estático getConnection() de la clase DriverManager puedes conectarte con una base de datos.
Sin embargo, la realidad es que esta manera de conectarte con una base de datos solo las vas a utilizar si haces prototipos o programas pequeños en el lenguaje de programación Java, y que ni de lejos se parece a la manera en la que se usa esta tecnología en la vida real.
Si has trabajado con Spring, Quarkus y otro tipo de aplicaciones web, te habrás dado cuenta que para hablar con tu base de datos no se utiliza directamente DriverManager.getConnection(). Es muy probable que en su lugar utilices JPA o algo parecido. En su lugar, en la vida real se utilizan DataSource.
¿Por qué no se usa DriverManager.getConnection?
El método getConnection() funciona correctamente y como su nombre indica: cuando lo invocas, JDBC va a levantar una conexión contra tu base de datos, con los criterios de conexión que le hayas especificado. Poniéndole el usuario, la contraseña y el resto de propiedades. Cuando llames a close() dentro de una instancia de Connection, se cerrará la conexión.
Computacionalmente, esto tiene un coste. Y en aplicaciones web, donde va a ser frecuente que se reciban peticiones web de forma concurrente, que deben ser despachadas lo más rápido posible, esto va a ser un problema. Para empezar porque va a malgastarse tiempo levantando conexiones. Y además, porque la propia base de datos tiene límites. Si tu endpoint se vuelve popular y acabas recibiendo cientos de peticiones por minuto, eso levantará cientos de conexiones por minuto, y en algún momento el servidor agotará sus recursos.
Por otra parte, tirar una conexión al final de una petición web con close(), sabiendo que dentro de un segundo vas a tener que montar otra conexión para la siguiente petición es un derroche de recursos.
Por eso, cuando hagas un proyecto real, lo normal será que quieras conservar tus conexiones en ejecución durante el máximo tiempo posible, haciendo que éstas sobrevivan a las peticiones web, para poder reutilizarlas más adelante. Y para ello, una de las formas más habituales de conservar estas peticiones, es mediante el uso del patrón object pool.
Un breve resumen de Object Pool
Aunque este no es el curso para hablar de object pool, os describiré en qué consiste este patrón de diseño, que es el que se está utilizando aquí.
Un object pool es un patrón pensado para conservar y reutilizar recursos que son caros de inicializar, y que por lo tanto interesa reciclar si puede ser.
Un object pool es una zona de memoria con espacio para múltiples instancias de un mismo recurso compartido. En nuestro caso, será un object pool de conexiones a bases de datos, pero este tipo de patrones se usan en muchos lugares de la informática. Por ejemplo, en videojuegos también es muy común para entidades auxiliares y modelos 3D, como partículas o decorados.

Dentro de esa zona de memoria, residirán instancias listas para utilizar. Por ejemplo, al arrancar la aplicación, el sistema levanta 5 conexiones a la base de datos y las deposita en esa zona de memoria.
Cuando la aplicación necesita una conexión a la base de datos, en vez de instanciar una nueva, accede a esta zona de memoria y se reserva una conexión que esté libre y sin usar. Al reservarla, mientras esté tomada, esa conexión no podrá ser reutilizada por otra petición que se reciba en paralelo. Pero a cambio, el programa puede recibir una conexión mucho más rápido porque no tiene que instanciar una conexión completamente de nuevo, sino que está reciclando una conexión que ya fue inicializada anteriormente.
Cuando el programa ha terminado de usar una conexión, en vez de cerrarla con close(), lo que hace es volcarla en la zona de memoria otra vez, dejándola abierta y sin cerrar, para dejarla libre hasta que otra vez vuelva a ser reclamada como parte de otra petición.
En definitiva, en vez de estar todo el rato levantando y cerrando conexiones, simplemente se toman temporalmente de una zona de memoria y se vuelven a dejar, para que cuando haya múltiples hilos o peticiones siendo gestionadas de forma simultánea, se puedan tomar más rápido.
¿Qué pasa si la aplicación está terriblemente ocupada y una petición intenta solicitar una conexión a base de datos cuando todas están ocupadas? En ese caso, tendrá que decidirse entre esperar a que se libere una conexión, algo que puede ahorrar recursos pero a cambio provocar que una petición tarde más de lo normal en gestionarse, porque ya depende de que termine otra; o bien puede pedirle al object pool que se redimensione y que prepare nuevas instancias.
De este modo, un object pool dinámico puede decidir que si el endpoint se ha vuelto terriblemente congestionado por la alta cantidad de tráfico, pase de conservar 5 a 10 conexiones abiertas. De ahí si continúa creciendo, puede subir a 15, a 20... En algún momento habrá que poner el límite, pero sin duda resulta mucho más flexible.
Y cuando el object pool detecte que ya no se están solicitando tantas conexiones como antes, puede empezar a liberarlas de verdad, para ahorrar recursos.
DataSource
En Java, un DataSource será una interfaz alternativa para obtener conexiones a una base de datos.
Es una interfaz muy sencilla con dos métodos:
public interface DataSource {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
Cuando llames al método getConnection() te va a entregar una conexión con la base de datos.
Sin embargo, a diferencia de lo que harías con DriverManager, un DataSource normalmente irá conectado a un object pool. Eso significa que al llamar a getConnection(), lo normal es que se recupere una conexión que esté libre en el pool, en vez de crear una sobre la marcha.
Además, las conexiones que devuelve el DataSource tienen cambiado el comportamiento del método close(), para que en vez de cerrarlas de verdad, simplemente vuelva a colocar la instancia en el object pool, dejándola disponible para el siguiente invocador que llame al método getConnection().
Hikari: el DataSource más rápido para aplicaciones básicas
HikariCP es un data source sencillo y ligero que implementa object pool para facilitar la conexión con una base de datos en una aplicación real.
Puedes instalar Hikari en tu proyecto agregando sus coordenadas de Maven:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>6.3.0</version>
</dependency>
O las de Gradle,
implementation 'com.zaxxer:HikariCP:6.3.0'
O como más te guste instalar JARs en Java.
Una vez instalado, hay que definir un data source. Para ello, tendrás que fabricar primero su configuración. Tienes dos formas de hacer esto.
Lo más directo para empezar a prototipar sería instanciar un HikariDataSource y establecerle los parámetros de configuración con setters. El manual lo explica bien, no me estoy inventando nada:
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgres://localhost:5432/clinica_veterinaria");
ds.setUsername("clinica");
ds.setPassword("guau_guau");
Con tu DataSource instanciado, ahora puedes hacer llamadas a getConnection() para recuperar conexiones a través de este pool.
Formas alternativas de configurar un DataSource
Lo malo del ejemplo que te he puesto antes es que para hacer cambios tendrás que modificar el código Java. Por eso, en aplicaciones reales tal vez te interese otra forma de configurarlo. HikariCP permite leer los parámetros de configuración directamente desde un archivo .properties. Tal y como está documentado en su manual, esto te permitiría declarar un archivo con los criterios de configuración:
dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
dataSource.serverName=localhost
dataSource.portNumber=5432
dataSource.databaseName=clinica_veterinaria
dataSource.user=clinica
dataSource.password=guau_guau
Y luego crear el DataSource a partir de este archivo:
var config = new HikariConfig("/database/hikari.properties");
var ds = new HikariDataSource(config);
En el caso de proyectos de Jakarta, por ejemplo, es posible que prefieras utilizar JNDI como forma de configurar la base de datos, sobre todo si te vas a integrar con un servidor de aplicaciones completo.
Además, si vas a usar un framework como Spring o Quarkus, es posible que tu framework ya defina su propia manera de configurar el object pool. En el caso de Spring, la biblioteca Spring DataSource ya te dice cómo conectarte con una base de datos.
Con Quarkus pasa algo parecido. De hecho, Quarkus utiliza Agroal, que es otra alternativa que también implementa javax.sql.DataSource.