7.1 Pool di connessioni

Oggi impareremo come lavorare con il database in modo ancora più professionale. E ora parleremo del thread pool, o connection pool in inglese.

La connessione al database richiede del tempo. Soprattutto se il database è remoto. Se effettuiamo una connessione al database per ogni richiesta, la risposta della nostra applicazione sarà incredibilmente lenta. Per non parlare delle risorse che consumerà.

Come soluzione a tali problemi, è stato proposto di memorizzare le connessioni alla base in un set, chiamato pool di thread.

Quando richiediamo una nuova connessione, il pool di connessione la crea e, una volta chiusa, non la chiude, ma la salva nel pool di connessione. E se richiediamo di nuovo una connessione dal pool di connessioni, ce ne darà una di quelle vecchie invece di crearne una nuova.

In effetti, l'applicazione funziona tramite un driver speciale, che è un wrapper per un normale driver JDBC e che ha funzionalità aggiuntive per lavorare con il pool. Questa situazione può essere rappresentata come segue:

pool di connessioni

Il programmatore può configurare manualmente le impostazioni per il pool di connessioni: il numero di connessioni attive, il timeout, ecc.

Per situazioni particolarmente estreme, puoi scrivere il tuo pool di connessioni: una classe che avrà un elenco di connessioni. Sovrascriverà la funzione di chiusura, che restituirà la connessione all'elenco, e ci saranno molte altre chicche come il timer di connessione aperta. Quando non c'è connessione per molto tempo, la connessione viene chiusa.

7.2* Interfaccia DataSource e ConnectionPoolDataSource

Il pool di thread di solito funziona in tandem con l'oggetto DataSource. Questo oggetto può essere pensato come un'astrazione di un database remoto. Il nome stesso Data Source può essere letteralmente tradotto come Data Source. Il che è una specie di suggerimento.

Gli oggetti DataSource vengono utilizzati per ottenere una connessione fisica a un database e sono un'alternativa a DriverManager. Non è necessario registrare il driver JDBC. Hai solo bisogno di impostare i parametri appropriati per stabilire una connessione eeseguire il metodo getConnection().

Quando si crea un oggetto di tipo DataSource localmente (direttamente nell'applicazione), i parametri di connessione vengono impostati dai metodi appropriati forniti dal provider del driver JDBC. Questi metodi non sono definiti dall'interfaccia DataSource, in quanto i parametri per la connessione a DBMS di fornitori diversi possono differire sia nel tipo che nel numero (ad esempio, non tutti i DBMS richiedono di specificare il tipo di driver o il protocollo di rete).

Pertanto, quando si lavora con il DBMS Oracle, per utilizzare i metodi set e get corrispondenti, è necessario ottenere un oggetto di tipo OracleDataSource, che è un'istanza dell'omonima classe che implementa l'interfaccia DataSource. Pertanto, questo modo di creare oggetti di tipo DataSource rende il codice meno portabile e meno dipendente dall'implementazione specifica del driver.

Di seguito è riportato un esempio di codice che illustra l'utilizzo locale di oggetti di tipo DataSource.

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;

public class DataSource {
    public static void main(String[] args) {
    	try {
        	OracleDataSource ods = new OracleDataSource();

        	ods.setUser("root");
        	ods.setPassword("secret");
        	ods.setDriverType("thin");
        	ods.setDatabaseName("test");
        	ods.setServerName("localhost");
        	ods.setPortNumber(1521);

        	Connection connection = ods.getConnection();
        	System.out.println("Connection successful!!!");

    	} catch (SQLException se) {
        	se.printStackTrace();
    	}
    	System.out.println("Goodbye!");
	}
}

7.3* Interfaccia JNDI

Le funzionalità complete degli oggetti di tipo DataSource si manifestano in combinazione con l'utilizzo dell'interfaccia JNDI. JNDI è un servizio speciale (qualcosa come una directory) per applicazioni server di grandi dimensioni che consente a un servizio di stabilire una connessione con un altro.

L'utilizzo del servizio nome e directory consente di archiviare oggetti di tipo DataSource precedentemente creati dall'amministratore di sistema con parametri di connessione predefiniti. Di seguito sono riportati alcuni dei nomi di proprietà standard (parametro) sviluppati da Sun:

Nome della proprietà Tipo di dati Java Scopo
nome del database Corda Nome del database
nome del server Corda Nome del server
utente Corda Nome utente (ID)
parola d'ordine Corda Password utente
numero di porta int Numero di porta del server DBMS

La combinazione di interfacce DataSource e JNDI svolge un ruolo chiave nello sviluppo di applicazioni aziendali multilivello basate sulla tecnologia dei componenti J2EE.

Se si utilizza una combinazione delle interfacce DataSource e JNDI nel codice dell'applicazione, è sufficiente richiedere un oggetto di tipo DataSource dal servizio di denominazione e directory. In questo caso, i dettagli della connessione e il codice del programma dipendente da una particolare implementazione del driver sono nascosti all'applicazione nell'oggetto memorizzato nel servizio nome e directory.

Pertanto, la condivisione di oggetti di tipo DataSource e JNDI prevede due fasi che vengono eseguite indipendentemente l'una dall'altra:

  1. È necessario archiviare l'oggetto denominato di tipo DataSource nel servizio di denominazione e directory utilizzando Context.bind()il javax.naming.
  2. Richiedere un oggetto di tipo DataSource dal servizio di denominazione e directory nell'applicazione utilizzando il Context.lookup(). Successivamente, puoi utilizzare il suo metodo DataSource.getConnection()per ottenere una connessione al database.

Di seguito è riportato un esempio di utilizzo dell'interfaccia JNDI e di un oggetto di tipo OracleDataSource insieme.

// Create a key JNDI object
Hashtable env = new Hashtable();
env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
Context ctx = new InitialContext(env);

// Get the DataSource object by its name
DataSource ods = (DataSource) ctx.lookup("myDatabase");

// Get the Connection from the DataSource object
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");

Non analizzeremo il lavoro di JNDI. Questo esula dallo scopo di questo corso. Voglio solo che tu sappia che questo approccio è molto comune nelle grandi applicazioni. Non perderti se vedi codice come questo in futuro.

Non importa come funziona. Devi solo sapere che con un servizio JNDI puoi ottenere l'oggetto proxy di qualsiasi servizio registrato nella directory del servizio. Ecco come si ottiene l'oggetto DataSource e lo si utilizza per lavorare con il database.

7.4 Esempi di pool di connessioni

Torniamo al punto di partenza: pool di connessioni.

I programmatori Java nei loro programmi utilizzano molto spesso librerie che contengono varie implementazioni di pool di connessioni. Tra questi ce ne sono tre più popolari:

  • Apache Common DBCP
  • C3PO
  • Hikari CP

Il progetto Apache è stato il primo a creare un buon pool di connessioni, viene utilizzato anche all'interno del server Web Tomcat. Un esempio di come lavorarci:

public class DBCPDataSource {

    private static BasicDataSource ds = new BasicDataSource();
    static {
    	ds.setUrl("jdbc:h2:mem:test");
    	ds.setUsername("user");
    	ds.setPassword("password");
    	ds.setMinIdle(5);
    	ds.setMaxIdle(10);
    	ds.setMaxOpenPreparedStatements(100);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private DBCPDataSource(){ }
}

Il secondo pool molto popolare è C3PO . Strani nomi, sono d'accordo. Il nome C3PO è un riferimento al robot c3po di Star Wars. E anche CP è l'abbreviazione di Connection Pool.

public class C3p0DataSource {

    private static ComboPooledDataSource cpds = new ComboPooledDataSource();
    static {
    	try {
        	cpds.setDriverClass("org.h2.Driver");
        	cpds.setJdbcUrl("jdbc:h2:mem:test");
        	cpds.setUser("user");
        	cpds.setPassword("password");
    	} catch (PropertyVetoException e) {
            // handle the exception
    	}
    }

    public static Connection getConnection() throws SQLException {
    	return cpds.getConnection();
    }

    private C3p0DataSource(){}
}

La documentazione può essere trovata sulla pagina ufficiale .

E infine, il pool di connessioni più popolare ai nostri tempi è HikariCP :

public class HikariCPDataSource {

    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;

    static {
    	config.setJdbcUrl("jdbc:h2:mem:test");
    	config.setUsername("user");
    	config.setPassword("password");
    	config.addDataSourceProperty("cachePrepStmts", "true");
    	config.addDataSourceProperty("prepStmtCacheSize", "250");
    	config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    	ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private HikariCPDataSource(){}
}

Ecco la sua pagina GitHub ufficiale .

E un buon articolo sulla configurazione.