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/
|