tramite poche istruzioni Φ possibile accedere ad un insieme di connessioni jdbc da poter utilizzare in maniera condivisa; questo perchΦ il pool dichiara, istanzia e gestisce le connessioni per noi, permettendo il riutilizzo delle stesse tramite un booleano indicante lo stato della connessione; al termine dell'elaborazione la connessione non viene chiusa ma semplicemente rilasciata; se alla richiesta di una connessione quelle attualmente in pool sono tutte occupate, e se il limite massimo di connessioni staibilito non Φ stato superato, ne verrα creata automaticamente una nuova.
Complementare a questo, un thread che si occupa di controllare periodicamente il pool per verificare se esiste qualche connessione non rilasciata oltre un tempo massimo stabilito.
I vantaggi principali dell'utilizzo del pool possono racchiudersi quindi in:
-a fronte di una richiesta, evitare di ricreare ogni volta la connessione, prelevandola dal pool e reinserendola al termine dell'utilizzo
-pi∙ thread possono richiedere contemporaneamente una connessione al pool.
A corredo un utile tester per effettuare stress test e benchmark calcolando il tempo medio in secondi per aqcuisire e rilasciare una connessione utilizzando un pool piuttosto che ricreare ogni volta una singola connessione.
ConnectionItem Bean che mappa la singola connessione.
ConnectionManager Thread dedicato al controllo periodico del pool delle connessioni alla ricerca di connessioni appese.
ConnectionParameters Bean che mappa i parametri acquisiti da file properties.
ConnectionPool Gestore del pool di connessioni.
MaxNumberConnectionException Eccezione sollevata per la richiesta di connessioni superiori al limite stabilito.
Tester Utility per effettuare stress test e benchmark fra connessioni in pool e connessioni singole.
PropertiesFileHandler Wrapper per la lettura di file properties.
La libreria, per poter funzionare, si basa sulle seguenti coppie chiave=valore inserite nel file connectionpool.properties:
jdbc.driver=<inserire qui il driver da utilizzare per la connessione jdbc>
jdbc.url=<inserire qui la stringa di connessione da utilizzare per la connessione jdbc>
jdbc.user=<inserire qui l'utenza da utilizzare per la connessione jdbc>
jdbc.password=<inserire qui la password da utilizzare per la connessione jdbc>
pool.minConns=<inserire qui il numero di connessioni che verranno create e inserite nel pool all'avvio>
pool.maxConns=<inserire qui il numero massimo di connessioni previste dal pool>
pool.maxInUse=<inserire qui il numero massimo di secondi di utilizzo di una connessione prima di essere forzatamente rilasciata>
manager.cleaningInterval=<inserire qui l'intervallo in secondi per l'esecuzione del controllo di connessioni non rilasciate nel pool>
stress.try.number=<inserire qui il numero di connessioni da richiedere in modalitα pool/single per effettuare lo stress test>
log4j.file.url=<inserire qui la url relativa al file di properties per la gestione del framework Log4J>
Tramite l'analisi degli attributi, del costuttore, del distruttore e dei metodi fondamentali (fra cui quelli richiamati dal client) della classe principale ConnectionPool, la classe che "avvolge" l'insieme delle connessioni JDBC, verrα illustrato il ciclo di vita del connection pool:
//bean contenente le chiavi acquisite dal file delle proprietα
private ConnectionParameters connectionParameters = new ConnectionParameters();
//gestore del logging
private Logger logger = null;
nel metodo getInstance, applicando il pattern Singleton, a fronte di una richiesta da un client viene effettuato il controllo se Φ giα presente una istanza della classe: in caso negativo ne viene creata una, chiamando quindi il costruttore
public static synchronized ConnectionPool getInstance() {
//gestione singola istanza della classe con pattern singleton
if (connectionPool == null) {
connectionPool = new ConnectionPool();
}
return connectionPool;
}
nel costruttore sono inserite le operazioni di inizializzazioni che permettono l'acquisizione dei parametri sopra descritti dal file delle proprietα;
per la gestione del framework open source Log4J rimando al seguente link: http://jakarta.apache.org/log4j
//creazione pool di connessioni minimo; questo soddisferα le richieste fino al momento in cui saranno effettuate pool.minConns+1 richieste contemporanee
getLogger().info("creazione pool minimo di " + getConnectionParameters().getMinConns() + " connessioni");
for (int i = 0; i < getConnectionParameters().getMinConns(); i++) {
makeConnection();
}
} catch (Exception e) {
getLogger().fatal(e.getMessage());
}
}
nel metodo getFreeConnection viene effettuata la gestione di richiesta connessione: a fronte di una richiesta viene ciclato il pool alla ricerca di una connessione marcata con l'attributo booleano inUse a falso; se l'esito della ricerca Φ negativo, viene invocato il metodo per la creazione di una nuova connessione da inserire nel pool
public synchronized Connection getFreeConnection() throws SQLException, MaxNumberConnectionException {
//ricerca connessione non in uso
for (Enumeration enum = getPool().elements(); enum.hasMoreElements();) {
getLogger().info("acquisizione Connection con progressivo " + currItem.getCounter());
return currItem.getConnection();
}
}
//non Φ stata trovata connessione non in uso, creazione di una nuova
getLogger().info("connessione attualmente non disponibile; richiesta creazione istanza Connection");
return makeConnection();
}
il metodo makeConnection provvede alla creazione di una nuova connessione JDBC, incapsulata al solito nella classe ConnectionItem; se il numero delle connessioni presenti supera il valore massimo definito nella chiave pool.maxConns viene sollevata una eccezione di tipo MaxNumberConnectionException
//controllo su limite max stabilito di connessioni
if (getConnectionParameters().getMaxConns() == getPool().size()) {
getLogger().error("richiesta connessione eccedente al numero max stabilito del pool");
throw new MaxNumberConnectionException();
}
//creazione connessione
newConnection =
DriverManager.getConnection(
getConnectionParameters().getUrl(),
getConnectionParameters().getUser(),
getConnectionParameters().getPassword());
//inserimento nel pool
releaseConnection(newConnection);
return newConnection;
}
nel metodo releaseConnection viene effettuata la gestione di rilascio connessione: a fronte di un rilascio la connessione corrispondente non viene distrutta ma semplicemente marcata come non in uso tramite impostazione dell'attributo inUse a falso; in questo modo la stessa potrα essere riutilizzata evitando inutile tempo di elaborazione per ricrearla
public synchronized void releaseConnection(Connection connection) {
nel distruttore Φ inserita la chiamata al metodo per fermare il thread per il controllo delle connessioni e al metodo per chiudere definitivamente tutte le connessioni presenti correntemente nel pool
il metodo cleanPool viene richiamato dal thread che si occupa di controllare lo stato delle connessioni in base al tempo di utilizzo: la verifica consiste nel controllare, ogni intervallo di tempo definito dalla chiave manager.cleaningInterval, se esiste qualche connessione non rilasciata oltre al tempo massimo definito dalla chiave pool.maxInUse; in caso affermativo, la connessione viene annullata e marcata come non in uso per poter permettere poi il riutilizzo
void cleanPool() throws SQLException {
//ricerca connessioni appese
getLogger().info("start pulizia del pool");
for (Enumeration enum = getPool().elements(); enum.hasMoreElements();) {
long diff = now.getTime() - currItem.getDate().getTime();
if (diff > (getConnectionParameters().getMaxInUse() * 1000)) {
//connessione aperta da pi∙ tempo rispetto al max stabilito
//rilascio forzato della connessione
getLogger().error("rilascio forzato della connessione con progressivo " + currItem.getCounter() + " aperta da " + (diff/1000) + " secondi rispetto al max stabilito di " + getConnectionParameters().getMaxInUse() + " secondi");
releaseConnection(currItem.getConnection());
}
}
}
getLogger().info("stop pulizia del pool; connessioni esaminate: " + getPool().size());
}
ESEMPIO DI CONSUMO DELLA LIBRERIA DA PARTE DI UN CLIENT: