MokaByte 94 - Marzo 2005 
Accesso ai database in J2EE
Come configurare Tomcat e JBoss per l'accesso ai database da applicazioni web ed EJB
di
Giovanni Puliti
Grazie alle funzionalità offerte dall' non si deve più preoccupare della connessione ad una sorgente dati Fra i compiti di un application server J2EE vi è quello di gestire la connessione al database: indirezione e intermediazione sono i benefici di questo approccio

Accesso a database in J2EE
La API JDBC introdotta con il JDK 1.1 ancora prima dell'avvento di J2EE, offre un potente sistema di astrazione per l'accesso ai database e motori di persistenza SQL tramite una interfaccia di alto livello. Il programmatore può e deve gestire ogni aspetto relativo allo scambio di dati da e verso il motore di persistenza: gestire il caricamento del driver, associare un URL JDBC ad un database, definire le proprietà di connessione, gestire le connessioni sono tutte operazioni che devono essere gestite dalla applicazione.
Anche se questo approccio ha certamente rappresentato un importato passo avanti rispetto al passato, con il tempo ha anche evidenziato alcune carenze.
In particolare tutti gli aspetti relativi al punto di contatto fra applicazione e database sono quelli in cui si incontrano le problematiche più importanti ed in cui in genere si spende una quantità di tempo non indifferente nel caso in cui non si disponga di strumenti adeguati. Si pensi ad esempio al problema del pool di connessioni. Inizialmente molti programmatori adattavano librerie personali per creare sistemi più o meno flessibili e dinamici per la gestione ottimizzata di connessioni. Poi con il tempo si è passati lentamente all'uso di connection pool prodotti da terze parti.
Anche la gestione diretta delle problematiche relative alla gestione della localizzazione e parametrizzazione della connessione è un aspetto non banale. Nella migliore delle ipotesi si rischia di legare l'applicazione al sistema in uso.

La piattaforma Java 2 Enterprise Edition ha introdotto una nuova modalità di lavoro per quanto concerne l'accesso ai database in cui il ruolo principale è giocato dal container e non dall'applicazione finale: il passaggio a questo nuovo modo di lavorare implica un notevole salto concettuale ma nella pratica la sua adozione è molto semplice ed agevole.
Importanti sono i benefici derivanti dall'uso di un container per la gestione delle connessioni e si possono raggruppare in due categorie: indirezione e intermediazione della connessione da parte del container.
La prima indica che l'applicazione non deve specificare nessun parametro specifico del database: niente nome host, nome database e credenziali di accesso. L'applicazione lavora con il concetto di sorgente dati e con un nome astratto che viene associato dal container al database fisico. Tramite la API JNDI l'applicazione in esecuzione all'interno del container deve quindi eseguire una ricerca presso un directory service (in genere messo a disposizione dallo stesso application server) di un oggetto che implementa l'interfaccia javax.sql.Datasource.
Intermediazione del container verso il database engine significa invece che tutte le operazione di gestione della connessione, del flusso dati, autenticazione ed autorizzazione, sono a carico del container il quale si frappone fra il database e l'applicazione finale.

Conseguenza di questo approccio è il fatto di non dover più gestire a livello applicativo del pooling delle connessioni o addirittura della chiusura delle connessioni (se il container è ben implementato, si premura di controllare se l'applicazione ha dimenticato la connessione aperta al termine del thread di esecuzione), anche se è da considerarsi una cattiva abitudine lasciare in sospeso volutamente le connessioni.
In genere un buon application server consente anche di specificare in vario modo credenziali di accesso ed autenticazione per il database, così come può essere configurato per gestire tutti i parametri di connessione.
Questi due aspetti (in direzione e intermediazione) permettono alla applicazione di essere del tutto svincolata dalla particolare piattaforma, massimizzando la portabilità della stessa. Una applicazione J2EE viene quindi pacchettizzata all'interno di un file con estensione war, jar o ear all'interno del quale non vi è nessun riferimento al nome del server, nome del database, credenziali di autenticazione o parametri di connessione.
Una volta che due piattaforme risultano essere completamente e correttamente configurate (dal database all'application server) le applicazioni potranno essere spostati da un server all'altro senza particolari operazioni aggiuntive.

 

Database e J2EE nella pratica
Quanto detto fin'ora vale per la maggior parte dei container J2EE, anche se al solito si vedranno i casi più comuni, ovvero quello del container web e di quello EJB.
Per la loro popolarità ed uso verrà fatto riferimento a Tomcat per la parte web e a JBoss per la parte EJB.
Configurazione del container: definire la sorgente dati
Per definire la sorgente dati è necessario configurare l'application server in modo da mappare un nome logico con un database reale. Questa operazione in genere si effettua tramite un opportuno pezzo di codice XML che deve essere opportunamente configurato all'interno del container in esame. Il codice XML ovviamente varia in funzione del database scelto. Negli esempi che verranno mostrati si analizzerà il caso di MySQL ed Oracle, i due prodotti attualmente più utilizzati.
Configurazione di Tomcat
Come dovrebbe essere noto al lettore, la maggior parte delle opzioni di funzionamento del container web del Jakarta Project si possono specificare all'interno del file di configurazione server.xml presente nella directory TOMCAT_HOME/conf.
Per quanto concerne la configurazione di una sorgente dati associata a un database MySQL è necessario inserire uno script XML come il seguente:

<Context path="/DBTest" docBase="DBTest"
         debug="5" reloadable="true" crossContext="true">
<Resource name="jdbc/TestDB"
          auth="Container" type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/TestDB">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>

<!-- Maximum number of dB connections in pool. Make sure you
configure your mysqld max_connections large enough to handle
all of your db connections. Set to 0 for no limit.
-->
<parameter>
<name>maxActive</name>
<value>100</value>
</parameter>

<!-- Maximum number of idle dB connections to retain in pool.
Set to -1 for no limit. See also the DBCP documentation on this
and the minEvictableIdleTimeMillis configuration parameter.
-->
<parameter>
<name>maxIdle</name>
<value>30</value>
</parameter>

<!-- Maximum time to wait for a dB connection to become available
in ms, in this example 10 seconds. An Exception is thrown if
this timeout is exceeded. Set to -1 to wait indefinitely.
-->
<parameter>
<name>maxWait</name>
<value>10000</value>
</parameter>

<!-- MySQL dB username and password for dB connections -->
<parameter>
<name>username</name>
<value>admin</value>
</parameter>
<parameter>
<name>password</name>
<value>admin</value>
</parameter>

<!-- Class name for the official MySQL Connector/J driver -->
<parameter>
<name>driverClassName</name>
<value>com.mysql.jdbc.Driver</value>
</parameter>

<!-- The JDBC connection url for connecting to your MySQL dB.
The autoReconnect=true argument to the url makes sure that the
mm.mysql JDBC Driver will automatically reconnect if mysqld closed the
connection. mysqld by default closes idle connections after 8 hours.
-->
<parameter>
<name>url</name>
<value>jdbc:mysql://localhost:3306/javatest?autoReconnect=true</value>
</parameter>
</ResourceParams>
</Context>

La prima cosa da notare in questo pezzo di codice XML è la presenza del tag

<Context path="/DBTest" docBase="DBTest" …>

tramite il quale si può definire un contesto web (ovvero una web application) all'interno del tag <server>: in questo caso l'applicazione in esame è quella deployata sotto il docBase (directory di deploy) DBTest e risponde all'URL /DBTest.
Essendo la sorgente dati definita all'interno della coppia <context></context> sarà visibile esclusivamente per la applicazione DBTest.
In alternativa, per rendere la sorgente accessibile per tutte le applicazioni web, è possibile definire la datasource all'interno del context di default definito dal tag <DefaultContext>.
Di seguito è inserito il tag che consente di definire un nome logico per una risorsa:

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"/>

In questo caso jdbc/TestDB è il nome JNDI per la sorgente dati: questo nome sarà quello che l'applicazione utilizzerà per ricavare con una lookup JNDI la datasource e connettersi al database. Il prefisso jdbc non è obbligatorio ma convenzionale.
L'autenticazione come si può vedere è definita a livello container, mentre il tipo di oggetto identificato dal nome è un javax.sql.DataSource.
Questo meccanismo permette di definire (o meglio pubblicare) nell'albero JNDI diverse tipologie di oggetti: in questo caso particolare si tratta di una datasource, ma si sarebbe potuto definire un file o una coda di messaggi JMS.
Dopo questi due tag sono riportati tutti i parametri di configurazione della datasource, cosa che da un punto di vista J2EE è probabilmente meno interessante e certamente di facile comprensione per l'autore: si notino i parametri di attesa massima (maxidle e maxwait) e di login (username e password), così come l'URL di connessione in perfetto stile JDBC.
Anche il parametro driverClassName è importante dato che consente di specificare il nome del driver da utilizzare.
Si noti che per utilizzare la versione precedente del JConnector di MySQL si sarebbe dovuto specificare un nome di classe diverso:

<parameter>
<name>driverClassName</name>
<value>org.gjt.mm.mysql.Driver</value>
</parameter>

Nella analisi del codice XML si è volutamente saltato un passaggio molto importante, rappresentato dal parametro che specifica il nome del factory di connessione da utilizzare:

<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>

In questo caso si specifica il factory di connessione BasicDataSourceFactory che fa parte della libreria Commons DBCP (vedi [DBCP]).
Di fatto questo è probabilmente il punto più importante di tutto il sistema dato che consente di specificare il nome del costruttore di connessioni ed implicitamente anche il connection pool da usare. Presso la pagina web [TOM] è possibile trovare alcuni approfondimenti relativi alla modalità di configurazione del DBCP.
Normalmente non vi è necessità di cambiare questo punto, anche se a volte si possono sperimentare prodotti alternativi.
Quanto visto fino a questo punto vale per la release 4.1 e 5.0 di Tomcat. Per le versioni precedenti o successive le cose non sono molto differenti ed eventualmente si consiglia di consultare la documentazione relativa ([TOM]).
Volendo cambiare tipo di database non è sufficiente modificare il codice XML appena visto: di seguito è riportato il codice XML relativo ad un database Oracle.
Come si può notare le differenze sono davvero minime e sono tutte relative ad alcuni parametri proprietari del server dati:

<Resource name="jdbc/myoracle" auth="Container"
type="javax.sql.DataSource"/>

<ResourceParams name="jdbc/myoracle">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>oracle.jdbc.driver.OracleDriver</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:oracle:thin:myschema@127.0.0.1:1521:mysid</value>
</parameter>
<parameter>
<name>username</name>
<value>scott</value>
</parameter>
<parameter>
<name>password</name>
<value>tiger</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>20</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>10</value>
</parameter>
<parameter>
<name>maxWait</name>
<value>-1</value>
</parameter>
</ResourceParams>


Al solito presso [TOM] si possono trovare numerosi esempi di configurazione per diversi database.
Concludiamo la parte relativa a Tomcat dicendo che quanto appena visto permette di inserire il nome logico della datasource all'interno del tree JNDI di Tomcat: eventualmente è bene sapere che è possibile non usare il directory service di Tomcat (appoggiandosi ad un altro esistente) JNDI specificando il parametro -nonaming allo start del server stesso.

 

Configurazione di JBoss
JBoss utilizza una filosofia di gestione delle risorse completamente diversa rispetto a Tomcat. In JBoss una applicazione EJB, una web application o un file di configurazione XML sono gestiti, in prima istanza, nello stesso modo: devono essere deployati all'interno dell'application server il quale potrà prenderli in gestione anche in modo dinamico "a caldo".
Senza addentrarsi ulteriormente nella spiegazione del funzionamento di JBoss si limiterà la trattazione ai passi necessari per specificare una sorgente dati e deployarla all'interno del server.

 

Deploy di un file di configurazione -DS
Per poter configurare una sorgente dati è necessario copiare un file XML il cui nome ricalca il seguente schema

<nome>-ds.xml

dove <nome> per convenzione ricalca quello della sorgente dati che si sta definendo. All'interno di tale file vengono inserite tutte le informazioni necessarie per definire il nome JNDI e per configurare l'accesso al database.
All'interno della distribuzione nella directory

JBOSS_HOME/docs/examples/jca

è possibile trovare molti file di configurazione di esempio per tutti i principali database disponibili in questo momento: da MySQL e Oracle, DB2, Postgres, Hypersonic DB, Informix e perfino SQL Server. Osservando il contenuti di tali file potranno essere facilmente create le configurazioni di per i casi più disparati.
Ad esempio per definire la sorgente dati MyDS di un database MySQL si potrà utilizzare un file fatto nel seguente modo;

<datasources>
<local-tx-datasource>
<jndi-name>MyDS</jndi-name>
<connection-url>jdbc:mysql://localhost/community</connection-url>
<driver-class>org.gjt.mm.mysql.Driver</driver-class>
<user-name>admin</user-name>
<password>admin</password>
</local-tx-datasource>
</datasources>

In questo caso si usa un contesto transazionale locale dato che il driver per MySQL non supporta le transazioni distribuite. Nel caso di Oracle il file DS invece potrebbe essere:

<datasources>
<local-tx-datasource>
<jndi-name>MyDS</jndi-name>
<connection-url>jdbc:oracle:thin:@youroraclehost:1521:yoursid</connection-url>
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<user-name>admin</user-name>
<password>admin>/password>
<!-- Uses the pingDatabase method to check a connection is still valid before handing
it out from the pool -->
<!--valid-connection-checker-class-name>
org.jboss.resource.adapter.jdbc.vendor.OracleValidConnectionChecker
</valid-connection-checker-class-name-->
<!-- Checks the Oracle error codes and messages for fatal errors -->
<exception-sorter-classname>
org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
</exception-sorter-class-name>
<!-- sql to call when connection is created
<new-connection-sql>some arbitrary sql</new-connection-sql>
-->

<!-- sql to call on an existing pooled connection when it is obtained from pool
- the OracleValidConnectionChecker is prefered
<check-valid-connection-sql>some arbitrary sql</check-valid-connection-sql>
-->
</local-tx-datasource>
</datasources>

Alcuni parametri sono stati omessi per brevità editoriale. Consultando uno dei file di esempio si potranno verificare i parametri tramite i quali configurare la connessione con strumenti alternativi (es. OCI).
Interessante notare come in questo caso si abbia maggior libertà nello specificare funzionalità aggiuntive (come il comando SQL da eseguire alla prima connessione o nel momento dell'ottenimento dal pool).
Di fatto queste variazioni sono prerogative del driver utilizzato e non sono in alcun modo dipendenti da JBoss o dalla specifica J2EE.

 

Indirezione a due livelli
Quanto visto fino a questo punto rappresenta la prima parte del processo di configurazione. Il container è infatti in grado adesso di esporre un nome (JNDI) logico al quale viene associata una connessione verso un database.
Per migliorare l'accoppiamento fra l'applicazione e l'application server il passo successivo è quello di definire all'interno del file di deploy della applicazione web o EJB un nome che corrisponderà in qualche modo a quanto specificato all'interno dell'application server. In questo modo migliora il disaccoppiamento della applicazione dall'ambiente di esecuzione e si favorisce di gran lunga la portabilità della applicazione stessa.

Per fare questo all'interno di ogni applicazione J2EE (web o EJB) si associa il nome pubblico definito nella configurazione dell'application server, con un nome privato che sarà quello acceduto da codice.
In questo modo il codice Java scritto farà sempre riferimento in ogni punto della applicazione al nome privato della applicazione e mai a quello definito nel server: se tale nome dovesse cambiare, sarà sufficiente modificare il file di deploy (XML) della applicazione e non sarà necessario in alcun modo modificare il codice applicativo.
Questo duplice livello di indirezione si ottiene in modo differente a seconda che si tratti di una web application o di una applicazione EJB. Di seguito sono riportati entrambi i casi.

 

Configurazione della applicazione: il caso di una web application
Per prima cosa è necessario rendere disponibile alla web application un nome logico tramite il quale poi da codice Java sarà possibile ricavare la sorgente dati: nel file web.xml si deve inserire il seguente pezzo di XML

<resource-ref>
<! - - res-ref-name is the JNDI name used in application's code - - >
<res-ref-name>jdbc/myDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>

Come si può notare il nome jdbc/myDataSource individuato dal tag <res-ref-name> non specifica il nome della datasource definito nell'application server ma un nome privato per la web application. In tal modo questo nome verrà pubblicato all'interno dell'environment (ENV) di esecuzione della web application.
Successivamente nel file di configurazione jbossweb.xml si deve realizzare la associazione fra nome privato della web application e nome pubblico definito nell'application server:

<jboss-web>
<context-root>/mywebcontext</context-root>
<resource-ref>
<res-ref-name>jdbc/myDataSource</res-ref-name>
<jndi-name>java:/jdbc/MyDS</jndi-name>
</resource-ref>
</jboss-web>

La coppia di tag <res-ref-name> e <jndi-name> permette di specificare proprio tale associazione: java:/jdbc/MyDS è il nome pubblico definito nell'application server mentre jdbc/myDataSource quello locale alla applicazione web.

All'interno della applicazione web la sorgente dati potrà essere ricavata con una semplice operazione di lookup JNDI come nel seguente pezzo di codice:

Context jndiCntx = new InitialContext();
DataSource ds = (javax.sql.DataSource)jndiCntx.lookup("java:comp/env/jdbc/myDataSource");
con = ds.getConnection();

Si noti l'operazione di lookup utilizzando il nome locale alla applicazione.

 

Configurazione della applicazione: il caso di una applicazione EJB
Per prima cosa è necessario definire all'interno della applicazione il legame con il nome pubblico definito all'interno del container: questa cosa può essere fatta modificando il file di deploy proprietario dell'application server.
La procedura per configurare questo nome interno può essere fatta a livello globale per ogni bean session o entity e varia a seconda che si usi BMP o CMP.
Vediamo il caso di un BMP session bean. Nel file proprietario di deploy, il jboss.xml deve essere modificato in modo da inserire il seguente codice

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.2//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_2.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>MySessionBean</ejb-name>
<jndi-name> MySessionBean </jndi-name>
<resource-env-ref>
<resource-env-ref-name>jdbc/MyDataSource</resource-env-ref-name>
<jndi-name>java:/jdbc/MyDS</jndi-name>
</resource-env-ref>
</session>
</enterprise-beans>
</jboss>

Analogamente al caso delle applicazione web, anche in questo caso si associa il nome pubblico della datasource java:/jdbc/MyDS (nome che segue la convenzione JNDI per risorse di tipo datasource) con un nome locale al session jdbc/MyDataSource. In quest'ultimo caso il prefisso jdbc non è obbligatorio ma convenzionale.
Fatto questo all'interno del codice, per ricavare la sorgente dati (operazione eseguita normalmente all'interno del metodo setSessionContext()) si potrà scrivere:

try {
InitialContext initialContext = new InitialContext();
dataSource = (DataSource) initialContext.lookup("java:comp/env/MyDS");
} catch (Exception ex) {
logger.error(ex);
throw new EJBException("Unable to get DataSource");
}

Si noti il prefisso env nella istruzione

dataSource = (DataSource) initialContext.lookup("java:comp/env/MyDS");

che permette di specificare l'accesso all'ENV di esecuzione del bean stesso.
Come per i casi precedenti a questo punto la datasource potrà essere utilizzata per ricavare la connessione ed eseguire le operazioni di lettura scrittura verso il database tramite SQL e JDBC. Ad esempio nel metodo di creazione del bean di potrebbe scrivere:

// il metodo di creazione del bean riceve dall'esterno i parametri nome e cognome
// ed inserisce un utente con tali valori che verranno usati anche come chiave
public String ejbCreate( String firstName, String lastName) throws CreateException {
Connection conn = null;
PreparedStatement preparedStatement = null;
try {
// ricava la connessione dalla datosource precedentemente ricavata
conn = dataSource.getConnection();

preparedStatement = conn.prepareStatement("INSERT INTO mytable (FIRST_NAME, LAST_NAME)
VALUES (?,?)");
preparedStatement.setString(1, firstName);
preparedStatement.setString(2, lastName);
preparedStatement.executeUpdate();
} catch (SQLException sqlex) {
logger.error("SQLException: " + sqlex.getMessage());
throw new EJBException("SQLException: " + sqlex.getMessage());
} finally {
logger.debug("infine si fa pulizia in casa conn"+conn);
if (conn != null)
try {
conn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// la specifica mi obbliga a ritornare la chiave del bean appena
// creato. Qui di inventa con nome+cognome. Nella realtà la chiave
// è generata in modo automatico con algoritmi più complessi (Es. UUID) oppure
// scelta dall'utente
return firstName+"."+lastName;
}

Nel caso in cui si utilizzi CMP le cose sono leggermente differenti. Il mapping interno fra nome pubblico della sorgente dati e interno nel codice viene risolto dal container in modo trasparente. Definendo il nome pubblico della sorgente dati nel file jbosscmp-jdbc.xml di fatto non vi sono altre operazioni da eseguire:

<jbosscmp-jdbc>
<defaults>
<datasource>java:/MyDS</datasource>
<datasource-mapping>mySQL</datasource-mapping>
</defaults>

Essendo in questo caso il mapping a carico del container, non è necessario scrivere nessuna riga di codice Java per accedere alla sorgente dati, ne sarebbe possibile.

 

Semplificare la complessità con un buon IDE
Per chi inizia per la prima volta a sviluppare applicazioni J2EE web o EJB le considerazioni viste fino a questo momento potranno apparire alquanto complesse e per certi versi discordanti.
In parte concordo con questa visione della tecnologia. Quello che consiglio ai neofiti è di studiare bene la procedura di configurazione dell'application server (quanto descritto nella prima parte dell'articolo) e di seguire le indicazioni dell'ambiente di sviluppo per quanto concerne la preparazione dei vari file di deploy dell'applicazione.
Essendo questi in genere dipendenti in parte dal tipo di server utilizzato ed in parte legati alla tecnologia utilizzata (web application, entity BMP, entity CMP) è forse consigliabile non gestire manualmente l'editazione dei vari file XML a ma farsi guidare dall'ambiente di sviluppo che normalmente sa cosa deve essere inserito in funzione dell'ambiente di deploy (application server) scelto.
Consiglio per questo a tutti i lettori, prima di addentrarsi nello sviluppo di applicazioni di questo genere, di verificare la compatibilità dell'IDE scelto.
Un buon riferimento per l'appronfondimento degli argomenti appena esposti è certamente il sito CoreDevelopersNetwork, (vedi [CDN]) che contiene un'intera sezione dedicata a JBoss.

 

Bibliografia
[TOM] - The Apache Jakarta Tomcat 5 Servlet/JSP Container - JNDI Datasource HOW-TO
http://jakarta.apache.org/tomcat/tomcat-5.0-doc/jndi-datasource-examples-howto.html
[DBCP] - "Jakarta Commons - VII parte: DBCP" di Alessandro Garbagnati.
http://www.mokabyte.it/2004/06/jcommons-7.htm
[CDN] - http://www.coredevelopers.net/

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it