Gestione dei Pool di Connessioni con Java |
| A cura di Davide Lorenzo Marino |
Durante lo sviluppo di applicazioni che accedono ad una base di dati,
vi sarà capitato di notare che stabilire la connessione verso il database
può richiedere anche qualche secondo. Si tratta di un problema rilevante quando si ha a che fare con la progettazione di un sito web, le cui pagine sono costruite dinamicamente accedendo ai dati contenuti in un database. In questo articolo vedremo come superare l'ostacolo.
|
| Problemi di performance durante l'accesso ai database |
Stabilire una connessione con una base di dati è un'operazione molto
complessa. Infatti, è necessario caricare il driver del database,
stabilire una comunicazione bidirezionale sicura fra l'applicazione che
deve accedere ai dati e il database stesso, eseguire una procedura di
autenticazione e allocare le risorse necessarie per gestire le query
successive. Queste operazioni sono spesso nascoste all'occhio dello sviluppatore che stabilisce la connessione con una o due chiamate di funzione. L'esecuzione di questa procedura è molto onerosa e richiede alcuni secondi per essere portata a termine. Questo tempo è del tutto trascurabile nel caso di un'applicazione stand-alone che spesso viene mantenuta in esecuzione per tempi molto lunghi. Nel caso di un'applicazione web le cose cambiano: lo scopo infatti è quello di gestire una singola richiesta, accedere al database e dare la risposta nel tempo minore possibile. Se per ogni richiesta non fosse necessario stabilire una nuova connessione verso il database, i tempi di risposta ne risulterebbero significativamente migliorati; inoltre il server sarebbe meno sottoposto a problemi di sovraccarico, potendo così gestire un maggior numero di visitatori.
|
|
| La soluzione? Un pool di connessioni! |
Esiste una soluzione elegante per ridurre i tempi di connessione al
database nel contesto che è stato descritto precedentemente: è sufficiente
che il client inserisca la connessione in una cache invece di chiuderla
quando siano terminata l'esecuzione di tutte le query. Un altro processo che debba accedere alla base di dati può in tal caso prelevare una connessione esistente dalla cache invece di stabilirne una nuova.
|
|
|
Come è fatto un pool di connessioni?
|
Ora analizziamo una struttura dati ed una serie di funzioni minimali che
ci permettano di gestire un pool di connessioni. Per gestire le connessioni disponibili, la struttura dati migliore è una coda. In tal caso quando un processo deve accedere al database otterrà, prelevandola dalla coda, la connessione più vecchia fra quelle in cache, minimizzando la possibilità di caduta per le connessioni inutilizzate da troppo tempo. Se non ci sono connessioni disponibili, il pool deve essere in grado di stabilirne una nuova. Pertanto deve avere a sua disposizione tutte le informazioni per farlo: generalmente si tratta di conoscere il driver, il login, la password di accesso e il nome del database. In base a quanto detto sopra, il codice che gestisce un pool deve contenere almeno tre funzioni: getConnection per ottenere una nuova connessione prelevandola dalla coda o creandone una nuova se necessario; releaseConnection per liberare una connessione già utilizzata e garantirne il riutilizzo da parte di altri processi, inserendola nella coda delle connessioni disponibili; inoltre poiché se getConnection non trova nessuna connessione disponibile nella coda deve stabilirne una nuova, è necessario aggiungere la funzione newConnection che sia in grado di farlo quando richiesto.
| |
|
Sviluppo passo passo di una classe ConnectionPool in Java
|
Procediamo con lo sviluppo di codice Java in grado di gestire correttamente
un pool di connessioni. Costruiamo innanzitutto una classe ConnectionPoolException, le cui istanze sono le eccezioni che verranno sollevate quando si dovesse presentare un errore a runtime nella classe ConnectionPool.
// Classe che gestisce le eccezioni sollevate a runtime dalla
// classe ConnectionPool
public class ConnectionPoolException extends Exception {
public ConnectionPoolException() {
}
}
Ricordiamo che al pool di connessioni dovranno accedere processi
distinti. E' necessatio quindi trovare un artificio per far si che tutti
i processi accedano ad unica istanza della classe ConnectionPool.L'idea è quella di avere una variabile statica connectionPool all'interno della classe ConnectionPool. Un metodo statico, getConnectionPool, permetterà di accedere a questa variabile da parte dei processi che ne fanno richiesta. Se la variabile non è stata ancora istanziata il metodo getConnectionPool provvederà alla sua istanziazione. Dal momento che più processi accedono a getConnectionPool in concorrenza, definiamo il metodo come synchronized.
...
// La classe che gestisce un pool di connessioni
public class ConnectionPool {
...
// La variabile che gestisce l'unica istanza di ConnectionPool
private static ConnectionPool connectionPool = null;
...
public static synchronized ConnectionPool getConnectionPool()
throws ConnectionPoolException {
if(connectionPool == null) {
connectionPool = new ConnectionPool();
}
return connectionPool;
}
...
}
Il codice di getConnectionPool accede al costruttore della classe per
creare l'unica istanza di ConnectionPool. Questo si preoccupa di creare
la coda per le connessioni libere, caricare i parametri per l'accesso
al database accedendo al metodo loadParameters e caricare il driver
della base di dati con il metodo loadDriver.Osserviamo che il costruttore è privato. In questo modo garantiamo che l'accesso ad esso avvenga solo tramite la funzione pubblica getConnectionPool.
import java.util.*;
...
// La classe che gestisce un pool di connessioni
public class ConnectionPool {
...
private Vector freeConnections; // La coda di connessioni libere
private String dbUrl; // Il nome del database
private String dbDriver; // Il driver del database
private String dbLogin; // Il login per il database
private String dbPassword; // La password di accesso al database
...
// Costruttore della classe ConnectionPool
private ConnectionPool() throws ConnectionPoolException {
// Costruisce la coda delle connessioni libere
freeConnections = new Vector();
// Carica I parametric per l'accesso alla base di dati
loadParameters();
// Carica il driver del database
loadDriver();
}
// Funzione privata che carica i parametri per l'accesso al database
private loadParameters() {
// Url per un database locale
dbUrl = "jdbc:mysql://localhost/prova";
// Driver per database mysql
dbDriver = "org.gjt.mm.mysql.Driver";
// Login della base di dati
dbLogin = "Login";
// Password per l'accesso al database
dbPassword = "Password";
}
// Funzione privata che carica il driver per l'accesso al database.
// In caso di errore durante il caricamento del driver solleva un'eccezione.
private loadDriver() throws ConnectionPoolException {
try {
java.lang.Class.forName(
dbDriver + "?user=" +
dbUser + "&password=" + dbPassword);
} catch (Exception e) {
throw new ConnectionPoolException();
}
}
...
}
Analizziamo la funzione getConnection che restituisce una connessione
libera. getConnection verifica innanzitutto che ci siano elementi
nella coda delle connessioni libere. Se la coda non è vuota, preleva la
prima connessione e verifica che sia valida. Se la connessione non è
più valida effettua una chiamata ricorsiva per prelevare l'elemento
successivo.Se la coda e' vuota, getConnection chiama newConnection per stabilire una nuova connessione. Il metodo getConnection è di tipo synchronized perché può essere richiamato da più processi concorrenti. Il metodo newConnection non deve necessariamente essere synchronized perché viene richiamato da getConnection che già opera in mutua esclusione.
import java.sql.*;
...
// Il metodo getConnection restituisce una connessione libera prelevandola
// dalla coda freeConnections oppure se non ci sono connessioni disponibili
// creandone una nuova con una chiamata a newConnection
public synchronized Connection getConnection()
throws ConnectionPoolException {
Connection con;
if(freeConnections.size() > 0) {
// Se la coda delle connessioni libere non è vuota
// preleva il primo elemento e lo rimuove dalla coda
con = (Connection) freeConnections.firstElement();
freeConnections.removeElementAt(0);
try {
// Verifica se la connessione non è più valida
if(con.isClosed()) {
// Richiama getConnection ricorsivamente
con = getConnection();
}
} catch(SQLException e) {
// Se c'è un errore richiama GetConnection
// ricorsivamente
con = getConnection();
}
} else {
// se la coda delle connessioni libere è vuota
// crea una nuova connessione
con = newConnection();
}
// restituisce la connessione
return con;
}
// Il metodo newConnection restituisce una nuova connessione
private Connection newConnection() throws ConnectionPoolException {
Connection con = null;
try {
// crea la connessione
con = DriverManager.getConnection(dbUrl);
} catch(SQLException e) {
// in caso di errore solleva un'eccezione
throw new ConnectionPoolException();
}
// restituisce la nuova connessione
return con;
}
...
Una volta terminato l'uso della connessione si chiama il metodo
releaseConnection per ritornare la connessione non più utilizzata
nella coda.Questo metodo come getConnection opera in regime di concorrenza e deve quindi essere synchronized.
...
// Il metodo releaseConnection rilascia una connessione inserendola
// nella coda delle connessioni libere
public synchronized void releaseConnection(Connection con) {
// Inserisce la connessione nella coda
freeConnections.add(con);
}
..
|
|
|
Il codice completo
|
Potete scaricare il codice completo da qui'.
|
|
|
Riferimenti
|
Accesso ai database da java: Java database e programmazione client/server - Giuseppe Naccarato - Apogeo Java 2 Tecniche avanzate - Cay S. Horstmann e Gary Cornwell - McGraw Hill
Tecniche per gestire eccezioni e per la programmazione concorrente:
Sviluppo di siti web dinamici tramite servlet:
Accesso a database MySql da codice Java:
|
|
Comments Max length of comments: 1000 chars. |
6 commenti riccardo dice il 22/04/2008 12:30: ottimo e semplice, qualche errore nei sorgenti ma la parte interessante e' la guida lorenzo dice il 12/06/2008 11:44: esistono pacchetti che fanno il pooling delle connessioni perché scriverselo? dbcp in jakarta e c3p0, alcuni orm supportano direttamente il pooling(hibernate) DoktorVis dice il 23/09/2008 19:34: Per alcuni è meglio conoscere come funziona una cosa anziché utilizzarla e basta... buon articolo. stecolna dice il 24/09/2008 09:21: Mi unisco al commento di DoktorVis. Classe snella, adattabile ai vari RDBMS e sopratutto estendibile. W il potere della programmazione ad oggetti!!!! massi dice il 02/10/2008 10:50: molto interessante; ma vorrei chiedervi: nello sviluppo di un progetto ho utilizzato il connection pool di hibernate;ora però ho necessità di utilizzarne un altro, più proprieario; una volta dichiarato come faccio a fare in modo che le portlet utilizzino questo qui? i commenti non sono un forum, leggi la documentazione di quel coso o domanda sul loro forum di supporto. uno qualsiasi dice il 13/11/2008 12:35: Sempre bello farsi le cose da soli ma qui non si tiene conto almeno di due cose fondamentali: quando una connection è fisicamente chiusa (cosa che JAVA non riesce a segnalare, vd documentazione) la si deve reinizializzare; inoltre il metodo DriverManager.getConnection deve lavorare in maniera non sincronizzata, per minimizzare i tempi di attesa: occorre una gestione con threads. Add a comment (max 1000 chars)
|
| Author | |
| Contribuire | Volete contribuire? Leggete come! |
| Copyright | This site is made by me with blood, sweat and gunpowder, if you want to republish or redistribute any part of it, please drop me (or the author of the article if is not me) a mail. |
This site isn't optimized for vision with any specific browser, nor
it requires special fonts or resolution.
You're free to see it as you wish.
Last Update: 04/12/2008