4.4. Pool de conexiones#
Abrir una conexión a la base de datos es un proceso costoso en recursos por lo que, si prevemos que nuestra aplicación abrirá y cerrará varias conexiones, es conveniente utilizar un pool de conexiones, que no es más que un mecanismo que se encarga de administrar un grupo de conexiones a una base de datos a fin de que puedan ser reutilizadas por diferentes partes de una aplicación. Esto ahorra al programa el coste de la creación y establecimiento de conexiones.

En la figura el pool de conexiones tiene abiertas tres conexiones a las bases de datos, dos de las cuales están siendo usados en la aplicación. Esto significa que aún hay una libre y que, si ésta necesitara otra, podría usarla sin necesidad de establecer una nueva conexión con la base de datos.
Para utilizar este mecanismo tenemos dos vías:
Usar el mecanismo básico que proporciona JDBC y que puede servirnos cuando no haya gran concurrencia ni necesitamos controlar todos los parámetros del pool.
Usar una librería especializada como HikariCP.
4.4.1. Gestor integrado#
Los controladores citados para distintas bases de datos disponen de un gestor integrado de pools de conexiones, aunque su uso puede diferir ligeramente entre ellos. Por lo demás, es sencillo de usar:
Path dbPath = Path.of(System.getProperty("java.io.tmpdir"), "test.db");
String dbUrl = String.format("%s%s", dbProtocol, dbPath);
SQLiteConnectionPoolDataSource ds = new SQLiteConnectionPoolDataSource();
ds.setUrl(dbUrl); // No hay que definir usuario ni contraseña.
try(Connection conn = ds.getConnection()) {
// Utilizamos la conexión.
}
try(Connection conn = ds.getConnection()) {
// Posiblemente se reutilice la conexión anterior,
// que se marcó como inactiva, al cerrarse.
}
Aclaración
En el código anterior los dos objetos DataSource generan dos objetos Connection distintos. Sin embargo, es más que probable que ambos estén utilizando, en realidad, la misma conexión a la base de datos, ya que se crean a partir de un pool de conexiones y al crear el segundo objeto, el primero ya se cerró y, por tanto, ha dejado disponible la conexión en el pool.
En cambio, si se hubieran generado directamente con DriverManager, ambos objetos conectores estarían asociados a dos conexiones distintas.
Advertencia
Y, sin embargo, lo anterior no es cierto a consecuencia de un bug del controlador de SQLite, que provoca que siempre se abra una nueva conexión, sin reaprovechar las ya establecidas. La alternativa, que es:
PooledConnection pc = ds.getPooledConnection();
try(Connection conn = pc.getConnection()) {
// ...
}
try(Connection conn = pc.getConnection()) {
// ...
}
tampoco funciona, porque el controlador nunca creará una segunda conexión, aunque sea necesaria porque el primer objeto Connection sigue activo, sino que cierra el primer objeto para aprovechar la conexión con el segundo objeto, es decir, que no es capaz de gestionar más que una conexión a la base de datos. En el ejemplo no se aprecia el error porque se cierra el primer objeto antes de crear el segundo, pero si el código fuera este:
try(Connection conn1 = pc.getConnection()) {
// Aquí podemos usar conn1.
try(Connection conn2 = pc.getConnection()) {
// Aquí no podremos usar conn1.
}
// Ni aquí tampoco.
}
al crearse el objeto conn2
, conn1
se cerrará y quedará inútil. El
bug, no obstante, es un defecto del controlador para SQLite. El
código equivalente para otros SGBD sí debería funcionar correctamente.
4.4.2. HikariCP#
La alternativa, que es común a cualquier controlador, es usar una librería especializada como HikariCP, que tiene repositorio de Maven.
Nota
Con esta librería no tendremos problemas al utilizar un pool de conexiones con SQLite.
Su uso, por otro lado es muy sencillo:
Path dbPath = Path.of(System.getProperty("java.io.tmpdir"), "test.db");
String dbUrl = String.format("%s%s", dbProtocol, dbPath);
// Configuramos el acceso.
HikariConfig hconfig = new HikariConfig();
hconfig.setJdbcUrl(url);
// En SQLite no hay credenciales de acceso.
hconfig.setUsername(null);
hconfig.setPassword(null);
// Máximo y mínimo de conexiones
hconfig.setMaximumPoolSize(10); // Nunca se abrirán más de diez conexiones.
hconfig.setMinimumIdle(1); // Al menos habrá una conexión.
HikariDataSource ds = new HikariDataSource(hconfig);
HikariPoolMXBean stats = ds.getHikariPoolMXBean(); // Para consultar estadísticas.
// Como el mínimo es una conexión, ya hay una conexión creada.
System.out.println(String.format("Conexiones activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 0/1
try(Connection conn1 = ds.getConnection()) {
// ...
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 1/1
}
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 0/1
try(Connection conn1 = ds.getConnection()) {
// ...
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 1/1
try(Connection conn2 = ds.getConnection()) { // Crea una conexión nueva.
// ...
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 2/2
}
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 1/2
}
System.out.println(String.format("activas/totales: %d/%d", stats.getActiveConnections(), stats.getTotalConnections())) // 0/2
ds.close(); // Se liberan recursos.