Gestione dei Pool di Connessioni con Java


Home Page | Commenti | Articoli | Faq | Documenti | Ricerca | Archivio | Storie dalla Sala Macchine | Contribuire | Imposta lingua:en it | Login/Register


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.

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.

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.

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.

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);
}
..

Potete scaricare il codice completo da qui'.

 

 

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:

Java la guida completa - Patrick Naughton e Herbert Schildt - Mc Graw Hill
Core Java - Gary Cornell e Cay S. Horstmann - Sunsoft Press
Java Fondamenti di programmazione - Deitel & Deitel - Apogeo

Sviluppo di siti web dinamici tramite servlet:

Java Servlet Programmino - Jason Hunter e Williiam Crawford - O'Reilly

Accesso a database MySql da codice Java:

JSP Servlet e MySql - David Harms - McGraw Hill


I commenti sono aggiunti quando e soprattutto se ho il tempo di guardarli e dopo aver eliminato le cagate, spam, tentativi di phishing et similia. Quindi non trattenete il respiro.

6 messaggi this document does not accept new posts

riccardo

riccardo Di riccardo postato il 22/04/2008 12:52

ottimo e semplice, qualche errore nei sorgenti ma la parte interessante e' la guida

lorenzo

lorenzo Di lorenzo postato il 12/06/2008 11:55

esistono pacchetti che fanno il pooling delle connessioni perché scriverselo?
dbcp in jakarta e c3p0, alcuni orm supportano direttamente il pooling(hibernate)

DoktorVis

DoktorVis Di DoktorVis postato il 23/09/2008 22:35

Per alcuni è meglio conoscere come funziona una cosa anziché utilizzarla e basta... buon articolo.

stecolna

stecolna Di stecolna postato il 24/09/2008 09:26

Mi unisco al commento di DoktorVis. Classe snella, adattabile ai vari RDBMS e sopratutto estendibile.
W il potere della programmazione ad oggetti!!!!

massi

massi Di massi postato il 02/10/2008 10:52

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

uno qualsiasi Di uno qualsiasi postato il 13/11/2008 13:23

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.

6 messaggi this document does not accept new posts

Precedente Successivo

Davide Lorenzo Marino, residente a Roma, e' esperto di programmazione Java, Visual Basic, C/C++, Pascal, Perl ed altri linguaggi piu' o meno noti. Attualmente e' in cerca di occupazione (possibilmente come programmatore Java, possibilmente in Roma).

Volete contribuire? Leggete come!.
 
 

Il presente sito e' frutto del sudore della mia fronte (e delle mie dita), se siete interessati a ripubblicare uno degli articoli, documenti o qualunque altra cosa presente in questo sito per cortesia datemene comunicazione (o all'autore dell'articolo se non sono io), cosi' il giorno che faccio delle aggiunte potro' avvisarvi e magari mandarvi il testo aggiornato.


Questo sito era composto con VIM, ora e' composto con VIM ed il famosissimo CMS FdT.

Questo sito non e' ottimizzato per la visione con nessun browser particolare, ne' richiede l'uso di font particolari o risoluzioni speciali. Siete liberi di vederlo come vi pare e piace, o come disse qualcuno: "Finalmente uno dei POCHI siti che ancora funzionano con IE5 dentro Windows 3.1".

Web Interoperability Pleadge Support This Project
Powered By Gojira