Sei sulla pagina 1di 111

2

INDICE
Indice............................................................................................................... 3
1 Introduzione ................................................................................................ 4
2 Notazioni:.................................................................................................... 5
3 J2EE ........................................................................................................... 6
4 Gli Application Server .................................................................................... 7
5 JNDI – Java Naming and Directory Interface..................................................... 8
6 JDBC ........................................................................................................ 14
7 CloudScape un database leggero e tutto in java .............................................. 20
8 JDBC 2.0 ................................................................................................... 22
8.1 Pool di connessioni ................................................................................ 23
8.2 DataSource .......................................................................................... 24
8.3 XADataSource ...................................................................................... 26
8.4 RowSet................................................................................................ 33
9 Paradigma M.V.C. (Model View Controller)...................................................... 37
9.1 Servlet ................................................................................................ 41
9.2 JavaBeans............................................................................................ 42
9.3 JSP (Java Server Page) .......................................................................... 43
10 WAR (Web Archive).................................................................................. 47
11 Gestione dell’accesso concorrente ad una JSP o servlet ................................. 51
12 EJB Introduzione...................................................................................... 55
13 EJB: Come è composto ............................................................................. 58
14 EJB: il client ............................................................................................ 63
15 EJB: Creazione di un jar............................................................................ 66
16 Deployare un EJB..................................................................................... 68
17 EJB:esempi ............................................................................................. 71
18 EJB Session ............................................................................................ 74
18.1 EJB Session Statefull........................................................................... 76
18.2 EJB Session stateless .......................................................................... 83
19 EJB Entity ............................................................................................... 88
19.1 EJB Entità B.M.P................................................................................. 92
19.2 EJB Entity C.M.P............................................................................... 103

3
1 INTRODUZIONE
Parlerò solo ed esclusivamente della J2EE, cercherò di entrare nel dettaglio dei singoli
componenti (Servlet, Jsp, Ejb, etc), cercare di capire il loro ciclo di vita, come
ottimizzarli, progettare architetture, cercherò di spiegare soprattutto il perché di certe
scelte e proverò a farlo nella maniera più semplice possibile, cercando per ogni
argomento di toccare solo i punti fondamentali e/o critici.
Questo vuole essere un libro/corso che riesca a rendere operativa velocemente una
persona, dotandola di tutte le principali conoscenze sulla J2EE, ovviamente consiglio di
approfondire tutte le materie.

Non ho la pretesa o la presunzione di voler insegnare niente a nessuno, ma solo la


voglia di confrontarmi con tutti i lettori di questa guida, vorrei tanto avere vostri
consigli, suggerimenti, aggiornamenti e correzioni.

Fino a quando il tutto non sarà finito i vari capitoli saranno soggetti a modifiche, se
volete essere aggiornati sulle correzioni o sugli aggiornamenti mandatemi una mail di
richiesta inserimento nella Mailing-List di quetso libro.

L'ordine dei capitoli è dal mio punto di vista fondamentale per questo corso.

Seguirò rigorosamente le specifiche ed i Blueprints della Sun.

Parleremo quindi di come progettare (e far funzionare) una applicazione Enterprise


secondo lo standard J2EE in maniera tale che sia veramente portabile e svincolata
dall'Application Server prescelto.

Come tutte le introduzioni dei libri vorrei fare dei ringraziamenti e dedicare
questo lavoro:

A mia moglie Monica ed a mio figlio Alessandro, che pur non vedendomi mai durante il
giorno sono stati così bravi da sopportarmi ed aiutarmi permettendomi di stare davanti
al PC anche la sera.

Alla mia famiglia che mi ha regalato il mio primo computer e mi ha sempre appoggiato
nelle mie scelte imprenditoriali ed informatiche.

Ed ovviamente a tutta la K-Tech.

Fabrizio Marini

4
2 NOTAZIONI:
AS = Application Server
VM = Virtual Machine

5
3 J2EE
E' un insieme di Standard e tecnologie.
Si può scaricare dal sito ufficiale della Sun dedicato a Java che è:
http://java.sun.com/

I prodotti li troviamo tutti a questo indirizzo:


http://java.sun.com/products/

Qui troviamo prima fra tutti la suddivisione che la Sun ha fatto delle sue tecnologie in
tre Macro Aree che sono:

· JavaTM 2 Platform, Standard Edition (J2SE™)


Qui possiamo scaricare il JDK o il JRE, insomma quello che ci serve per produrre ed
eseguire una applicazione Java.
· JavaTM 2 Platform, Enterprise Edition (J2EE™)
Da qui si scarica la J2EE, di cui parleremo abbondantemente in questo manuale.
· JavaTM 2 Platform, Micro Edition (J2ME™)
Si tratta di un Java runtime environment specifico ed ottimizzato per prodotti di
consumo quail per esempio Telefoni cellulari, palmari, sistemi di navigazione per
machine, cercapersone etc.

Nella J2EE sono presenti le seguenti API:

API importanti della J2SE


-RMI Remote Method Invocation
-JDBC Java Database Connectivity

Enterprise API
-JSP Java Server Pages
-Servlets Servlet API
-EJB Enterprise JavaBeans
-JNDI Java Naming and Directory Interface
-JDBC 2.0 Extensions Java Database Connectivity Extensions
-RMI-IIOP RMI over Internet Inter-Orb Protocol
-JMS Java Message Service
-Java Mail Java Mail
-JAF JavaBeans Activation Framework
-JTA Java Transaction API
-XML Extensible Markup Language
-JMX Java Management Extensions

La J2EE per poter essere operativa ha necessariamente bisogno di una J2SE.


Per esempio l'ultima versione di J2EE, la 1.3, vuole che sia presente ed installata sulla
stessa macchina una J2SE 1.3.1_01.

6
4 GLI APPLICATION SERVER
Gli AS altro non sono che dei prodotti basati su una J2EE.
Sono dei "software" che utilizzano e coordinano nel migliore dei modi tutte le tecnologie
contenute in una J2EE rendendo alla fine dei conti la vita facile agli sviluppatori che
anche se di basso profilo possono riuscire a costruire applicazioni Distribuite, Scalabili,
Sicure, Affidabili, Portabili e Transazionali, queste ultime sei parole sono la filosofia di
fondo della J2EE.

Per esempio alla fine di questo corso saprete fare una applicazione remota e
transazionale che sfrutta pool di connessioni e con dei sistemi di sicurezza ad alto
livello, ed il bello e' che non avrete nemmeno scritto una riga di codice al riguardo se
non dei semplici metodi che ovviamente dovete inserire in particolari "contenitori" che
devono essere configurati tramite dei file "xml" che per la maggior parte degli AS
vengono creati da dei wizard presenti in opportuni tools.

Oggi ci sono circa una trentina di produttori di J2EE - Application Server.

Ne nomino alcuni:

· Bea WebLogic Server 6.0


· SilverStream Server 3.7
· IBM WebSphere 4.0
· Oracle OC4J
· Sun J2EE (Per Sviluppatori)

Questi sono alcuni fra gli AS commerciali che sono stati certificati sullo standard delle
specifiche della SUN J2EE 1.2

Ottimo e non commerciale è anche:

· Jboss (Open Source)

Il primo ad essere stato certificato dalla SUN su specifiche J2EE1.3 avendo passato ben
15.000 tests nella Sun Microsystems J2EE Compatibility Test Suite è Pramati Server
3.0 (www.pramati.com), anche se uno dei primi a darne il supporto, dicembre 2000,
già da quando le specifiche ancora non erano state rilasciate ed ufficializzate è stato
Bea WebLogic Server 6.0.

E' importante sapere quale è la differenza tra un AS "Certificato" dalla Sun ed uno che
si dichiara compatibile.
Con la Certificazione abbiamo la certezza che una applicazione Entreprise, basata sulle
specifiche della Sun, sarà svincolata dall'AS su cui e per cui è stata sviluppata, cosa non
ovvia nell'altro caso. Tra l'altro anche se molti AS sono certificati, possono usare delle
loro particolarità, che se sfruttate rendono l'applicazione non portabile e di conseguenza
tipica per quel prodotto.

Quello che bisogna domandarsi prima di progettare una applicazione Enterprise è:

1) L'AS è certificato dalla Sun?


2) Per quale versione di J2EE è certificato?
3) Devo seguire le linee guida della Sun o posso usare particolarità dell'AS prescelto?

Cercherò di dare tutte le informazioni necessarie su problemi tipici ed architetture da


evitare per risolvere i problemi di una eventuale portabilità magari facendo riferimento
ad alcuni AS di mercato.

7
5 JNDI – JAVA NAMING AND DIRECTORY INTERFACE
Ricopre un ruolo molto importante in un AS, infatti tutte le operazioni tipiche che si
possono compiere su un AS passano prima di tutto attraverso JNDI, per esempio tra le
principali abbiamo:
· Transazioni (UserTransaction & TransactionManager)
· Pool di Connessioni ai Database (Datasource)
· Pool di Connessioni Transazionali ai Database (XADatasource)
· Connettersi a JMS e utilizzare Queue e/o Topic
· Connettersi ad un EJB
· Sicurezza

La maniera migliore per spiegare a chi non l'ha mai visto cosa è JNDI è quella di
paragonarlo ad una Hashtable.

L' Hashtable si trova fra le classi del JDK in particolare nel package java.util

In una Hashtable si possono mettere degli oggetti ed assegnargli dei nomi che li
identificano, così se conosco il nome identificativo dell'oggetto che voglio, per ottenerlo
faccio una chiamata diretta usando quel nome identificativo piuttosto che scorrermi
tutta una struttura di dati per trovarlo (come per esempio con un Vector sempre del
package java.util)

Esempio:

/*
Istanzio una Hashtable e la popolo con tre oggetti Integer, assegnando ad ogni oggetto
una stringa di identificazione
*/
Hashtable numbers = new Hashtable();
numbers.put("numero_uno", new Integer(1));
numbers.put("numero_due", new Integer(2));
numbers.put("numero_tre", new Integer(3));

/*
Nel momento in cui devo per esempio ottenere l'oggetto presente nell'Hashtable
numbers, identificato dalla stringa "numero_due" per assegnarlo ad un Integer, il
codice che dovrò scrivere sarà:
*/
Integer n = (Integer)numbers.get("numero_due");
if (n != null) {
System.out.println("numero_due = " + n);
}

Il metodo get(…) utilizzato ritorna un Object, quindi l'unica cosa che bisogna sempre
ricordarsi di fare è il cast dell'Object ritornato nel tipo dell'oggetto voluto.

L'Hashtable una volta istanziata è sfruttabile da quella VM, gli oggetti contenuti in essa
non possono essere ottenuti direttamente da una applicazione Remota.

JNDI in parole semplici è esattamente come una Hashtable, e' un "Server" che risponde
su una porta di un indirizzo IP, a cui ci si connette remotamente o localmente ed a cui
posso inviare oggetti ed assegnargli una stringa di identificazione, oppure ottenerli
tramite la stringa che li identifica.

/*
Mi collego a JNDI e gli invio tre oggetti.
*/
Context ic = new InitialContext();
ic.bind("numero_uno", new Integer(1));

8
ic.bind("numero_due", new Integer(2));
ic.bind("numero_tre", new Integer(3));

/*
Mi collego a JNDI e richiamo l'oggetto identificato dalla stringa "numero_due" per
assegnarlo ad un Integer
*/
Context ic = new InitialContext();
Integer n = (Integer) ic.lookup("numero_due");
if (n != null) {
System.out.println("numero_due = " + n);
}

Tra poco vedremo meglio come si fa a connettersi a JNDI, da notare che anche qui
devo castare l'oggetto che ottengo da una lookup(…).

Gli oggetti che possono essere messi in JNDI devono essere Serializzabili. Cioè tutti
quelli che implementano la Interfaccia Serializable del package java.io (Ricordarsi che i
Thread non sono serializzabili)

Quindi per connettersi a JNDI i passi necessari da fare sono:

- importare il package di cui fa parte JNDI e cioè javax.naming che ormai potete
trovare all'interno di una J2SE
- Utilizzare un costruttore che di solito è InitialContext() o InitialContext(Hashtable
environment)
- Valorizzare dei parametri che servono al costruttore InitialContext per raggiungere ed
autenticarsi sul server JNDI, parametri che di solito sono scritti o in un file, che si deve
chiamare jndi.properties, o in una Hashtable.
- Se mi collego a jndi da una classe che gira sulla stessa macchina dell'AS non ho molti
problemi (se ho settato bene il classpath), ma se la classe dovesse essere remota, devo
assicurarmi che nel classpath di tale macchina ci siano tutte le classi di jndi e quelle
dello specifico produttore a cui mi sto collegando.

Nel caso del costruttore InitialContext(Hashtable environment) scriveremo:

/*
esempio per connettersi a JNDI su Bea WebLogic, considerando di aver installato l'AS
su una macchina con indirizzo IP 200.100.50.1, e dove 7001 è la porta di default su cui
risponde il JNDI di WebLogic
*/
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://200.100.50.1:7001");
env.put(Context.SECURITY_PRINCIPAL, "utente");
env.put(Context.SECURITY_CREDENTIALS, "password");
try {
Context initialContext = new InitialContext(env);
} catch (NamingException e) { System.out.println(e); }

Nel caso del costruttore InitialContext() e se Utilizziamo JNDI1.2 o superiore


scriveremo:

/*
esempio per connettersi a JNDI su Bea WebLogic
*/
try {
Context initialContext = new InitialContext();
} catch (NamingException e) { System.out.println(e); }

9
ovviamente, non passandogli i parametri tramite una Hashtable, dobbiamo scrivere
sulla macchina su cui sta girando questo codice un file che si deve chiamare
jndi.properties
Tale file deve essere messo sotto il CLASSPATH o in alternativa sotto la directory
$JAVA_HOME/lib

Nel file jndi.properties ci sarà scritto (considerando di avere sempre Bea WebLogic):

java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://200.100.50.1:7001
java.naming.security.principal=utente
java.naming.security.credentials=password

Ovviamente la parte a destra dell'uguale varia a seconda dell'AS che utilizzo, il resto sia
per l'Hashtable sia per il file jndi.properties e' uguale per ogni AS certificato dalla
SUN.

Nel file jndi.properties posso definire solo un server JNDI a cui connettermi, quindi un
solo ip una sola porta con un solo utente e password. Nel caso in cui io debba poter
dinamicamente cambiare utente o password oppure connettermi a JNDI diversi allora
sono obbligato ad utilizzare il costruttore InitialContext(Hashtable environment)

L'utente con relativa password che utilizzo, deve essere un utente riconosciuto dall'AS.
Deve quindi comparire tra gli utenti di quell'AS e quell'utente deve essere presente con
gli opportuni privilegi nella ACL (Access Controlled List) di Jndi.

JNDI è organizzato come fosse un Hard Disk è come se con InitialContext() noi
andassimo in c:\ , e se io faccio ic.bind("numero_uno", new Integer(1)); è come
se io creassi un file numero_uno sotto c:\ cioè c:\numero_uno.
Volendo, per organizzare meglio gli oggetti che invio a JNDI, posso scrivere
ic.bind("mio_path/numero_uno", new Integer(1)); ed è come se io scrivessi il
file numero_uno sotto la directory mio_path di c:\. La relativa lookup sarà:
ic.lookup("mio_path/numero_uno ");

Ricordando questa regola concludiamo dicendo che non possono esistere sotto una
stessa radice oggetti con lo stesso nome ma oggetti identici possono essere sotto radici
diverse.

10
Noi scriviamo una Java Application (o servlet, jsp, ejb) e tramite le Jndi Api
(javax.naming) ci connettiamo ad un naming manager su cui effettuiamo delle bind o
delle lookup, gli oggetti che prendiamo saranno "collegati" a dei JNDI SPI (Service
Provider Interface).

Sempre alla stessa maniera riceverò un oggetto da JNDI, ma ognuno di essi mi potrà
fornire in maniera trasparente un servizio diverso (grazie agli SPI).

JNDI sarà sempre presente in questo corso e tramite lui impareremo a comunicare con
un EJB, a prendere una Connessione da un Pool di Connessioni, a gestire le Transazioni,
utilizzare JMS … etc.

Tutti gli AS hanno a disposizione dei Tools che ci permettono di vedere il contenuto di
JNDI che di solito viene presentato a forma di Albero.

Per esempio in questa finestra fornita dalla console di Bea WebLogic, si possono notare:

- ejbcourse-datasource (Datasource ad un pool di connessioni)


- SimpleTopic (Topic su JMS)
- javax/transaction/UserTransaction (UserTransaction di JTA)

11
Un Esempio con JNDI [provaJNDI.zip] :

Se decomprimete provaJNDI.zip vi trovate i seguenti file Java:

- Cliente.java
- PutCliente.java
- GetCliente.java

Descrizione:

- Cliente è una classe serializzabile.


- PutCliente fa una istanza di Cliente, gli assegna il nome logico "windows" e la invia
ad un JNDI su Bea WebLogic.
- GetCliente si collega allo stesso jndi e tramite il nome logico "windows" si fa inviare
l'istanza di Cliente e ne stampa il valore di una proprietà valorizzata da PutCliente.

PutCliente e GetCliente non necessariamente devono essere sulla stessa macchina


potete provare l'esercizio mettendo Jndi , PutCliente e GetCliente su tre macchine
diverse, ovviamente per qualsiasi prova dovete mettere un idoneo indirizzo IP, utente e
password. In questo esercizio ho impostato in maniera che tutto sia sulla stessa
macchina (indirizzo IP = localhost) ed ho usato un utente accreditato "system" con
password "weblogic". PutCliente e GetCliente devono avere nel CLASSPATH la classe
Cliente, volendo GetCliente può avere solo una interfaccia di tipo Cliente. (per problemi
di casting)

Dopo aver lanciato la classe PutCliente, se controllate il vostro JNDI Tree troverete:

12
Risorse:

- JNDI tutorial - http://java.sun.com/products/jndi/tutorial/index.html


- JNDI esempi e documenti - http://java.sun.com/products/jndi/docs.html
- JNDI link principale - http://java.sun.com/products/jndi/

13
6 JDBC
Di JDBC base ne parlerò poco, lo do per scontato, quello che dobbiamo vedere in
particolare è il package - JDBC 2.0 Standard Extension API (JDBC 2.0 Optional Package
API) - ma cerchiamo di fare un riassunto di come scrivere una classe generica in grado
di connettersi ad un qualsiasi database.

Intanto è opportuno ricordare che se scriviamo una classe con l'intento di farle fare
delle interrogazioni SQL ad un database, dobbiamo necessariamente indicare a quella
classe quale JDBC driver deve utilizzare per interrogare la base di dati voluta.

Solo nel caso in cui io voglia utilizzare ODBC (Open DataBase Connectivity - Microsoft),
allora non dovrò scaricare nulla in quanto le classi di cui ho bisogno le trovo all'interno
della J2SE. Anche se sono classi generiche e poco performanti ed è sempre consigliabile
utilizzare un JDBC Driver di terze parti.

Se non dobbiamo connetterci tramite una fonte ODBC, allora devo andare sul sito di
quel produttore di Database e scaricarmi da li i driver JDBC (se esistono) di quel
Database. Una volta scaricati li renderò disponibili alla mia classe con un opportuno
CLASSPATH.

La SUN ha provveduto ad indicare quali sono tutti i produttori di JDBC Driver a questo
indirizzo:
http://industry.java.sun.com/products/jdbc/drivers

I driver JDBC sono divisi in 4 Classi:

Classe 1: sono i driver JDBC-ODBC.


Classe 2: sono i driver che si aspettano di trovare sulla macchina su cui sono utilizzati
uno strato di software, scritto in linguaggio nativo per quella Macchina/Piattaforma, i
quali si preoccupano di connettersi e di operare sul Database. (nel caso di Oracle i
driver in questione vengono chiamati OCI, Oracle callback Interface, e vogliono che
sulla macchina su cui girano ci sia installato SQL-Net)
Classe 3: sono i driver che si aspettano di trovare un Gateway, o in generale un server
a cui connettersi il quale provvederà a ritornare una connessione dal Database. (i JDBC-
RMI driver sono di classe 3)
Classe 4: sono i driver totalmente scritti in Java ed autonomi. (Nel caso di Oracle
vengono chiamati THIN). stanno uscendo anche quelli per SQL-Server

Una volta che ho scritto una classe in grado di connettersi ad un database, quello
stesso codice lo riutilizzerò per qualsiasi altro database, dovrò solamente modificare
due cose:

- Il nome della Factory da utilizzare. (Factory: termine usato per indicare della classi in
grado di creare e gestire connessioni nei confronti di generici server)
- La Url di connessione.

Vediamo ora un esempio in grado di connettersi ad una fonte di Dati ODBC. Cerchiamo
di capire come è scritto il codice ed i passi necessari per farlo funzionare:

Esempio: [da qui puoi scaricare tutto il codice necessario, compreso di file Excel - ProveJDBC.zip]
import java.util.*;
import java.sql.*;

class GenericConnection

14
{
public static void main(String args[])
{
String class_driver = "sun.jdbc.odbc.JdbcOdbcDriver"; //Factory da
utilizzare
String url_connect = "jdbc:odbc:JDBCExcel"; //JDBCExcel=nome DSN da
aprire - Url

Connection DBConnection; // database connection


Statement SQLStatement; // SQL statement
ResultSet rsQuery; // query result set

String str;

try{
Class.forName(class_driver);
}
catch(java.lang.Exception exc){
System.out.println("Class Driver ERROR " + exc.toString());
}

try{
DBConnection = DriverManager.getConnection(url_connect);
SQLStatement = DBConnection.createStatement();
rsQuery = SQLStatement.executeQuery("select * from A:A");
ResultSetMetaData rsmd = rsQuery.getMetaData();
int nCols = rsmd.getColumnCount();
while(rsQuery.next()){
str = "";
for(int ctCol = 0; ctCol < nCols; ctCol++){
str = str + rsQuery.getString(ctCol+1)+" ";
if(str == null) str = "NULL";
}
System.out.println(str);
}
if(rsQuery != null) rsQuery.close();
if(SQLStatement != null) SQLStatement.close();
if(DBConnection != null) DBConnection.close();
} // end try
catch(java.sql.SQLException exc){
System.out.println("SQL ERROR: " + exc.toString());
}
} // end main
} // end GenericConnection

In questo esempio stiamo interrogando tramite ODBC un file Excel, il cui nome su
ODBC è stato settato a "JDBCExcel", alla classe passiamo una Factory ed una URL
idonei per il driver utilizzato. Trattandosi di un file Excel, le interrogazioni SQL
cambiano di poco. Per avere tutti gli elementi della colonna A la select da fare sarà:
"select * from A:A". Per far funzionare questo esempio, dopo aver scaricato il file
ProveJDBC.zip decomprimetelo in c:\> ed eseguite i seguenti passi:

(validi per Windows 2000 Professional, ma molto simili per le altre versioni)
Andiamo nel menu di ODBC:

15
Se clicchiamo su "Origine Dati ODBC" otteniamo:

Qui clicchiamo su Aggiungi:

16
Selezioniamo il Driver Excel come mostrato e premiamo Fine, otterremo:

Nel codice abbiamo dato l'orifgine dati "JDBCExcel" e quindi dobbiamo riportare lo
stesso nome, la descrizione è opzionale, poi cliccando su "Selezione cartella di lavoro"
otteniamo:

17
Basta ora andare a selezionare il nostro file prova.xls e poi premere OK, ci apparirà la
seguente finestra:

Premiamo Ok ed assicuriamoci che il nostro File Excel sia integro e del tipo:

18
A questo punto basta andare nella Directory dell'esercizio e lanciare la classe
GenericConnection ed otteremo il seguente risultato:

Se vogliamo possiamo installare Oracle su una macchina che sia in rete con la nostra,
sulla nostra macchina nel CLASSPATH inseriamo il package per Oracle classes12.zip
(driver jdbc THIN di classe 4), e modifichiamo a questo punto la Factory e la Url in
questo modo:

- Factory: "oracle.jdbc.driver.OracleDriver"
- URL: "jdbc:oracle:thin:utente/password@ip_macchina:porta:sid"
esempio : "jdbc:oracle:thin:scott/tiger@mioIP:1521:orcl" (dati standard di default per
Oracle)

Possiamo a questo punto fare per esempio la query "select * from cat".

19
7 CLOUDSCAPE UN DATABASE LEGGERO E TUTTO IN JAVA
Cloudscape, purtroppo, non è più scaricabile dal suo sito http://www.cloudscape.com , da
quando è stato acquisito dalla IBM.
Prima era della Informix, ma se provate a fare http://www.informix.com, finite sul sito della
IBM.
Il sito di Cloudscape dice che se gli sviluppatori gli mandano una mail loro (IBM) inviano
informazioni per come averlo e svilupparci, io l'ho mandata da circa un mese e non ho
avuto ancora la risposta.

A parte questi problemi c'è da dire che se scaricate la J2EE1.3 dal sito della SUN e la
installate, troverete al suo interno Cloudscape 4.0 cioè l'ultima versione.
Quello che manca è un programmino grafico per gestire il tutto che si chiama
Cloudview, potete però scaricare il necessario da :

http://cloudweb1.cloudscape.com/support/downloads.jsp

in particolare ci serve:
- cloudview.jar
- jh.jar

Immaginiamo di aver installato la J2EE 1.3 in e:\> allora avremo come root directory:
E:\j2sdkee1.3

Le classi di Cloudscape le troviamo in :


E:\j2sdkee1.3\lib\cloudscape

Qui dobbiamo aggiungere cloudview.jar e jh.jar.

Andiamo ora a modificare il CLASSPATH e per farlo modifichiamo il file che ha


preimpostato la SUN nella J2EE 1.3 che si chiama setenv.bat e si trova in:
E:\j2sdkee1.3\bin

Aggiungiamo in setenv.bat la seguente riga:


set
CLASSPATH=%CLOUDSCAPE_INSTALL%\cloudview.jar;%CLOUDSCAPE_INST
ALL%\jh.jar

Se andiamo con una finestra del prompt DOS in E:\j2sdkee1.3\bin e dopo aver
lanciato setenv lanciamo il seguente comando:

java COM.cloudscape.tools.cview

Otteniamo la segente finestra:

20
Da qui possiamo creare tabelle, inserire dati etc.

La cosa bella di Cloudscape è che è scritto in Java e quindi è multipiattaforma, è molto


leggero infatti tutte le sue librerie messe insieme pesano meno di 3MB, non ha un
server che rimane in ascolto ma simula il tutto scrivendo in una directory che
rappresenta il Database, é standard per quanto riguarda SQL e da pieno supporto a
JDBC2.0 ed alle transazioni distribuite.

Lo si incontra spesso, se per esempio scaricate ed installate AS quali SilverStream o


Bea WebLogic, al loro interno lo trovate, e con le opportune modifiche al CLASSPATH
lo potete utilizzare alla stessa maniera che ho descritto in precedenza.

Lo useremo spesso nel libro, soprattutto quando arriveremo al punto di dover testare
degli EJB di Tipo Entity, io prepareò il database lo zipperò e voi semplicemente
scaricandolo e decomprimendolo potrete far funzionare il tutto.

Per ora prendeteci confidenza, sul sito della Cloudscape trovate tutta la
documentazione e gli esempi che volete.

Nel caso dell'esempio descritto nel capitolo JDBC, per farlo funzionare con Cloudscape,
dovete prima creare un Database e se lo chiamate MioDb e lo salvate in c:\>
scriverete:

- Factory: "COM.cloudscape.core.JDBCDriver"
- URL: "jdbc:cloudscape:c:/MioDB"

Le query sono standard ed ovviamente dipendono dalle tabelle che avete creato.

Tra poco vedremo come settare dei DataSource su un AS in grado di passarci delle

21
connessioni nei confronti di Cloudscape (ovviamente il discorso sarà identico per gli altri
Database).

8 JDBC 2.0
Ormai le JDBC API sono arrivate alla versione 3.0, ma dovendo noi parlare di AS ed
essendo la maggior parte degli AS certificati sulla J2EE 1.2, ed avendo al loro interno le
JDBC API 2.0, ho deciso di soffermarmi per ora su di loro.
Al momento giusto aggiornerò questo capitolo.

Le JDBC 2.0 API sono state divise in due sezioni :

- JDBC 2.0 Core Api


- JDBC 2.0 Standard Extension API (JDBC 2.0 Optional Package API)

Possono essere scaricate da:

http://java.sun.com/products/jdbc/download.html

Le principali novità introdotte nelle API di JDBC 2.0 sono:

- I DataSource, per gestire le connessioni di un Database tramite JNDI.


- I Pool di Connessioni
- Il supporto alle Transazioni Distribuite tramite gli XADataSource
- RowSet, un Java Bean serializzabile molto simile al Resultset ma che risolve grandi
problemi.

Tutte queste novità sono facilmente utilizzabili tramite un AS e risolvono problematiche


di architetture distribuite e di ottimizzazione nella gestione delle connessioni.

Inoltre le JDBC 2.0 Standard Extension API, fanno riferimento a tre implementazioni di
RowSet (CachedRowSet, JDBCRowSet e WebRowSet di questi parleremo molto
avanti e faremo esempi) che sono in un package separato che si scarica da:

http://developer.java.sun.com/developer/earlyAccess/crs/

per scaricarlo bisogna essere iscritti a JDC (iscrizione gratuita), fa parte dei prodotti
Early Access.

22
8.1 POOL DI CONNESSIONI
Immaginiamo di aver creato un database con Cloudscape, di averlo chiamato demo
(creato in c:\>), ed immaginiamo di voler creare un Pool di Connessioni nei suoi
confronti e farlo gestire da un AS quale Bea WebLogic 6.0

Tramite una console abbiamo semplicemente indicato quale Factory usare e quale Url
di connessione, con opportune properties per il driver, in questo caso un utente ed una
password (none=nessuna) che hanno le autorizzazioni di aprire delle connessioni sul
database demo di Cloudscape.

Trattandosi di un Pool, posso settare altri parametri all'AS per gestire un certo numero
di connessioni al meglio:
Initial Capacity: In questo caso ho detto all'AS che alla partenza deve aprire una
connessione (ovviamente per quanto detto prima con utente e password none, ed il
numero di partenza può essere alto quanto si vuole)
Maximum Capacity: qui ho definito che il numero massimo di connessioni che
possono essere aperte sul DB demo sono 2, il numero può essere molto più alto ma
per problemi di licenze con Cloudscape bisogna limitarlo.
Capacity Increment: se la richiesta di connessione dovesse essere maggiore delle
connessioni aperte, ne vengono aperte altre a seconda del numero che qui definisco,
ma in ogni caso non posso superare la Maximum Capacity.

A seconda di cosa fanno gli utenti, apertura e chiusura delle connessioni, l'AS gestisce il
tutto in maniera trasparente ed al meglio.

Per ottenere una connessione da questo Pool, riferito al DB demo e chiamato nella
console di WebLogic demoPool, devo definire un DataSource.

8.2 DATASOURCE
JDBC 2.0 ha introdotto i DataSource per slegare l'applicazione dalla Factory e dalla URL
di connessione, e legare il codice solo ad un semplice nome logico a cui far riferimento
su JNDI.
Abbiamo imparato che se in JNDI c'e' un nome logico, ci deve essere a lui associato un
Oggetto.
Gli Oggetti associati a questi nomi logici, sono chiamati DataSource ed altro non sono
che degli oggetti che implementano l'interfaccia javax.sql.DataSource.
I DataSource sono delle Factory, termine usato per indicare della classi in grado di
creare e gestire connessioni nei confronti di generici server, per le connessioni di JDBC.

24
Avendo a disposizione un AS, sarebbe ingenuo (anche se consentito), connettersi ad
una base di dati i maniera tradizionale piuttosto che configurare ed utilizzare un
DataSource

Configurare un DataSource è spesso una operazione molto semplice (non si scrive


codice), come nel caso di questa console di Bea WebLogic 6.0, basta dire quale sarà il
nome logico JNDI del dastasource che verrà messo come oggetto appunto in JNDI
(operazione effettuata dall'AS).

Abbiamo creato un DataSource che nella console appare con il nome DSdemoPool, con
il JNDI name DSdp ed associato al Pool che in console appare con il nome demoPool.

Se apriamo la finestra di JNDI ed andiamo a vedere gli oggetti di cui è stato fatto il Bind
troveremo anche un oggetto associato al nome logico DSdp :

25
A questo punto una Servlet, una JSP, un Java Bean o un EJB, se vogliono possono
connettersi al Database demo creato con Cloudscape tramite questo DataSource
DSdp.

Ottenere una connessione a questo punto è molto semplice, basta inserire queste
poche righe di codice, il resto è identico a quanto detto nella sezione JDBC:

import java.sql.*;
import javax.naming.*;
...
try {
Context context = new InitialContext();
javax.sql.DataSource ds =(javax.sql.DataSource)context.lookup(DSdp);
Connection connection = ds.getConnection();
...
connection.close();
}catch (Exception e) { System.out.println("Error:" + e); }
...

Dalla console posso cambiare Pool di connessione, cambiare addirittura il produttore di


Database, ovviamente se la struttura di dati non cambia e lascio lo stesso JNDI name,
la mia applicazione non ne risente.

Chiedendo una connessione come nel caso di sopra, la prendo dal Pool, il quale
immediatamente decide se aprirne altre o meno a seconda del carico e dei paramentri
settati, quando eseguo la riga connection.close(); rilascio semplicemente la
connessione al Pool, che a sua volta decide se tenerla o chiuderla a seconda del carico e
delle richieste.
Andando avanti nel libro verranno presentati degli esempi completi al riguardo.

8.3 XADATASOURCE
Abbiamo detto che una delle principali novità introdotte nelle API di JDBC 2.0 è il
supporto alle Transazioni Distribuite tramite gli XADataSource.

26
Supponiamo di dover risolvere il seguente problema:

Connettersi dall'Italia al nostro conto in Banca in Svizzera, da cui dobbiamo prelevare


dei soldi per depositarli nel conto in banca in Italia.

Immaginiamo di risolvere il problema creando una servlet in grado di connettersi ai due


Database tramite due Datasource.

Questa è l'architettura che abbiamo visto fino ad ora ed è valida ma ha il problema che
se per caso non riusciamo a depositare i soldi in Italia ed avviene una ROLLBACK ci
dobbiamo preoccupare di andare a rimettere i soldi nel conto in Svizzera, cioè la
Transazione fa il ROLLBACK limitatamente ad una connessione, quella fallita.

Un DataSource quindi gestisce la transazione limitatamente al DB a cui il suo Pool di


riferimento è configurato.

27
Quindi se l'architettura rimane la stessa ma configurassimo degli XADataSource, con
poche righe di codice risolviamo il problema.

Per sapere se un driver JDBC2.0 o superiore supporta le transazioni distribuite, basta


vedere scompattandolo se al suo interno e' presente la classe XADataSource.

Ecco un esempio di come impostare un XADataSource:

Come sempre dobbiamo settare prima un Pool di connessioni, ma qui dobbiamo


indicare la classe XADataSource, come nel caso di cloudscape che dobbiamo scrivere
COM.cloudscape.core.XaDataSource ed in più dobbiamo impostare opportune
properties (che ci verranno indicate dai produttori dei driver):

Ovviamente in questo esempio stiamo supponendo che i due database siano DB_I e
DB_S creati con cloudscape in c:\.
Dopo aver settato il Pool di connessioni sia per DB_I che per DB_S dobbiamo impostare
i DataSource Transazionali che nel caso di WebLogic vengono chiamati Tx Data
Source, che dal punto di vista della console altro non sono che dei nomi logici da
mettere in JNDI riferiti ai Pool settati con la classe XADataSource:

28
A questo punto possiamo scrivere il codice per testare il tutto.

Ecco un esempio: [TxServlet.java]


import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import java.sql.*;
import javax.sql.*;
import javax.transaction.*;

public class TxServlet extends HttpServlet {

public void service(HttpServletRequest req,


HttpServletResponse res) throws IOException {

Connection con_s = null;


Connection con_i = null;

try{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory" );
env.put(Context.PROVIDER_URL, "t3://localhost:7001");

29
InitialContext ic = new InitialContext(env);

UserTransaction ut =
(UserTransaction)ic.lookup("javax.transaction.UserTransaction");

ut.begin();
DataSource ds_s = (javax.sql.DataSource)
ic.lookup("Bank_S_DSTX");
con_s = ds_s.getConnection();
//eseguo le mie operazioni con con_s

DataSource ds_i = (javax.sql.DataSource)


ic.lookup("Bank_I_DSTX");
con_i = ds_i.getConnection();
//eseguo le mie operazioni con con_i
ut.commit();

}catch(Exception e){
System.out.println("Error " + e);
}

res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("Prova Tx in Servlet.");
out.close();

//chiudo le mie connessioni


}

Tutto è come sempre le righe nuove sono quelle relative alla UserTransaction, di cui
parleremo molto in seguito a proposito delle Transazioni all'interno di un EJB.

In ogni caso sappiate che la UserTransaction viene messa in JNDI in automatico alla
partenza degli AS.

E' proprio la UserTransaction che gestisce la commit o la rollback su tutti i


Resource Manager (Database nel nostro caso) coinvolti.

Se infatti una qualsiasi operazione fallisce nella nostra architettura, la UserTransaction


fa fare la RollBack a tutte le entità coinvolte.

30
Per maggiori informazioni su UserTransaction vi consiglio di leggere le specifiche di
JTA, verranno trattate comunque in seguito.

Per capire come mai il codice della lookup non cambia basta guardare i seguenti due
schemi presi dalle specifiche di JDBC2.0:

...
DataSource ds = (javax.sql.DataSource) ic.lookup("JNDI_NAME");
con = ds.getConnection();
...

Questo avviene sia nel casi di DataSource che di XADataSource

31
32
Possiamo vedere infatti che noi nel nostro Application Code (sorgente) facciamo sempre
la lookup alla stessa maniera, ma in un caso ci viene data una connessione e nell'altro
una con supporto transazionale tramite specifica XA.

E' l'AS che al momento della creazione del DataSource o XADataSource si preoccupa di
settare il tutto nella maniera opportuna.

Quindi un XADataSource deve essere settato sia per risolvere architetture come sopra
descritto sia se il chiamante e/o utilizzatore è una entità transazionale come per
esempio un EJB, situazione che descriveremo proveremo e commenteremo nei prossimi
capitoli.

8.4 ROWSET
RowSet nasce con le specifiche JDBC2.0 e come abbiamo già accennato nel capitolo
jdbc2.0 si tratta di un componente di tipo JavaBeans™, che ne segue il suo modello e la
sua gestione degli eventi.

Un RowSet, che estende un ResulSet, può essere riempito a partire da un ResulSet,


ed in più seguendo la gestione degli eventi dei JavaBeans, può notificare eventi ad altri
componenti all'interno di una applicazione, come per esempio la modifica dei suoi
valori.

33
Essendo un JavaBeans può essere "configurato" tramite un ambiente visuale di
modellazione di Beans, e può essere associato non solo a tabelle di un Database ma
anche ad altri sistemi come spreadsheet.

Quindi è facile crearsi una propria implementazione di un RowSet, per risolvere le varie
problematiche, soprattutto all'interno di architetture distribuite.

La SUN ha creato tre implementazioni di RowSet:

-- CachedRowSet : si tratta di un rowset "disconnesso" che mantiene i propri dati in


memoria; non è ottimale per trasportare grossi quantitativi di dati ma è ottimo per
architetture distribuite in quanto è serializzabile, per query controllate. Si usa anche
nei confronti di Client Java "leggeri" come per esempio i Palmari.
In seguito faremo funzionare un client che invoca un metodo di un EJB, questo metodo
lancia una query, popola il rowset con i dati del ResulSet chiude la connessione e
manda remotamente un CachedRowSet , il client lo scorre e gli fa delle modifiche e lo
riinvia all'EJB, il quale con un semplice trucco è in grado di riconnettersi ed apportare
alla base di dati tutte le modifiche che ereano state fatte dal client in Remoto, in questa
maniera le connessioni sono ottimizzate al massimo.

34
-- JDBCRowSet: si tratta di un rowset che lavora con connessione aperta, ma da tutti i
vantaggi aggiuntivi tipici del rowset, se non ci bastano le funzioni di un ResulSet,
possiamo utilizzarlo, per la sua serializzazione o per la sua possibilità di gestire gli
eventi, ricordandoci che segue il modella dei JavaBeans.

-- WebRowSet:rowset che lavora in un contesto Http. Si tratta di un rowset che lavora


a connessione chiusa nei confronti del DataBase, ma necessita di una connessione di
tipo http. Tipico utilizzo è nelle applicazioni web che devono inviare dati ai browser. La
libreria che possiamo scaricare dal sito della SUN comprende la classe WebRowset, la
servlet, ed il protocollo per trasmettere i dati che è bastato su XML, sono presenti
infatti due classi XmlWriter e XmlReader, che spesso risolvono molti problemi in quei

35
contesti in cui vogliamo che remotamente venga lanciata una query e che il risultato ci
venga inviato in formato XML.

Ricordo che le specifiche, da cui ho preso queste immagini ed il package con le tre
implementazioni del RowSet con tanto di documentazione ed esempi possono essere
scaricate dai link che ho riportato nel capitolo JDBC2.0.
Vedrete che il loro utilizzo è semplice e tutto è perfettamente documentato.

36
9 PARADIGMA M.V.C. (MODEL VIEW CONTROLLER)
Introduco ora il concetto di Paradigma M.V.C e cercherò di farlo nella maniera più
semplice possibile tanto per far capire cosa è e quale è il suo vantaggio nell'adottarlo
soprattutto nel contesto di architetture distribuite basate su web application.

Partiamo da un' errore per arrivare all'MVC:


Immaginiamo di dover far scambiare un messaggio fra due classi, cioè una classe deve
invocare il metodo di un'altra per prenderle un valore e settarselo al suo interno,
immaginiamo di risolvere il problema scrivendo le classi Model e View nella segente
maniera:

Model.java:

public class Model {

private int val;

public void setVal(int val){


this.val=val;
}

public int getVal(){


return val;
}
}
View.java:
public class View {

private Model model = new Model();


private int val;

public void setVal(int val){


this.val=val;
}

public int getVal(){


return val;
}

public void scambiaVal(){


val=model.getVal();
}
}
In UML:

Tutto quello che abbiamo scritto funziona, risolve il problema ma è sbagliato


"formalmente" per i seguenti motivi:
la programmazione ad oggetti dice che il suo obiettivo è quello di far progettare Classi

37
che possano vivere autonomamente in un qualsiasi altro contesto, e che possano
essere riutilizzabili. Nel nostro caso la classe View per parlare con Model ha dovuto
dichiarala al suo interno ed ha creato un legame con lei. Se in un altro progetto mi
dovesse servire la classe View, devo portarmi dietro la classe Model, oppure devo
modificare la classe View, e questo non è accettabile ed in contrasto con la filosofia
della programmazione ad oggetti.

Ma se riprogetto le mie classi aggiungendo anche una nuova classe Controller:

Model.java:

public class Model {

private int val;

public void setVal(int val){


this.val=val;
}

public int getVal(){


return val;
}
}
View.java:
public class View {

private int val;

public void setVal(int val){


this.val=val;
}

public int getVal(){


return val;
}
}
Controller.java:

public class Controller {

private Model model = new Model();


private View view = new View();

public void scambiaVal(){


view.setVal(model.getVal());
}
}
In UML:

38
In questa situazione risolviamo il problema come prima, ma mentre prima avevamo
due classi dipendenti ora abbiamo tre classi di cui due indipendenti, quindi secondo la
filosofia ad oggetti è meglio. Abbiamo creato una classe chiamata Controller, e
l'abbiamo resa dipendente, quindi sicuramente non la riutilizzeremo in un altro
progetto. I Controller sono le classi più importanti dei progetti, sono quelle che hanno la
logica applicativa per risolvere le specifiche problematiche dei progetti, utilizzano nel
modo più opportuno ai loro scopi le classi Model e View, che a loro volta dovrebbero
essere :

-- Model: una classe in grado di gestire i dati.


-- View: una classe in grado di rappresentare i dati

Anche non volendo spesso nelle nostre applicazioni ci sono sempre due Controller e
dovrebbero essere sfruttati come tali e considerali le Classi "sacrificabili" cioè quelle che
sicuramente non riutilizzeremo in altri contesti, questi Controller tipici sono:
-- La Classe contenente il Main
-- Le Classi GUI (Graphical User Interface), quelle che costruiscono le interfacce
grafiche.
-- Nei contesti Web le Servlet sono dei Controller che vedremo in seguito.

In definitiva il controller dice al Model come gestire i dati e dice alla view come
rappresentarli, passando i dati dal model alla view.

Queste sono regole di base per impostare l'analisi ed il successivo disegno delle Classi
della nostra applicazione.

A questo punto dobbiamo vedere come sia possibile applicare il paradigma MVC alle
applicazioni WEB, quali sono i vantaggi e quali ruoli ricoprono i componenti.

Applicazione dell'MVC alle Web Application

In una WEB Application, il paradigma MVC si applica secondo il seguente schema:

39
Controller = Servlet
Model = JavaBean
View = JSP

Ovviamente ci sono piccole differenze, ma queste sono dovute al tipo di architettura


anche se l'idea di fondo è la stessa.

La servlet solitamente prende i dati inviati dal Browser, istanzia un JavaBean e gli
valorizza le proprietà di classe con i valori presi, poi richiama dei metodi del JavaBean
in maniera che con le proprietà di classe passate il JavaBean sia in grado di ottenere ed
elaborare dei dati. Successivamente la Servlet fa un forward della request ad una JSP
che si deve preoccupare di farsi passare i dati di interesse dal JavaBean dargli un layout
e rappresentarli a video.

Ovviamente dipende dai tipi di applicazioni e dalla capacità analitica delle singole
persone se è il caso di applicare una tale architettura o meno.

Una tale architettura rende possibile dividere la logica applicativa dalla sua
rappresentazione, cioè permette di slegare chi fa HTML da chi produce Codice Java,
tutti possono lavorare contemporaneamente. I programmatori non si devono più
preoccupare di scrivere servlet con centinaia di righe HTML, e chi fa HTML non si deve
preoccupare di conoscere Java. (Ovviamente all'inizia si devono prendere delle
convenzioni, fissate dall'analista)

Il Model solitamente è colui che si connette ai DataBase, ad EJB etc.

Se in produzione ci dicono di dover cambiare la pagina html in ingresso o dei risultati,


per magari aggiungerci un Banner o delle promozioni, basta prendere un editor e
modificare o l'HTML o la JSP. Alla chiamata successiva di quella funzione la JSP in
automatico viene ricompilata e l'utente vedrà la nuova versione, i vantaggi sono che
non abbiamo fermato il servizio e che non abbiamo dovuto toccare il codice di Logica.

A questo punto non ci resta che scrivere una WEB Application basata su tale
architettura, lo faremo nei prossimi capitoli.

ESEMPIO:
Nei prossimi capitoli scriveremo una web Application basata su paradigma MVC,
composta da una pagina HTML, una Servlet, un JavaBean ed una JSP. Vogliamo fare

40
una pagina HTML in grado di inviare in modalità POST due valori alla Servlet, la quale li
passerà ad un JavaBean che avrà istanziato, ne richiamerà un metodo che calcola la
moltiplicazione dei due valori. infine la Servlet farà il forward della request ad una JSP
che si farà passare dal JavaBean il risultato della moltiplicazione e lo rappresenta a
video. Vogliamo che lo scope del JavaBean sia "request" e cioè che il JavaBean sia in
vita fino a quando è in vita la request.

Esempio INPUT:

Esempio OUTPUT

Se volete approfondire:
http://developer.java.sun.com/developer/onlineTraining/JSPIntro/contents.html#JSPIntro4

9.1 SERVLET
Per scrivere quanto abbiamo descritto alla fine del capitolo MVC seguiremo inoltre le
regole di creazione di un File WAR (Web Archive).

Creiamo in c:\ la directory MioWar ed al suo interno mettiamo una pagina Html che
chiameremo ServletInput.html quindi
c:\MioWar\ServletInput.html
[download ServletInput.html]

creiamo a questo punto la servlet che istanzierà un JavaBean che costruiremo nel
prossimo capitolo:
[download Servlet.java]

41
package mvc;

import mvc.JavaBean;

public class Servlet extends javax.servlet.http.HttpServlet {

public void doPost(javax.servlet.http.HttpServletRequest request,


javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException,
java.io.IOException {

javax.servlet.ServletContext sc;
javax.servlet.RequestDispatcher rd;

try
{
mvc.JavaBean javaBean = null;

// instantiate the bean.


javaBean = new mvc.JavaBean();

request.setAttribute("javaBean", javaBean);

// Initialize the bean x property


javaBean.setX(Integer.parseInt(request.getParameter("x")));

// Initialize the bean y property


javaBean.setY(Integer.parseInt(request.getParameter("y")));

// Invoke moltiplica action on the bean.


javaBean.moltiplica();

sc = getServletContext();
rd = sc.getRequestDispatcher("/ServletResults.jsp");
rd.forward(request, response);
}
catch(Throwable theException){
// Gestione errore
}
}
}//end Servlet
la servlet dichiara il package mvc e deve essere messa in:

C:\MioWar\WEB-INF\classes\mvc\Servlet.java

Siccome abbiamo detto che il JavaBean deve avere uno scope request abbiamo scritto:
request.setAttribute("javaBean", javaBean);

9.2 JAVABEANS
Continuiamo a scrivere quanto abbiamo descritto alla fine del capitolo MVC seguendo
inoltre le regole di creazione di un File WAR (Web Archive).

Dopo aver creato la Servlet, passiamo a creare il JavaBean:


[download JavaBean.java]
package mvc;

public class JavaBean {

private int x=0;


private int y=0;

42
private int risultato=0;

public JavaBean() {
super();
}

public void setRisultato(int newRisultato) {


risultato = newRisultato;
}

public int getRisultato() {


return risultato;
}

public void setX(int newX) {


x = newX;
}

public int getX() {


return x;
}

public void setY(int newY) {


y = newY;
}

public int getY() {


return y;
}

public void moltiplica() {


risultato = x*y;
}

}
Il JavaBean dichiara il package mvc e deve essere messo in:

C:\MioWar\WEB-INF\classes\mvc\JavaBean.java

Le regole di creazione di un JavaBean sono semplici:

-- Deve avere il costruttore di Default.


-- le proprietà devono essere scritte con le lettere minuscole.
-- Gli accessori delle proprietà, cioè i metodi get e set delle proprietà devono iniziare
con get e set minuscole e poi il nome della proprietà con la prima lettera Maiuscola, se
per esempio ho la proprietà nome avrò getNome() e setNome(...).

9.3 JSP (JAVA SERVER PAGE)


Continuiamo a scrivere quanto abbiamo descritto alla fine del capitolo MVC seguendo
inoltre le regole di creazione di un File WAR (Web Archive).

Dopo aver creato la Servlet ed il JavaBean, passiamo a creare la JSP che chiameremo
ServletResults.jsp:
[download ServletResults.jsp]

<HTML>
<HEAD></HEAD>
<BODY>

43
<jsp:useBean id="javaBean" class="mvc.JavaBean"
scope="request"></jsp:useBean>

<TABLE border="0">
<TR>
<TD>risultato</TD>
<TD>
<%=javaBean.getRisultato() %>
</TD>
</TR>
</TABLE>

</BODY>
</HTML>

Dobbiamo copiare la JSP ServletResults.jsp nella directory:

c:\MioWar\ServletResults.jsp

Soffermiamoci ora a parlare della riga:


<jsp:useBean id="javaBean" class="mvc.JavaBean" scope="request"></jsp:useBean>

scope="request", altri valori che posso scrivere allo scope sono


"page|request|session|application" a seconda del valore che noi diamo diciamo che
l'istanza della classe class="mvc.JavaBean" a cui abbiamo dato il nome id="javaBean"
rimarrà in vita fino a quando rimane in vita la "request".

-- page : Istanza della classe del JavaBean in vita fino a quando la pagina JSP è in vita,
quindi appena ha risposto e mandato un output al client, l'istanza viene deallocata dalla
memoria.
-- request : L'istanza del JavaBean rimane in vita fino a quando è in vita la request di
riferimento, quindi fino a quando c'e' un Forward della request
-- session : L'istanza del JavaBean è legata ad una sessione, e quindi rimane in vita
fino a quando è in vita quella sessione di riferimento del client.
-- application : L'istanza del JavaBean rimane in vita fino a quando è in vita quella
Web Application, quindi fino a quando non fermo la web Application

La jsp se non trova l'istanza del JavaBean, si preoccupa lei di farne una ed è per quello
che il JavaBean deve avere il costruttore di Default, perchè il tag <jsp:useBean ...> per
fare l'istanza della classe mvc.JavaBean e chiamarla javaBean, invoca il costruttore di
Default.

Vediamo tre modi di scrive ed utilizzare il Tag <jsp:useBean ...> in particolare vediamo
come sia possibile lavorare con i metodi e le proprietà del JavaBean tramite queto Tag:

Modo 1:
Chiamo i mtodi dell'istanza e gli passo i valori.
<%@ page import = "mvc.JavaBean" %>
<jsp:useBean id="javaBean" class="mvc.JavaBean" scope="request"/>
</jsp:useBean>
<%
javaBean.setX(20);
javaBean.setY(3);
%>

Modo 2:
All'interno del tag dico che setto delle proprietà del'oggetto javaBean in particolare in
questo esempio setto il valore 20 alla proprietà x del JavaBean istanziato con nome
javaBean della classe mvc.JavaBean.

44
<%@ page import = "mvc.JavaBean" %>
<jsp:useBean id="javaBean" class="mvc.JavaBean" scope="request"/>
<jsp:setProperty name="javaBean" property="x" value="20"/>
</jsp:useBean>

Modo 3
E' il modo più sfruttato, anche perchè da un grande valore aggiunto, qui diciamo che
tutte quelle proprietà che mi arrivano da una request che hanno l'esatto nome delle
proprietà all'interno del JavaBean (nel nostro caso l'istanza javaBean della classe
mvc.JavaBean) devono essere settate in automatico:
<%@ page import = "mvc.JavaBean" %>
<jsp:useBean id="javaBean" class="mvc.JavaBean" scope="request"/>
<jsp:setProperty name="javaBean" property="*"/>
</jsp:useBean>

Tenedo conto di quest'ultima modalità capiamo come mai spesso incontriamo


architetture del tipo:

Se in effetti progettiamo una pagina html che ha dei campi di inserimento con dei nomi
identici ai nomi delle proprietà del JavaBean, quando per esempio in modalità post,
richiamiamo una jsp e gli passiamo tali valori, la jsp con tre semplici righe, quelle del
tag <jsp:useBean ...> è in grado di creare una istanza del JavaBean ed allocare tutte
quelle sue proprietà che hanno lo stesso nome delle proprietà inviate nella request, non
devo piu scrivere codice come nelle servlet per prendere le proprietà e convertirle nel
tipo esatto dichiarato dal JavaBean.

45
Quest'ultima architettura non è sbagliata, ma la si consiglia per applicazioni di piccola
entità. Tra l'altro architetture di questo tipo rendono il codice complicato e poco
leggibile.

E' sempre consigliato di mettere una Servlet in ingresso ed una JSP in uscita,
soprattutto per questioni di performance.

46
10 WAR (WEB ARCHIVE)
Abbiamo ormai scritto tutti i componenti necessari per una Web Application basata su
paradigma MVC, vogliamo imparare a creare moltiplicazione.war .

Abbiamo realizzato quanto descritto alla fine del capitolo MVC , cioè :
-- HTML + Servlet
-- JavaBean
-- JSP

Abbiamo messo tutto in opportune directory:

JSP e HTML: c:\MioWar


Servlet e JavaBean: C:\MioWar\WEB-INF\classes\ da qui poi abbiamo creato le
directory relative ai package.

Vediamo quindi perchè abbiamo deciso di creare queste directory, parliamo quindi di
cosa è un WAR.

Un War, nato con la specifica delle servlet 2.2, è un Web Archive, un War si crea con il
tool jar.exe che trovate nella sotto directory bin di dove avete installato il vostro JDK.
Se per esempio avete installato il jdk 1.3.1 su una piattaforma Windows in c: troverete
il Jar.exe nel seguente percorso:
c:\jdk1.3.1\bin\jar.exe
All'interno di un file.war posso inserire i seguenti componenti:
- file html ed immagini
- Jsp ed Applet
- Servlet e Java Bean
- Librerie java

All'interno di un file.war non posso inserire Enterprise Java Bean (Ejb), più avanti
parleremo di ejb in file jar e di ejb e war in file ear.

War = web archive


Jar = Ejb (in un jar posso mettere anche più ejb)
Ear = Enterprise Archive, in un ear posso mettere jar (ejb) insieme a war

Di jar per ejb e di ear, parleremo in seguito.

Questi sono alcuni link per chi vuole avere maggiori informazioni:

Servlet:
http://java.sun.com/products/servlet/index.html

Specifica Servlet 2.2:


http://java.sun.com/products/servlet/download.html

Tutorial:
http://developer.java.sun.com/developer/technicalArticles/Servlets/servletapi/

Le regole del War sono:


1) si crea una directory con un nome a piacere, noi abbiamo scelto c:\MioWar questa
per noi ora è come se fosse la root di un http server e li ci possiamo mettere tutti quei
file che possono essere usati da un http server con gestione delle jsp, come le pagine
html, le immagini, le jsp e le applet.
2) sotto c:\MioWar devo creare la sottodirectory WEB-INF quindi avrò
C:\MioWar\WEB-INF , in questa directory deve essere presente un file chiamato
web.xml che tra poco vedremo ed in seguito approfondiremo.
3)Sotto WEB-INF posso creare due directory classe e lib, quindi posso avere :

47
C:\MioWar\WEB-INF\classes
C:\MioWar\WEB-INF\lib
classes è la directory sotto cui posso mettere le Servlet ed i JavaBean, nel nostro caso
facendo parte sia la servlet che il JavaBean del package mvc abbiamo creato sotto
classes la directory mvc. classes è inserita per questa web Application nel classpath.
lib è la directory in cui posso mettere le librerie che voglio che siano viste da questa
Web Application, ci si mettono jar con librerie di supporto per la mia applicazione, non
ci posso mettere jar di EJB. Nel nostro caso non abbiamo bisogno di lib.

Dobbiamo aggiungere il file web.xml:


[download web.xml]

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 1.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app>
<servlet>
<servlet-name>Servlet</servlet-name>
<servlet-class>mvc.Servlet</servlet-class>
</servlet>

<servlet>
<servlet-name>ServletResults</servlet-name>
<jsp-file>ServletResults.jsp</jsp-file>
</servlet>

<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>Servlet</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>ServletInput.html</welcome-file>
</welcome-file-list>
</web-app>

In questo file web.xml abbiamo dato degli alias ai nostri componenti, ed abbiamo detto
che nel momento in cui richiamiamo la nostra web applicatio, ci deve essere dato come
pagina iniziale il file ServletInput.html

A questo punto se andate sotto c:\MioWar e lanciate il comando :


c:\MioWar>jar cvf moltiplicazione.war *.*

ottenete:
[download moltiplicazione.war]

48
Proviamolo con Bea WebLogic 6.0:
Se avete fatto una installazione di default, ed avete installato anche gli esempi,
troverete nella directory in cui avete installato WebLogic il seguente dominio (nel mio
caso ho installato Weblogic in e:\)

E:\bea\wlserver6.0\config\examples
copiate il file moltiplicazione.war in
E:\bea\wlserver6.0\config\examples\applications andate poi a lanciare il server
relativo al dominio degli esempi lanciano il file startExamplesServer.cmd che si trova
E:\bea\wlserver6.0\config\examples

Appena l'application Server è partito lanciate il browser e scrivete:

http://localhost:7001/moltiplicazione

vi apparirà:

Se inserite i valori 20 alla x e 3 alla y e premete Submit otterrete:

49
Ovviamente in un Servlet Engine, posso mettere quante Web Application voglio.
Offrono molti vantaggi che vedremo in seguito, tra cui la facilità di consegnare ai miei
clienti una applicazione web rendendo automatica la sua installazione, facendomi
evitare di scrivere specifiche e regole per la corretta installazione e settaggi del
CLASSPATH e di quanto altro serva al corretto funzionamento di tali architetture, senza
trascurare che essendo uno standard funzionerà alla stessa maniera su tutti quei server
che seguono quelle specifiche delle servlet 2.2.

50
11 GESTIONE DELL’ACCESSO CONCORRENTE AD UNA JSP O
SERVLET
Facciamo velocemente una panoramica su uno dei problemi fondamentali delle JSP e
delle Servlet, da molti trascurato e causa dei più grossi problemi nelle architetture
basate su tali componenti, e cioè la gestione di più Thread all'interno di una JSP o di
una Servlet, stiamo quindi parlando della richiesta simultanea di più client a tali
componenti, in definitiva di accesso concorrente di più Thread alle JSP ed alle Servlet.

Dobbiamo sempre ricordarci che una JSP è una Servlet, o per essere precisi, nel
momento in cui viene compilata e messa in memoria è esattamente una servlet.

Lo possiamo anche dimostrare, se avete installato sulla vostra macchina Bea WebLogic,
e lanciate il file setEnv.cmd che trovate nella directory del disco dove avete installato il
prodotto e che sarà bea\wlserver6.0\config\mydomain
potete ora lanciare il comando "java weblogic.jspc -keepgenerated
vostronome.jsp" in quella directory troverete: _vostronome.class e
_vostronome.java se aprite con un editor il file sorgente vedrete che è una servlet.

Quindi nonostante io faccia un esempio su una JSP, il discorso è identico per le Servlet.

Dobbiamo anche ricordare che il ciclo di vita di tali componenti è il seguente:


alla partenza del nostro Servlet Engine, o alla loro prima richiesta (dipende dalla
configurazione prescelta) tali componenti vengono istanziati e messi in memoria.

Di istanza ne viene fatta una sola e rimarrà in memoria fino a quando non fermiamo il
nostro Servlet Engine, alla successiva partenza verrà rifatta una nuova istanza.

La regola generale è che se sappiamo che la nostra JSP/Servlet potrà essere chiamata
da più client contemporaneamente, dobbiamo evitare di mettere al suo interno delle
proprietà di classe.

Se più client chiamano la mia jsp/servlet vuol dire che avrò più thread che percorrono
la mia unica istanza della jsp/servlet, per chi ha lavorato con i thread ha visto che
spesso nonostante un thread parta per primo non è detto che finisca per primo, e nel
caso della nostra unica istanza vorrebbe dire che le sue variabili verrebbero sporcate
dai dati di altri.

Quindi il problema si presenta solo se nel caso di accesso concorrente ad una


jsp/servlet al cui interno sono state dichiarate variabili di classi, vediamo quindi come
risolverlo.

Una Servlet può implemetare "public interface SingleThreadModel" questa


interfaccia senza metodi, assicura che una servlet sia percorsa da un Thread alla volta,
il servlet engine in questo caso sincronizza l'accesso alla singola istanza, oppure può
decidere di crearsi un pool di istanze di quella servlet, e di assegnare una istanza per
ogni Thread nel caso di concorrenza di accessi.

E' raro veder utilizzare tale interfaccia, di certo rende la vita facile al programmatore,
ma non ottimizza la nostra applicazione, in quanto o accodando troppi thread potrebbe
risultare lenta o creando troppe istanze occuperei troppa memoria, cerchiamo quindi di
dimenticarci della sua esistenza e vediamo come intervenire.

Anche se qualche servlet engine ci consente di creare un pool di istanze di servlet,


dobbiamo sempre evitare di farlo, spesso quegli stessi servlet engine consigliano di
farlo solo per sviluppo ma non in esercizio.

51
L'esempio che riporto di seguito è l'esempio di quello che dobbiamo evitare di
scrivere, certo se lo provo funziona, ma dovrei riuscire a testarlo simulando l'accesso
concorrente, in quel caso avrei di certo risultati errati dovuti a sovrapposizione di dati:

L'errore sono le proprietà di classe nome, cognome, indirizzo, citta, cap.


<HTML><BODY>
<%!
String nome;
String cognome;
String indirizzo;
String citta;
String cap;
%>
<%
nome = request.getParameter("nome");
cognome = request.getParameter("cognome");
indirizzo = request.getParameter("indirizzo");
citta = request.getParameter("citta");
cap = request.getParameter("cap");

componiIndirizzo(out);
%>
</BODY></HTML>
<%!
void componiIndirizzo(JspWriter out) throws java.io.IOException
{
out.println("<PRE>");
out.println(nome + " " + cognome);
out.println(indirizzo);
out.println(citta+", "+cap);
out.println("</PRE>");
}
%>
E' semplice risolvere il problema, per esempio il programmatore può sincronizzare dei
blocchi ed assicurarsi che vengano percorsi da un Thread alla volta e farlo è
semplicissimo. La bravura del programmatore deve essere nel riuscire a progettare la
jsp/servlet in modo che ci siano pochi blocchi sincronizzati solo nei punti assolutamente
necessari.
Di seguito riporto le modifiche necessarie da apportare alla jsp precedente, è
funzionante, non c'e' sovrapposizione dei Thread, ma nel caso in cui molte richieste
arrivino contemporaneamente i Thread vengono messi in coda e potrebbero esserci
tempi lunghi di attesa, il metodo è valido se si vuole occupare poca memoria.
Il codice racchiuso all'interno del blocco synchronized(...){...} viene percorso da un
Thread alla volta.
<HTML><BODY>
<%!
String nome;
String cognome;
String indirizzo;
String citta;
String cap;
%>
<%
synchronized(this)
{
nome = request.getParameter("nome");
cognome = request.getParameter("cognome");
indirizzo = request.getParameter("indirizzo");
citta = request.getParameter("citta");
cap = request.getParameter("cap");

52
componiIndirizzo(out);
}
%>
</BODY></HTML>
<%!
void componiIndirizzo(JspWriter out) throws java.io.IOException
{
out.println("<PRE>");
out.println(nome + " " + cognome);
out.println(indirizzo);
out.println(citta+", "+cap);
out.println("</PRE>");
}
%>
Nonostante il caso di prima sia funzionante e corretto, potremmo desiderare di avere
maggiori performance a scapito di una maggior occupazione di memoria.
Se ci ricordiamo, dalla teoria dei Thread, che ogni Thread copia nel suo stack i dati che
passa ai metodi, potremmo risolvere il problema senza blocchi synchronized(...){...}
strutturando la nostra jsp/servlet senza variabili di classi ma con variabili dichiarate
all'interno dei metodi doGet(), doPost, service() e poi passate in ingresso ad ogni
metodo che le deve gestire, in quel caso avrei risolto la problematica togliento i blocchi
di sincronismo ma occupando (anche se per breve tempo) maggior memoria.
Certo se una mia jsp/servlet dovesse gestire per esempio 100 variabili e passarle a
parecchi metodi, sarebbe un codice un pò brutto da vedere, ma posso risolverlo
facilmente dichiarando una classe di sole proprietà (anche se contrario alla OOP)
facendola istanziare ad ogni Thread che arriva e passandola ai metodi in questione.

Scarica l'esempio finale completo di pagina html per richiamare la jsp: [ThreadInJsp.zip]

<HTML><BODY>
<%
DatiCliente dati = new DatiCliente();

dati.nome = request.getParameter("nome");
dati.cognome = request.getParameter("cognome");
dati.indirizzo = request.getParameter("indirizzo");
dati.citta = request.getParameter("citta");
dati.cap = request.getParameter("cap");

componiIndirizzo(dati,out);
%>
</BODY></HTML>
<%!
class DatiCliente
{
public String nome;
public String cognome;
public String indirizzo;
public String citta;
public String cap;
}
%>

<%!
void componiIndirizzo(DatiCliente dati,JspWriter out) throws
java.io.IOException
{
out.println("<PRE>");
out.println(dati.nome + " " + dati.cognome);
out.println(dati.indirizzo);
out.println(dati.citta+","+dati.cap);

53
out.println("</PRE>");
}
%>
Come far funzionare l'esempio:

Per far funzionare l'esempio basta copiare la pagina html e la Jsp, all'interno di una web
application "aperta", se per caso avete una installazione di default di Bea WebLogic 6.0
basta copiare i due file all'interno di:
E:\bea\wlserver6.0\config\mydomain\applications\DefaultWebApp_myserve
r
ovviamente considerando di averlo installato su e: come nel mio caso.

Se fate partire il server lanciando il file:


E:\bea\wlserver6.0\config\mydomain\startWebLogic.cmd

per richiamarlo dovete scrivere come nella seguente immagine:

Se riempite i campi e cliccate su Submit otterrete:

54
12 EJB INTRODUZIONE
Siamo arrivati a parlare di EJB, la mia materia preferita, e se avete letto i capitoli
precedenti avete le nozioni necessarie per seguirmi negli esempi che farò.

Prima di tutto bisogna cercare di capira cosa siano, a cosa servono e quando è il caso di
utilizzarli.

Molti credono che se si ha un Application Server si debba necessariamente utilizzare gli


EJB, il che non è vero, così come in molte architetture sono stati proposti e sviluppati
più per moda e per interessi economici piuttosto che per necessità.

Gli EJB sono una delle tante tecnologie racchiuse dentro la J2EE e quindi presenti in un
Application Server certificato J2EE, ma sono l'unica di queste tecnologie che non può
vivere al di fuori di un AS, non solo perchè all'interno dell'AS c'è un motore (Container)
che si preoccupa di istanziarli e gestirne il ciclo di vita, ma anche perchè necessitano
della collaborazione di altre tecnologie quali JMX, JTS, JNDI, XADataSource/DataSource,
JDBC, JMS, Servlet & JSP, etc, per poter dare tutti i loro benefici ad uno sviluppatore.

Gli EJB sono ormai arrivati alla specifica 2.0, ma la prima loro release fu la 1.0. La
specifica 1.0 fu più o meno seguita da molti produttori di AS, dico piu o meno perchè in
quel periodo la Sun non aveva ancora rilasciato le certificazioni per gli AS e così ognuno
seguiva delle proprie regole che rendevano le applicazioni enterprise legate agli ejb
vincolate all'AS prescelto (situazione da evitare). La specifica degli ejb 1.1 rientra nella
specifica della J2EE1.2 che è quella adottata dalla maggioranza degli AS di mercato e di
cui i vari produttori hanno ottenuto la certificazione di compatibilità totale dalla Sun. La
specifica 2.0 rientra nella J2EE1.3 rilasciata da poco e su cui i vari produttori di AS si
stanno facendo certificare.

Intanto iniziamo col dire che gli EJB sono delle classi lato server e che possono essere
richiamate remotamente da chiunque, anzi per essere precisi, dalla specifica 1.1 e
successive possono essere richiamati remotamente da chiunque purchè tramite
Corba/IIOP e quindi anche RMI/IIOP come si può vedere nel seguente schema :

55
Nella versione 1.0 potevano essere richiamati anche da protocolli come COM o DCOM,
ma questa soluzione fu abbandonata perchè in effetti rendeva le applicazioni enterprise
poco scalabili.
Per esempio vecchie versioni di Bea WebLogic prevedevano di rendere accessibili gli ejb
da protocolli quali COM e DCOM, ma per poterlo rendere possibile all'interno dell'AS
erano presenti delle DLL per windows che avevano effetto solo se l'AS era ovviamente
installato su piattaforma Windows.
Se un domani avessimo voluto installare il tutto su Unix, avremmo dovuto riscrivere la
logica di comunicazione dei client. La SUN, per risolvere il problema, ha fatto uscire uno
strumento (JavaTM 2 Platform, Enterprise Edition Client Access Services (J2EETM CAS) COM Bridge 1.0
Early Access bisogna essere registrati a JDC), che se installato su una macchina Windows
rende possibile la comunicazione da quella macchina e da linguaggi quali Visual Basic
verso AS installati su piattaforma Unix.

L'EJB può essere richiamato quindi remotamente, cioè una classe remota (che è in
funzione su un'altra macchina o nel caso di Java su un'altra Virtual Machine) può
invocare i metodi di un'ejb passargli dei parametri e riceverne dei risultati. Sicuramente
uno dei motivi di scelta architetturale degli ejb è il caso in cui ci siano presenti dei client
che non siano dei Browser, il che non è necessario visto che i client più famosi degli ejb
sono le servlet e le jsp.
In realtà gli ejb riescono a semplificare grosse problematiche nel contesto delle
architetture distribuite e con accessi concorrenti di più Thread.

56
La stessa SUN dice che gli EJB sono dei componenti server-side, che semplificano lo
sviluppo di componenti middleware che debbano essere TRANSAZIONALI, SCALABILI,
PORTABILI e SICURI. In effetti queste tipiche problematiche di sviluppo rientrano nella
"vita" di un ejb. Fino a pochi anni fa se volevamo sviluppare una applicazione remota,
distribuita, scalabile transazionale sicura e portabile con client di diverso tipo,
dovevamo sicuramente cercare sviluppatori con Skill di alto livello, prevedere un certo
tipo di analisi e di specifiche di dettaglio e funzionali, prevedere problematiche di
sicurezza, di integrazione e di eventuali aggiornamenti e modifiche sul sistema, oggi
tutto questo viene fornito in automatico dall'ejb, certo dobbiamo capire i suoi
comportamenti le sue particolarità ed i suo meccanismi ma una volta fatto un
programmatore di medio livello riesce a costruire architetture enterprise di alto livello,
inoltre i tempi di sviluppo si abbattono.

Personalmente ho sorriso quando non troppo tempo fa ho letto su una rivista italiana
famosa inerente alla programmazione un'articolo che riportava un confronto
prestazionale fra una architettura in cui era presente un'ejb ed un'altra basata su
servlet che otteneva lo stesso risultato ma dove l'ejb non era presente. L'autore poneva
l'accento sul fatto che dove c'era l'ejb ci voleva più tempo ad avere un risultato, senza
mai essersi domandato se era necessario l'ejb nella sua architettura. Ovviamente l'ejb è
un Signore di tutto rispetto è complesso e risolve una serie di problematiche e deve
essere utilizzato per reale bisogno, nel caso dell'autore dell'articolo, vi assicuro che la
sua architettura non gestiva transazioni, sicurezza etc etc eppure lui usava l'ejb, che di
default è transazionale. Nei risultati del suo test non ha mai detto che lato server dove
c'era l'ejb, fra le altre cose, c'era una transazione. Ovviamente l'architettura opposta
basata su una servlet non solo non gestiva la transazione (non gli serviva) ma
nemmeno andava su JNDI etc etc. Insomma la morale è che prima di scrivere un EJB
bisogna pensare se effettivamente sia necessario.

Nel corso dei prossimi articoli descriverò tutte le funzionalità che possiamo sfruttare di
un EJB e farò per ognuna un esempio, cercherò di far capire come evitare di scrivere
codice (obiettivo degli ejb), se non dei semplici metodi, per risolvere le problematiche
di sicurezza, transazionalità etc.

Tutti gli esempi che farò saranno pronti per funzionare su Bea WebLogic 6.0/6.1,
quando ci sarà bisogno di un database utilizzeremo cloudscape che è interno a WebLogic,
se mi volete seguire vi consiglio quindi di scaricarvi WebLogic.

57
13 EJB: COME È COMPOSTO
Vediamo ora da cosa è composto un Ejb, immaginiamo di voler creare l'ejb
ConvertitoreEuro con un metodo converti(), richiamabile remotamente, che se
riceve un intero (le lire) mi da come valore di ritorno un double (gli euro).
Userò le nomenclature indicate dalla SUN, che vi consiglio di rispettare, e quindi visto
che per fare un EJB dobbiamo scrivere una classe e due interfacce, le chiameremo:

CLASSE ConvertitoreBean Bean Class (logica applicativa)


INTERFACCIA ConvertitoreHome Home Interface
INTERFACCIA Convertitore Remote Interface

Tra poco capiremo perché dobbiamo scrivere due interfacce di supporto alla nostra
Bean Class.

Nella Bean Class dobbiamo inserire tutti i metodi che ci servono per risolvere il nostro
problema, nel nostro caso in ConvertitoreBean devo inserire il metodo:

public double converti(int lire){


return (lire/1936.27) ;
}

Per completezza pubblico tutto il codice del nostro esempio, ma in seguito spiegherò in
dettaglio come scrivere queste classi ed interfacce a seconda del tipo di EJb che si vuole
creare (Session o Entity).

ConvertitoreBean
package ktech;

import javax.ejb.*;
import java.util.*;

public class ConvertitoreBean implements SessionBean {


private SessionContext ctx;
public ConvertitoreBean() {}
public void setSessionContext(SessionContext c) {
ctx=c;
}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbPassivate() {}
public void ejbActivate() {}

public double converti(int lire) {


return lire/1936.27;
}
}

ConvertitoreHome
package ktech;

import javax.ejb.*;
import java.rmi.*;

public interface ConvertitoreHome extends EJBHome {


public Convertitore create() throws CreateException, RemoteException;
}

58
Convertitore
package ktech;

import javax.ejb.*;
import java.rmi.*;

public interface Convertitore extends EJBObject {


public double converti(int lire) throws RemoteException;
}

Di tutti i metodi che voglio rendere accessibili remotamente ne devo inserire il prototipo
all'interno della Remote Interface. Ovviamente per ogni metodo dichiarato nella Remote
Interface devo avere un metodo dichiarato ed implementato all'interno della Bean
Class, non è vero il viceversa, infatti nella Bean Class posso avere dei metodi richiamati
da altri metodi e posso esporre solo questi ultimi nella Remote Interface.

Nella Home Interface è presente, ed è obbligatorio che ci sia, il metodo create(), di lui
parleremo in dettaglio in seguito ma per ora è utile capire che se remotamente invoco
la create(), la mia richiesta va verso un motore dell'AS che si chiama Container il
quale in quel momento crea una istanza di quell'Ejb, invocando il metodo ejbCreate()
che si trova nella Bean Class, in generale vedremo che nella Bean Class ci sono più
metodi con il prefisso ejbXXX(), questi metodi vengono invocati univocamente dal
Container o per naturale ciclo di vita dell'ejb o perché un client ha invocato particolari
metodi, come per esempio la create, dalla Home o dalla Remote Interface.

Il fatto di scrivere la classe e due interfacce, non significa aver scritto un ejb. Un ejb
per essere tale deve essere composto non solo da questi 3 elementi, ma anche da un
file XML che si deve chiamare ejb-jar.xml e che è stato ideato dalla Sun.
Il DTD (Document Type Definition) del file ejb-jar.xml è reperibile al seguente link:
http://java.sun.com/dtd/ejb-jar_2_0.dtd
In generale i DTD per la J2EE sono al seguente indirizzo:
http://java.sun.com/dtd/

Tale file descrive i comportamenti dell'ejb relativamente alle sue risorse, alla sicurezza,
alla transazionalità ed a moltre altre informazioni e lo analizzeremo dettagliatamente in
seguito.

L'insieme, all'interno di un file JAR, della Bean Class, della Home e Remote Interface e
del file ejb-jar.xml viene definito Naked Ejb.

ejb-jar.xml
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems,
Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>ConvBean</ejb-name>
<home>ktech.ConvertitoreHome</home>
<remote>ktech.Convertitore</remote>
<ejb-class>ktech.ConvertitoreBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>

Ora iniziano i problemi, in effetti sulla maggior parte degli AS sul mercato, questi 4 file
insieme non sono sufficienti affinché l'ejb da Naked possa essere trasformato in

59
Deployable ed essere quindi poi "Deploiato" cioè installato e configurato sull'AS in
questione.

Bisogna aggiungere al JAR un altro file xml, dove posso inserire altre informazioni
relativamente alla gestione ed al comportamento dell'ejb.
Purtroppo per questioni di mercato ed economiche i produttori di AS non sono allineati
su questo punto e ad oggi tutto ciò che la SUN riconosce come standard è il Naked Ejb.
Il punto è che per qualsiasi AS sono obbligato a studiare e capire come configurare
questi specifici file xml (ovviamente molto dissimili).

Bea WebLogic: weblogic-ejb-jar.xml


Oracle OC4J: orion.xml
Jboss : jboss.xml

Per il nostro esempio e per Deploiarlo su Bea WebLogic abbiamo bisogno quindi di
weblogic-ejb-jar.xml :

weblogic-ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC
'-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN'
'http://www.bea.com/servers/wls60/ejb20/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>ConvBean</ejb-name>
<jndi-name>ConvEuro</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>

Tra l'altro bisogna stare attenti a quello che si scrive in questi file xml, in quanto ci sono
dei tag che se usati cambiano radicalmente il comportamento ed il ciclo di vita dell'ejb
rendendolo difficilmente portabile.
Quindi prima di scrivere informazioni dentro tali xml (tranne che i jndi name delle varie
risorse) è opportuno informarsi se il tag inserito fornisce solo maggiori performance allo
specifico AS in uso o cambia il ciclo di vita degli ejb sviluppati.

Riassumendo i passi da fare sono (entreremo successivamente nel dettaglio):

1) scrivere una Bean Class


2) scrivere la RemoteInterface
3) scrivere la Home Interface
4) scrivere l'ejb-jar.xml
5) scrivere gli xml ulteriori e necessari per lo specifico ejb (su Bea WebLogic per
esempio nel caso di Entity Bean CMP si devono scrivere due file xml oltre all'ejb-
jar.xml).
6) creare un Naked Ejb tramite un JAR. (verrà descritto in seguito)
7) trasformare il Naked ejb in un Deployable ejb. (verrà descritto in seguito)
8) "Deploiare" l'ejb. (verrà descritto in seguito)
9) Richiamarlo dai client. (verrà descritto in seguito)

Trasformare un Naked Ejb in un Deployable Ejb, significa aggiungere all'interno di quel


naked ejb una serie di classi tra cui anche due Stub e due Skeleton che sono
necessarie nel contesto delle architetture distribuite e delle comunicazioni remote.
Queste classi vengono generate da dei tools specifici per ogni AS ed è per questo che
un ejb deploiato su un AS, non può funzionare così come è su un altro AS di un altro
produttore, deve essere rideploiato e per poterlo fare è opportuno ritornare al punto di
un Naked Ejb.

60
Deploiare un ejb significa installarlo e renderlo operativo su un AS. La fase di Deploy
viene gestita solitamente dall'AS tramite dei Tools o dei wizard.

In fase di Deploy l'AS fa diverse operazioni tra cui prendere lo Stub della Home
Interface e metterlo nel JNDI in locale associandogli come jndi name quello
indicato nei file xml di quell'ejb.
Nel nostro caso: <jndi-name>ConvEuro</jndi-name>

Ma come mai abbiamo bisogno di due interfacce e come mai ci sono due
coppie di Stub e di Skeleton?

La prima risposta generica che si può dare è che ci sono due interfacce proprio perché
ci sono due coppie di Stub/Skeleton

In una generica architettura distribuita, sia che sia basata su CORBA che su RMI o
RMI/IIOP, sono sempre presenti un ORB (Object Request Broker), un Client ed uno
Stub, un Server ed uno Skeleton.
Il Client ed il Server sono delle classi, ma si definisce Client la classe che richiede un
servizio ad un'altra classe che in quel momento fa da Server ma ovviamente ogni Client
può a sua volta essere Server e viceversa.
Lo Stub e lo Skeleton vengono creati da dei tools, nel caso di RMI da rmic, sono
proprio lo Stub e lo Skeleton che rendono possibile la comunicazione remota (ogni Stub
comunica con il suo Skeleton).

Una volta ottenuti si copia lo Stub sulla macchina del Client ed ovviamente lo Skeleton
sulla macchina del Server.

Come abbiamo appena descritto in una architettura distribuita solitamente lato client e
lato server sono già stati installati tutti i componenti necessari (stub e skeleton), nel

61
caso di un client che richiama un ejb all'inizio della interazione la situazione è che il
client non ha nulla (se non le due interfacce) ed il server ha due coppie di stub e
skeleton.

Vediamo quindi, nel prossimo capitolo, cosa succede nel momento in cui un Client
decide di richiedere un servizio ad un EJB.

62
14 EJB: IL CLIENT
La prima cosa che un client deve fare per iniziare una comunicazione remota è quella di
reperire uno STUB, ricordandoci che in fase di Deploy lo stub della Home Interface di
quell'ejb è stato inserito in JNDI l'operazione è semplice.

Ricordiamoci che ho dato all'ejb il jndi name "ConvEuro" :


weblogic-ejb-jar.xml --> <jndi-name>ConvEuro</jndi-name>
1) ci connettiamo a JNDI:
InitialContext ic = new InitialContext();

2) Facciamo lookup del jndi name dell'ejb e sapendo che quello che ci viene mandato è
un oggetto serializzato che è lo stub della Home Interface, usiamo la nostra Home
Interface per farne il casting, quindi un client prima di iniziare una comunicazione
remota deve sicuramente avere le due interfacce dell'ejb in questione visibili nel suo
classpath
ConvertitoreHome home = (ConvertitoreHome) ic.lookup("ConvEuro");

3) ottenuta una Home, sappiamo che vi è presente il metodo create() quindi quello
che possiamo fare è invocare questo metodo. L'invocazione della create() fa si che lo
stub della home interface comunichi con il suo relativo skeleton lato server, il quale va
a dire al Container di fare una istanza di quell'ejb e di mandare indietro al client lo stub
della remore interface. Il risultato della create() è quindi lo stub della Remote
Interface, e quindi faccio una operazione di casting con la mia Remote Interface in
locale.
Convertitore convertitore = (Convertitore) home.create();

4) a questo punto il mio stub della remote può comunicare con il suo relativo skeleton
e quello che posso invocare sono tutti i prototipi dei metodi della Remote Interface che
trovano una corrispettiva implementazione lato server.
double euro = convertitore.converti(8500);

Riporto di seguito un diagramma che riassume i 4 punti che ho appena descritto:

63
Ecco l'esempio completo:

Client.java
package client;

import javax.naming.*;
import java.rmi.*;
import ktech.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();
ConvertitoreHome home = (ConvertitoreHome) ic.lookup("ConvEuro");
Convertitore convertitore = (Convertitore) home.create();
double euro = convertitore.converti(8500);
System.out.println("Risultato:"+euro);
}catch (Exception e){
System.out.println(e);
}
}
}

La specifica degli ejb 1.1 (negli ejb 2.0 ci sono cambiamenti che vedremo in seguito)
dice che un qualsiasi Client, sia esso in locale o remoto, deve richiamare un servizio di
un ejb sempre alla stessa maniera, quindi sempre come descritto nell'esempio.

Il Client oltre che avere la Home e la Remote Interface in locale e visibili nel
CLASSPATH (e nel nostro caso anche il jndi.properties visto che ho utilizzato

64
InitialContext ic = new InitialContext(); ), deve avere un JDK (consiglio sempre la
versione 1.3.1 ... per ora) installato ed una serie di librerie che comprendono al loro
interno JNDI e le Factory etc etc.

Nel caso di Bea WebLogic 6.1 in locale il Client deve avere:

..\bea\wlserver6.1\lib\weblogic.jar
..\bea\wlserver6.1\lib\weblogic_sp.jar

In realtà la specifica degli ejb 1.1 dice che lato client deve essere sempre usato il
metodo PortableRemoteObject.narrow(...) anche se spesso nei client Java i
programmatori non lo utilizzano.
Tale metodo (che verrà descritto in seguito) si preoccupa di fare opportuni "Casting", e
nel caso di Client scritti in java puo' essere omesso.
Il metodo indica con quale classe deve essere fatto il "Casting" nel nostro caso
ConvertitoreHome.class e se in fase di compilazione, tale classe dovesse essere
presente due volte nel CLASSPATH (situazione tipica quando si hanno ambienti di test e
sviluppo su una stessa macchina) si genera un errore dovuto ovviamente all'indecisione
sulla classe da utilizzare.

Client.java - Versione Corretta


package client;

import javax.naming.*;
import java.rmi.*;
import javax.rmi.*;
import ktech.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

ConvertitoreHome home = (ConvertitoreHome)


PortableRemoteObject.narrow(ic.lookup("ConvEuro"),
ConvertitoreHome.class);

Convertitore convertitore = (Convertitore) home.create();


double euro = convertitore.converti(8500);
System.out.println("Risultato:"+euro);
}catch (Exception e){
System.out.println(e);
}
}
}

Nei prossimi capitoli spiegherò tutti i comandi da dare per creare un Naked EJB,
trasformarlo in Deployable EJb ed infine per Deployarlo su Bea WebLogic 6.1, potrete
scaricare ovviamente tutto il codice completo di file *.cmd preconfigurati per eseguire
tutte le citate operazioni e per compilare e lanciare il Client che abbiamo descritto.

65
15 EJB: CREAZIONE DI UN JAR
Scarica l'esempio completo: [Convertitore.zip]

Creare un JAR per un EJB è una operazione molto semplice, ma ci sono delle regole di
percorsi e nomenclature di directory da seguire.

Prima di tutto si decide il nome (a piacere) di una directory di lavoro, io ho scelto il


nome "Convertitore" come potete vedere nelle immagini sottostanti, li sotto si devono
mettere le classi dell'ejb, nel mio caso le classi fanno parte del package ktech e quindi
ho creato la sottodirectory ktech della directory di lavoro Convertitore.

Sottodirectory obbligatoria della prescelta directory di lavoro è la directory META-INF


al cui interno devo mettere tutti i file XML di cui ho bisogno.

A questo punto, dopo aver compilato le classi dell'ejb, posso creare il Naked Jar e per
farlo devo mettermi all'interno della directory di lavoro e lanciare il comando Jar con
opportune informazioni in input, nel mio caso farò:

66
Per poter compilare ovviamente dovete impostare il CLASSPATH giusto e questo
dipende dall'AS che utilizzate vi metto un esembio di file cmd, che chiamerò
setenv.cmd, che potete lanciare su Windows 2000 e valido per Bea WebLogic 6.1,
Il file setenv.cmd deve essere copiato, in questo caso, sotto la directory
c:\Convertitore e fa riferimento ad un Bea WebLogic 6.1 che sulla mia macchina è
installato sul disco e:\, ognuno dovrà controllare la prima riga di setenv.cmd e scrivere
il disco giusto di dove è stato installato WebLogic 6.1, ovviamente aprite un finestra
DOS andate in Convertitore lanciate il setenv.cmd e poi gli altri comandi che abbiamo
descritto:

setenv.cmd
set WL_HOME=e:\bea
set CLASSPATH=.
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\lib\ejb20.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\lib\weblogic.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\lib\weblogic_sp.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\samples\eval\cloudscape\lib\
tools.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\samples\eval\cloudscape\lib\
client.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\wlserver6.1\samples\eval\cloudscape\lib\
cloudscape.jar
set CLASSPATH=%CLASSPATH%;%WL_HOME%\jdk131\lib\tools.jar

set CLASSPATH=%CLASSPATH%;c:\Convertitore

set PATH=%WL_HOME%\jdk131\bin;%PATH%
set PATH=%WL_HOME%\wlserver6.1\bin;%PATH%

Nel file setenv.cmd ho inserito anche le righe per cloudscape, ora non ci serve ma
negli esempi successivi si.

A questo punto possiamo creare il nostro Naked jar e nel capitolo successivo vediamo
come renderlo deployable e renderlo "operativo"

67
Scarica l'esempio completo: [Convertitore.zip]

16 DEPLOYARE UN EJB
Abbiamo visto come creare un Naked EJB, ora spiegherò come renderlo Deployable,
cioè creare un nuovo JAR con al suo interno tutte quelle classi di supporto, tra cui gli
stub e gli skeleton, di cui ha bisogno l'ejb per essere operativo.

Ogni Application Server ha i suoi tipici e propri strumenti per compiere le operazioni che
descriverò ma ovviamente il filo conduttore è uguale per tutti.

L'esempio che farò sarà relativo a Bea WebLogic 6.1.

Scarica l'esempio completo [Convertitore.zip]:


in questo file trovate tutto il necessario per compilare l'ejb ed il suo client, creare il
naked ed il deployable jar e per lanciare il client remotamente.

L'esempio è stato preparato considerando di aver decompilato il file Convertitore.zip


in radice di c: quindi dovreste avere c:\Convertitore e la prima riga di setenv.cmd
nel mio caso fa riferimento a Bea WebLogic 6.1 installato in e: (set
WL_HOME=e:\bea) cambiate questa riga a seconda di dove avete installato Bea.

I passi da fare sono i seguenti:

1) aprire una finestra DOS ed andare dentro la directory Convertitore


C:\>cd Convertitore

2) da dentro la Directory Convertitore lanciare il file compilaEjb.cmd che altro non fa


che richiamare il file setenv.cmd che setta il CLASSPATH necessario per tutte le nostre
operazioni e lancia il comando:
C:\Convertitore>javac ktech\*.java

3) sempre da dentro Convertitore lanciare il file creaNaked.cmd che a sua volta


esegue il comando:
C:\Convertitore>jar cvf convertitoreNaked.jar META-INF\*.* ktech\*.class

4) a questo punto trasformiamo in nostro naked in deployable lanciando il file


creaDeployable.cmd che esegue il comando:
C:\Convertitore>java weblogic.ejbc20 ConvertitoreNaked.jar
ConvertitoreDeployable.jar

5) ora dobbiamo installare o deployare il nostro ejb, per farlo lanciamo il dominio di
default che si trova dentro Bea WebLogic denominato "mydomain" che nel mio caso
(WebLogic installato su e:\) si trova sotto E:\bea\wlserver6.1\config\mydomain
per avviare il dominio basta lanciare il file
E:\bea\wlserver6.1\config\mydomain\startWebLogic.cmd
Appena il dominio è startato avviate il browser e richiamate la console:
http://localhost:7001/console
Dovete ora cliccare sull'albero di sinistra sulla voce EJB:

68
Cliccate su "Install a new EJB ..."

Premete il bottone Sfoglia e andate a selezionale il vostro file


ConvertitoreDeployable.jar creato al punto [4], poi premete Upload ed ottenete:

69
Il vostro ejb è ora deployato, e se aprite il JNDI TREE, troverete al suo interno il JNDI
NAME (ConvEuro) associato allo stub della home interface:

6) possiamo ora compilare il nostro client lanciando il file compilaClient.cmd che a


sua volta esegue il comando:
C:\Convertitore>javac client\Client.java

7) ed infine possiamo richiamare il client lanciando il file lanciaClient.cmd che a sua


volta esegue il comando:
C:\Convertitore>java client.Client
Otterrete il seguente risultato:

70
Il client funzionerà perchè troverà il file jndi.properties e le classi dell'ejb di cui ha
bisogno all'interno della directory
C:\Convertitore>
che viene impostata nel CLASSPATH dal file setenv.cmd nella riga:
set CLASSPATH=%CLASSPATH%;c:\Convertitore

jndi.properties
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://localhost:7001
java.naming.security.principal=system
java.naming.security.credentials=weblogic

Naturalmente dovete modificare i valori all'interno del file jndi.properties a seconda


delle vostre scelte durante l'installazione dove si nota che io ho scelto la porta di default
7001, l'utente system con la relativa password weblogic. Utente che risulta negli
User di WebLogic e che rientra nel gruppo everyone che ha i privilegi per accedere a
jndi.

17 EJB:ESEMPI
Attenzione: questa pagina verrà aggiornata spesso, controllate regolarmente in
"modifiche e news" gli eventuali aggiornamenti.

Nei prossimi capitoli presenterò la teoria degli Ejb e prenderò come


riferimento gli esempi scaricabili da questa pagina.

Download [K-Tech.zip], un dominio preconfigurato per Bea WebLogic 6.1 con


al suo interno:

EJB:
- Un EJB Stateless con connessioni a cloudscape tramite DataSource Transazionale
- Un EJB Stateless con connessioni a cloudscape tramite DataSource Transazionale e
l'utilizzo del CachedRowSet
- Un EJB Entity BMP
- Un EJB Entity CMP (il precedente ma in versione CMP)

File CMD per automatizzare:

71
- il settaggio delle variabili d'ambiente necessarie a
setEnv.cmd
Bea WebLogic 6.1
- l'avvio automatico di Bea WebLogic 6.1 startServer.cmd
- la creazione dei DB (su cloudscape) necessari agli
CreaDB.cmd
EJB, tramite file DDL.
- l'avvio automatico di cloudscape runcloudscape.cmd
- la compilazione, creazione e Deploy degli Ejb create_and_deploy_Ejb.cmd
- la compilazione ed il lancio dei client compila_e_lancia_client.cmd

Informazioni:

Prove effettuate su Bea WebLogic 6.1 con JDK131 e con sistema operativo Windows
2000 Professional.

Il file K-Tech.zip deve essere unzippato nella directory "config" di dove avete
installato Bea, nel mio PC si trova su "E:\bea\wlserver6.1\config\K-Tech"

Prima di tutto dovete modificare la prima riga di setEnv.cmd che si trova sotto la
directory K-Tech e che di default è settata a "set WL_HOME=e:\bea" ovviamente al
posto di "e:\" mettete il drive in cui avete installato Bea WebLogic. Verificate, sempre
nello stesso file, che i percorsi ed i nomi di wlserver6.1 e jdk131 siano esatti rispetto
alla vostra installazione, ma se avete fatto l'installazione standard di Bea WebLogic 6.1
non dovrete modificare nulla.

Il file setEnv.cmd non deve essere mai lanciato, viene richiamato da tutti gli altri file
cmd.

Potete ora creare le tabelle necessarie agli EJB su cloudscape, lanciando il comando
CreaDB.cmd, che usa a sua volta il file "DBSchema.ddl" e che crea il db "KTDB"
sotto la directory "K-Tech".

NB - La creazione del DB deve avvenire prima dell'avvio del server WebLogic.

A questo punto potete "startare" il vostro server (WebLogic 6.1) lanciando il file
startServer.cmd.

L'utente e la password del server sono:


- Utente: system
- password: weblogic

Sono gia impostate in jndi.properties, e nel file startServer.cmd, non devono essere
fornite dai client e neppure all'avvio del server.

Lanciando la console di WebLogic, dal browser, con la url


"http://localhost:7001/console" si apre una finestra che vi chiede di inserire i valori
dell'utente e relativa password che ovviamente sono quelli gia descritti.

Gli esempi si trovano sotto la directory "esempi" che si trova sotto "K-Tech".

Per compilare, creare i naked ed i deployable, copiare le classi nel classpath, ed infine
deployare gli ejb basta lanciare (meglio da una finestra dos) il comando
create_and_deploy_Ejb.cmd che si trova nella directory di ogni esempio.

Per compilare ed eseguire i client di prova basta lanciare (meglio da una finestra dos) il
comando compila_e_lancia_client.cmd (e simili) che si trovano nella directory di
ogni esempio.

72
Download [StatefulJdbc.zip], un EJB Stateful con connessione a DataBase, compreso di
client di prova.

Per poterlo compilare, deployare e richiamare tramite il client è necessario che tutti i
passi precedenti di questo documento siano stati effettuati, dopodichè basta
scomprimere il file StatefulJdbc.zip all'interno della directory "esempi" che si trova
sotto il dominio K-Tech.
In conclusione se avete fatto tutto bene avrete la seguente directory "StatefulJdbc"
nel seguente percorso:
"VostroDisco:\bea\wlserver6.1\config\K-Tech\esempi\StatefulJdbc"
All'interno della directory StatefulJdbc troverete sia l'ejb che il client che tutti i file
necessari al loro utilizzo.
Se volete essere sicuri che tutto funzioni per il meglio i passi da eseguire nel
preciso ordine sono:
1) Compilare e Deployare l'EJB, lanciando il file:
"VostroDisco:\bea\wlserver6.1\config\K-
Tech\esempi\StatefulJdbc\create_and_deploy_Ejb.cmd"
2) Lanciare il server tramite il file "VostroDisco:\bea\wlserver6.1\config\K-
Tech\startServer.cmd"
3) Lanciare il Client tramite il file "VostroDisco:\bea\wlserver6.1\config\K-
Tech\esempi\StatefulJdbc\compila_e_lancia_client.cmd"

73
18 EJB SESSION
Gli EJB si dividono in due grosse famiglie i Session e gli Entity, i Session a loro volta
si suddividono in Stateful ed in Stateless e gli Entity a loro volta si suddividono in
CMP (Container Managed Persistence) ed in BMP (Bean Managed Persistence).

Enterprise Java Bean


SESSION ENTITY
Stateful Bean Managed Persistence (BMP)
Stateless Container Managed Persistence (CMP)

Iniziamo quindi a capire come mai abbiamo a disposizione 4 diversi tipi di Ejb,
dobbiamo riuscire a capire i loro diversi cicli di vita e quindi riuscire a capire quale sia
opportuno usare a seconda delle nostre necessità.

EJB Session [ Stateful ]:

Gli Stateful devono essere utilizzati quando lato server si ha bisogno di una classe che
mantenga uno stato, chi da uno stato ad una classe sono le sue proprietà e lo stesso
vale per questi ejb.

Gli Stateful sono degli ejb con delle proprietà di classe che hanno uno stato assegnato
dal client che ha richiesto quell'ejb.

Una istanza di un ejb stateful, assume quindi uno stato specifico per l'operazione che il
client sta compiendo con esso ed ovviamente tale istanza deve essere gestibile sono da
quel client, e non può essere gestita contemporaneamente da piu client.

Tutti gli ejb Session sono MonoThread.

Se per esempio uso uno stateful per gestire degli ordini di acquisto di libri e memorizzo
ogni ordine all'interno del mio ejb, il quale assume lo stato di "ordine in corso", è ovvio
che tale istanza di ejb deve essere univoca per me solo io devo essere in grado di
gestirla, nessun altro deve essere in grado di accedere alla mia istanza altrimenti
potrebbe rimuovere o modificare i libri che ho scelto o addirittura annullare l'ordine.

In effetti una delle regole fondamentali degli ejb Stateful è che ad ogni
create() corrisponde necessariamente una nuova istanza lato server anche se
chi invoca la create() è lo stesso client.

Di solito quindi uno Stateful ha senso farlo se in esso sono presenti proprietà di classe
con valori che vengono assegnati dai client tramite le varie create(...) presenti nella
Home Interface.

Lo Stateful può quindi avere la create() in overloading, e per ogni create(...) presente
nella Home Interface, deve corrispondere una ejbCreate(...) all'interno della Bean
Class.

Gli Stateful non possono stare in Pool di Istanze in quanto ogni client deve avere un suo
specifico Remote Stub marcato in modo tale che attraverso il Remote Skeleton possa
parlare univocamente con l'istanza creata in quel momento e con quello specifico stato.

EJB Session [ Stateless ]:

Gli ejb Stateless sono considerati classi lato server che danno dei servizi, cioè classi con
metodi richiamabili remotamente a cui do un input e da cui so che ricevo un output,
sono classi che non devono mantenere uno stato specifico rispetto al client che li ha

74
invocati.

Per esempio nel capitolo "EJB: come è composto" ho descritto uno Stateless, con un metodo
di servizio chiamato converti(..) a cui passo le lire e ricevo un double con la conversione
in euro delle lire passate.

Come si può notare l'ejb citato è privo di proprietà di classe, in effetti non ne ha
bisogno :)

L'aver detto che uno Stateless non deve mantenere uno stato tipico per il client che l'ha
invocato non significa che esso non possa avere proprietà di classe, in effetti a volte per
capire se si ha a che fare con uno Stateless o con uno Stateful, (nel caso in cui ci sia la
create() non in overloading e ci siano proprietà di classe) l'unica cosa da fare è quella di
controllare se nel suo file descrittore ejb-jar.xml nella riga
<session-type>Stateless</session-type>
ci sia scritto Stateless o Stateful.

Siccome è possibile mettere proprietà di classe in uno Stateless, la specifica degli ejb
ha però vietato di far passare valori dal client allo Stateless tramite la create().

La create() degli Stateless infatti non può essere in overloading, e nella Home Interface
possiamo avere solo la create() e quindi nella Bean Class solo la ejbCreate(), quindi se
all'interno della ejbCreate() di uno Stateless decido di valorizzare delle proprietà di
classe, per esempio prendendo dei valori da un DataBase, questi valori saranno
ovviamente identici per tutte le istanze di quell'ejb (sempre che nessuno modifichi i
valori sul DB).

Gli Stateless quindi possono stare in Pool di Istanze, tanto saranno tutte identiche,
l'accesso agli Stateless tramite la create() è quindi molto più veloce.

Anche essi come gli Stateful sono MonoThread, ma mentre nel caso degli Stateful
se due client in tempi diversi invocano la create() ho necessariamente due istanze
dell'ejb, questo non è vero con gli Stateless, in effetti se una istanza di uno Stateless in
uno specifico momento ha finito di far lavorare il suo metodo ed ha dato una risposta al
client, e se un'altro client invoca una create() quella istanza potrebbe essergli
assegnata dal container.

Nel caso peggiore delle ipotesi avrò tante istanze di Stateless quanti sono i client se e
solo se tutti i client contemporaneamente richiedono dei servizi a quell'ejb stateless, ma
questo è statisticamente poco probabile ed in assoluto con uno Stateless ho sempre
molte meno istanze contemporaneamente in memoria che con uno Stateful.

comunque il Pool di Istanze deve essere sempre uguale al numero massimo di client
che esistono nel sistema, sarebbe inutile farlo maggiore e pericoloso farlo piu piccolo.

Da ciò ne derivano le regole principali degli stateless:

- Ad ogni create() non necessariamente corrisponde una nuova istanza lato


server di quell'ejb.
- Ad una chiamata successiva di un metodo di un ejb stateless da parte di uno
stesso client, non è detto che risponda la stessa istanza di quell'ejb.

Se per esempio ho uno Stateless EJB_STATELESS_A con i metodi METODO_1() e


METODO_2() ed un Client CLIENT_A, se CLIENT_A invoca la create di
EJB_STATELESS_A e poi invoca prima il METODO_1() e dopo qualche secondo
METODO_2(), non è detto che il METODO_2() che gli risponde sia di
EJB_STATELESS_A, infatti potrebbe essere stato assegnato dal container ad un'altro
client.

75
Questa regola è importante per le Transazioni degli ejb (che vedremo in seguito),
mentre infatti in uno Stateful posso aprire una transazione in un metodo e chiuderla in
un'altro, negli Stateless una transazione deve iniziare e concludersi all'interno di ogni
singolo metodo.

Ovviamente di questo ne devo tenere conto non solo per le transazioni ma anche per
tutte quelle risorse che devono essere rilasciate.

Ogni programmatore dovrebbe sempre chiedersi se il suo problema possa essere risolto
quindi con uno Stateless, spesso si vedono ejb Stateful che hanno come proprietà di
classe tutte quelle informazioni JDBC necessarie per connettersi ad un DB come la Url il
Driver l'utente e la password, e poi inseriscono dei metodi a cui si passano delle query
da eseguire, senza aver la necessità di mantenere uno stato con opportuni valori.
Lo stesso problema può essere risolto con uno stateless che ha dei metodi che
ricevono, oltre la query da lanciare, i valori necessari a JDBC, ovviamente le prestazioni
e le risorse dell'Application Server ne guadagnano.

18.1 EJB SESSION STATEFULL


Cerchiamo ora di capire cosa si deve scrivere in un ejb di tipo Stateful ed a che cosa
servono i suoi metodi principali, per spiegarlo prendiamo come esempio l'ejb Stateful
[StatefulJdbc.zip] presente nel capitolo "Esempi di EJB" in cui è anche descritto come
compilarlo e deployarlo.

L'ejb è a scopo didattico, ho immaginato di avere la necessità di mantenere una


connessione nei confronti di un Database globale all'ejb, grazie ad essa diversi metodi
possono compiere operazioni su DB senza ogni volta aprire e chiudere una connessione.

Ho anche immaginato di dover mantenere dei dati di un cliente in variabili di classe


dell'ejb, dati che vengono presi dal database.

StatefulDb.StatefulJdbcBean EJB - Bean Class


package StatefulDb;

import javax.ejb.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;

public class StatefulJdbcBean implements SessionBean {

private SessionContext ctx;


private DataSource ds = null;

transient private Connection con = null;


transient private PreparedStatement ps = null;
transient private ResultSet rs = null;

private String cognome = null;


private String nome = null;
private String p_iva = null;
private String cod_fiscale = null;

public StatefulJdbcBean() {}

public void setSessionContext(SessionContext c) {


ctx=c;
}

76
public void ejbCreate(){
InitialContext initCtx = null;
try {
initCtx = new InitialContext();
ds = (javax.sql.DataSource)
initCtx.lookup("java:comp/env/jdbc/DSCliente");
} catch(NamingException ne) {
System.out.println("UNABLE to get a connection from
[java:comp/env/jdbc/DSCliente]");
throw new EJBException("Unable to get a connection
from [java:comp/env/jdbc/DSCliente]");
} finally {
try {
if(initCtx != null) initCtx.close();
} catch(NamingException ne) {
System.out.println("Error closing context: " + ne);
throw new EJBException("Error closing context " + ne);
}
}
}

public void ejbRemove(){


try{
chiudiConnessione();
}catch(SQLException ex){
ex.printStackTrace();
throw new EJBException("Error closing resources " + ex);
}
}

public void ejbPassivate(){


try{
chiudiConnessione();
}catch(SQLException ex){
ex.printStackTrace();
throw new EJBException("Error closing resources " + ex);
}
}

public void ejbActivate(){


try{
apriConnessione();
}catch(SQLException ex){
ex.printStackTrace();
throw new EJBException("Error in opening a connection " + ex);
}
}

public void apriConnessione() throws SQLException {


con = ds.getConnection();
}

public void chiudiConnessione() throws SQLException {


if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
}

public void interroga(String str) throws SQLException {


ps = con.prepareStatement(str);
rs = ps.executeQuery();
if (rs.next()){

77
cognome=rs.getString("COGNOME");
nome=rs.getString("NOME");
p_iva=rs.getString("P_IVA");
cod_fiscale=rs.getString("COD_FISCALE");
}
}

public String getCognome(){


return cognome;
}

public String getNome(){


return nome;
}

public String getP_iva(){


return p_iva;
}

public String getCod_fiscale(){


return cod_fiscale;
}
}

Le parti in rosso sono quelle che io ho aggiunto tutto il resto, cioè le parti in giallo, è
standard per uno Stateful.

Nel momento in cui un client invoca la create(...) dalla Home Interface, il Container
passa ad invocare la corrispondente ejbCreate(...) dell'ejb, le create() in uno Stateful
possono essere in overloading e per ogni create(...) presente nella Home Interface,
deve corrispondere una ejbCreate(...) nella Bean Class.

Come tutte le classi anche un ejb può avere un costruttore, anche se è sempre meglio
inizializzare le proprietà di classe (o operazioni simili) tramite la create() che può essere
considerata il costruttore degli ejb session.

Nella create() dell'esempio, ho interrogato JNDI ed ho ottenuto un DataSource,


dopodichè ho chiuso la mia connessione a JNDI ed ho assegnato il DataSource alla
variabile globale "DataSource ds" in questa maniera il mio ejb deve accedere una sola
volta a jndi (è molto lento e non è buona norma interrogarlo spesso se non ce ne è il
reale bisogno).

Il metodo che viene chiamato successivamente alla create(), dopo che l'istanza è stata
creata, è il setSessionContext(SessionContext c) che vedremo in dettaglio in
seguito quando parleremo di transazioni, in poche parole qui si memorizza il riferimento
ad una zona di memoria "session context" riservata dal container a quella istanza
dell'ejb.

A questo punto l'istanza rimane in vita a meno che non venga invocata la remove() da
parte del client a cui corrisponde una ejbRemove() nella Bean Class, la remove()
solitamente viene invocata dalla Remote Interface.

In realtà se per molto tempo un ejb Stateful non viene richiamato da un client, questo
viene PASSIVATO cioè il container lo toglie dalla memoria e lo serializza su disco, se
successivamente dovesse arrivare un secondo time out per inutilizzo, tutti i riferimenti
a quell'istanza vengono cancellati.

78
La passivazione può essere gestita solo dal container, che la effettua invocando il
metodo della Bean Class ejbPassivate().

La passivazione avviene nei seguenti casi:

- Inutilizzo dell'ejb
- crisi di risorse sull'Application Server

Un ejb viene passivato solo e solamente se nessun client lo sta invocando e non ha
transazioni in corso.

Bisogna sempre tener conto che uno stateful può andare in passivazione, ed il
programmatore si deve preoccupare di chiudere all'interno del metodo ejbPassivate()
tutte le risorse che precedentemente aveva aperto/allocato.

Nell'esempio vengono chiuse tutte le risorse associate alla connessione.

Nella passivazione vengono scritte su disco i valori che le variabili di classe hanno in
quel momento.

Possono essere serializzati tutti quegli oggetti che sono serializzabili cioè che
implementano l'interfaccia Serializable.

I Thread ovviamente non sono serializzabili, ed una connessione ad un Db che è un


Thread non è quindi serializzabile, mentre il Datasource si, altrimenti sarebbe
impossibile riceverlo da JNDI.

Per questo motivo le seguenti proprietà di classe:

- transient private Connection con = null;


- transient private PreparedStatement ps = null;
- transient private ResultSet rs = null;

sono state dichiarate "transient", cioè al momento della serializzazione si dice al


container di non serializzare le citate proprietà.

Se prima del "secondo time out" il client riinvoca un metodo di quella istanza di
quell'ejb, il container deserializza l'oggetto e lo rimette in memoria ed invoca il metodo
ejbActivate().

A questo punto il programmatore deve preoccuparsi di scrivere all'interno della


ejbActivate() tutte quelle righe di codice necessarie per ripristinare all'interno dell'ejb
uno stato identico a quello che quella istanza aveva prima di essere passivata, nel caso
del nostro esempio basta riaprire la connessione.

I metodi presenti nella Bean Classe ed anche nella Remote Interface e quindi
richiamabili remotamente sono:
- apriConnessione(), metodo che si fa passare dal DataSource una connessione e la
setta come proprietà di classe dell'ejb.
- interroga(String query), metodo che riceve una query sql e grazie alla connessione
di classe la esegue, riceve dei valori dal db e con essi valorizza le proprietà di classe
[nome, cognome, p_iva, cod_fiscale]
- chiudiConnessione(), rilascia tutte le risorse sql allocate.
- gli accessor delle proprietà di classe che ritorano i loro valori [getNome(),
getCognome(), getP_iva(), getCod_fiscale()]

79
In alcuni casi utilizzo la EJBException (javax.ejb.EJBException), è comoda quando
vogliamo che un metodo di una istanza dell'ejb vada a dire al container che si è
verificato un errore e che non è il caso di andare avanti.

StatefulDb.StatefulJdbcHome EJB - Home Inteface


package StatefulDb;

import javax.ejb.*;
import java.rmi.*;

public interface StatefulJdbcHome extends EJBHome {


public StatefulJdbc create() throws CreateException, RemoteException;
}

Nella Home Interface ci deve essere la create(), che come abbiamo esposto nei
capitoli precedenti, ritorna lo stub della Remote Interface, nel nostro caso
"StatefulJdbc". Vi ricordo che nel caso degli Stateful la create() può essere in
overloading, posso quindi, per esempio, aggiungere nella Home Interface create(String
nome, String cognome) ma se lo faccio ad essa deve corrispondere il metodo
ejbCreate(String nome, String cognome) nella Bean Class.

StatefulDb.StatefulJdbc EJB - Remote Inteface


package StatefulDb;

import javax.ejb.*;
import java.rmi.*;

import java.sql.*;

public interface StatefulJdbc extends EJBObject {


public void interroga(String str) throws RemoteException,
SQLException;
public void apriConnessione() throws RemoteException, SQLException;
public void chiudiConnessione() throws RemoteException, SQLException;
public String getCognome()throws RemoteException;
public String getNome()throws RemoteException;
public String getP_iva()throws RemoteException;
public String getCod_fiscale()throws RemoteException;
}

Nella Remote Interface devo riportare i "prototipi" dei metodi della Bean Class che
voglio poter richiamare Remotamente. Ad ogni metodo dichiarato nella Remote deve
corrisponderne uno nella Bean Class, ovviamente non è vero il contrario, infatti posso
mettere nella Bean Class dei metodi che vengano richiamati da altri metodi di quella
Classe ma che non voglio che possano essere chiamati dall'esterno.

ejb-jar.xml
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems,
Inc.//DTD Enterprise JavaBeans 2.0//EN"
"http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">

<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>SFJdbcBean</ejb-name>
<home>StatefulDb.StatefulJdbcHome</home>
<remote>StatefulDb.StatefulJdbc</remote>
<ejb-class>StatefulDb.StatefulJdbcBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Container</transaction-type>

80
<resource-ref>
<res-ref-name>jdbc/DSCliente</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</session>
</enterprise-beans>
</ejb-jar>

weblogic-ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.
//DTD WebLogic 6.0.0 EJB//EN'
'http://www.bea.com/servers/wls60/ejb20/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>SFJdbcBean</ejb-name>
<reference-descriptor>
<resource-description>
<res-ref-name>jdbc/DSCliente</res-ref-name>
<jndi-name>TXDSCliente</jndi-name>
</resource-description>
</reference-descriptor>
<jndi-name>SFJdbc</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>

Per poter renderlo un "EJB" e per poterlo Deployare abbiamo bisogno dei suoi files XML
descrittori, ho riportato sia quello Standard dettato dalla Sun, sia quello necessario a
WebLogic, ma di solito la storia per gli atri AS è simile.

La cosa importante per ora da vedere di questi file, che verranno abbondantemente
descritti in seguito, è come sono statti messi in relazione l'uno con l'altro.

In ejb-jar.xml ho dovuto dare un nome di riferimento alla mia sezione <ejb-


name>SFJdbcBean</ejb-name>, questo perchè come vedremo in un jar posso
metterci piu ejb ed utilizzare un solo ejb-jar.xml con piu sezioni <ejb-name>...</ejb-
name> ovviamente ci sarà un solo weblogic-ejb-jar.xml che avrà quindi bisogno di
un riferimeto ad ogni sezione per aggiungere e "linkare" le sue informazioni.

Nel nostro caso quindi in ejb-jar.xml ho <ejb-name>SFJdbcBean</ejb-name> ed in


weblogic-ejb-jar.xml dico che il JNDI name dell'ejb dichiarato nella sezione
SFJdbcBean sarà <jndi-name>SFJdbc</jndi-name>.

Una volta Deployato l'ejb se lancio la console di WebLogic e vado a vedere il JNDI Tree
troverò:

81
Client
import javax.rmi.*;
import StatefulDb.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

StatefulJdbcHome home = (StatefulJdbcHome)


PortableRemoteObject.narrow(ic.lookup("SFJdbc"),
StatefulJdbcHome.class);

StatefulJdbc sfjdbc = (StatefulJdbc) home.create();

sfjdbc.apriConnessione();
sfjdbc.interroga("select * from Cliente
where COD_FISCALE = 'MRNFRZ68M29E463B'");
sfjdbc.chiudiConnessione();

System.out.println("Dati Cliente:");
System.out.println("-------------------------");
System.out.println(sfjdbc.getNome());
System.out.println(sfjdbc.getCognome());
System.out.println(sfjdbc.getP_iva());
System.out.println(sfjdbc.getCod_fiscale());
System.out.println("-------------------------");

82
sfjdbc.remove();

}catch (Exception e){


System.out.println(e);
}
}
}

Se lanciamo il nostro client otteniamo:

18.2 EJB SESSION STATELESS


Facciamo ora degli esempi di EJB Stateless, prendiamo come riferimento i due Statelees
presenti nel dominio per WebLogic [K-Tech.zip] presente nel capitolo "Esempi di EJB" e
chiamati:

- StatelessJdbc
- StatelessRowSet

Questi due ejb compiono le stesse operazioni e sono uno l'evoluzione dell'altro, ed in
linea di massima compiono la stessa operazione dello Stateful mostrato nel capitolo
precedente .

Nello Stateful lanciavamo una query e con il risultato valorizzavamo delle proprietà di
classe, questi Stateless lanciano la stessa query, ritornano lo stesso risultato ma non
valorizzano alcuna proprietà di classe, infatti uno stateless dovrebbe sempre dare dei
servizi a client remoti e non deve mantenere stati per uno specifico client, cosi' come
abbiamo detto nel capitolo "EJB Session".

StatelessJdbc.JdbcBean EJB - Bean Class


package StatelessJdbc;

import javax.ejb.*;
import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;

83
public class JdbcBean implements SessionBean {

private SessionContext ctx;

private DataSource ds;

public JdbcBean() {}

public void setSessionContext(SessionContext c) {


ctx=c;
}

public void ejbCreate(){


InitialContext initCtx = null;
try {
initCtx = new InitialContext();
ds = (javax.sql.DataSource)
initCtx.lookup("java:comp/env/jdbc/DSCliente");
} catch(NamingException ne) {
System.out.println("UNABLE to get a connection
from [java:comp/env/jdbc/DSCliente]");
throw new EJBException("Unable to get a connection
from [java:comp/env/jdbc/DSCliente]");
} finally {
try {
if(initCtx != null) initCtx.close();
} catch(NamingException ne) {
System.out.println("Error closing context: " + ne);
throw new EJBException("Error closing context" + ne);
}
}
}

public void ejbRemove() {}


public void ejbPassivate() {}
public void ejbActivate() {}

public void interroga(String str) {


Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
con = ds.getConnection();
ps = con.prepareStatement(str);
rs = ps.executeQuery();

ResultSetMetaData rsmd = rs.getMetaData();


int nCols = rsmd.getColumnCount();
String st;
while(rs.next()){
st = "";
for(int x = 0; x < nCols; x++){
st = st + rs.getString(x+1)+" ";
if(st == null) st = "NULL";
}
System.out.println("> " + st);
}

} catch (SQLException sqe) {


throw new EJBException(sqe.getMessage());
}

84
finally {
try {
if(rs != null) rs.close();
if(ps != null) ps.close();
if(con != null) con.close();
} catch (Exception ex) {System.out.println(ex);}
}
}

In linea di massima le regole sono le stesse dello Stateful, come si può notare mancano
le proprietà di classe, l'unica che ho messo è "private DataSource ds;" in questa
maniera tengo il riferimento ad un DataSource evitando così di fare accessi inutili a
JNDI, tutte le istanze dell'ejb (gli Stateless stanno in pool) avranno cosi' tutti lo stesso
"DataSource" e questo è sicuramente accettabile, quello che non sarebbe accettabile è
che tutti i client potessero vedere per esempio lo stesso valore nell'ipotetica proprietà di
classe String nome o String cognome etc, a fronte di query diverse.

La ejbActivate() e la ejbPassivate() in uno Stateless, significano rispettivamente


togliere una istanza dal Pool per darla a disposizione di un client e rimettere l'istanza
nel Pool.

il metodo interroga(String str) è puramente didattico, esegue una query e non


ritorna valori ma li stampa a video (ovviamente sulla console del Server).

Visto che nel capitolo dello Stateful ho mostrato come prendere dei dati da un ResulSet
usando il metodo rs.getString(), in questa occasione faccio la stessa cosa ma
utilizzando un ResultSetMetaData per calcolarmi dinamicamente il numero di colonne
e recuperare da ognuna di esse i dati, operazione valida quando si interroga una tabella
di cui non si conosce la struttura.

Ritornare i valori di una query remotamente puo' essere spesso fonte di problemi,
anche perchè il ResulSet non è serializzabile e per poterlo utilizzare bisogna mantenere
una connessione aperta.

Nell'esempio successivo mostrerò come utilizzare il CachedRowSet, popolarlo nell'ejb


chiudere la connessione e mandarlo ad un client, che lo utilizzerà come un ResulSet.

Il CachedRowSet era stato presentato nei capitoli JDBC 2.0 e RowSet.

StatelessJdbc.JdbcHome EJB - Home Inteface


package StatelessJdbc;

import javax.ejb.*;
import java.rmi.*;

public interface JdbcHome extends EJBHome {


public Jdbc create() throws CreateException, RemoteException;
}

Nella Home Interface di uno Stateless deve esserci la create(), e non può essere in
overloading come nello Stateful, per non dare la possibilità a client diversi di valorizzare
una istanza con uno stato definito dallo stesso client.

Avendo a disposizione solo la create() e senza avere la possibilità di passargli dei valori,
tutti i client avranno istanze valorizzate alla stessa maniera, come in questo esempio in
cui recuperiamo un riferimento ad un DataSource.

85
StatelessJdbc.Jdbc EJB - Remote Inteface
package StatelessJdbc;

import javax.ejb.*;
import java.rmi.*;

public interface Jdbc extends EJBObject {


public void interroga(String str) throws RemoteException;
}

Nella Remote Interface ho dichiarato il prototipo del metodo "public void


interroga(String str)" dichiarato nella Bean Class, questo sarà l'unico metodo
richiamabile remotamente.

ejb-jar.xml
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems,
Inc.//DTD Enterprise JavaBeans 2.0
//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">

<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>SLJdbcBean</ejb-name>
<home>StatelessJdbc.JdbcHome</home>
<remote>StatelessJdbc.Jdbc</remote>
<ejb-class>StatelessJdbc.JdbcBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<resource-ref>
<res-ref-name>jdbc/DSCliente</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</session>
</enterprise-beans>
</ejb-jar>

weblogic-ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.
//DTD WebLogic 6.0.0 EJB
//EN' 'http://www.bea.com/servers/wls60/ejb20/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>SLJdbcBean</ejb-name>
<reference-descriptor>
<resource-description>
<res-ref-name>jdbc/DSCliente</res-ref-name>
<jndi-name>TXDSCliente</jndi-name>
</resource-description>
</reference-descriptor>
<jndi-name>SLJdbc</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>

I files xml descrittori sono identici a quelli dello Stateful, l'unica riga che li distingue è
quella del file ejb-jar.xml:
<session-type>Stateless</session-type>

Client

86
package client;

import javax.naming.*;
import java.rmi.*;
import javax.rmi.*;
import StatelessJdbc.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

JdbcHome home = (JdbcHome)

PortableRemoteObject.narrow(ic.lookup("SLJdbc"),JdbcHome.class);

Jdbc jdbc = (Jdbc) home.create();

jdbc.interroga("select * from Cliente");

jdbc.remove();

}catch (Exception e){


System.out.println(e);
}
}
}

87
19 EJB ENTITY
Abbiamo descritto gli EJB Session, iniziamo ora a descrivere gli EJB di tipo Entity che si
suddividono in:

• Bean Managed Persistence (BMP)

• Container Managed Persistence (CMP)

In particolare dobbiamo capire quali vantaggi offrono a differenza dei Session e quando
è utile utilizzarli.

Gli EJB Entity sono una rappresentazione di "dati persistenti", nel senso che
rappresentano tramite le loro proprietà di classe dei dati che possono essere in un
sistema persistente quale un DataBase, un file etc.

Possono sopravvivere a "Crash" di sistema, in quanto espongono nelle loro proprietà di


classe dei valori solo dopo che c'è stata la certezza che gli stessi valori siano stati
correttamente inseriti nel sistema di persistenza dei dati (database, file etc).

Piu clients possono usare EJBs Entity che rappresentano gli stessi dati, ed il Container si
preoccupa della gestione degli accessi concorrenti al dato (Novità rispetto ai Session).

Molti credono che se esiste un Database, deve esistere un Entity, questo non è
propriamente corretto.

Quando un EJB Entity viene creato, i dati che tale EJB deve rappresentare ed esporre,
vengono inseriti nel sistema di persistenza dei dati, nel caso di un Database ciò avviene
per esempio tramite una insert, ed una copia di questi valori viene messa in memoria
nell'ejb nelle sue proprietà di classe.

Ogni volta che le proprietà di classe dell'ejb vengono modificate (per esempio tramite
gli accessor delle proprietà getxxx() e setxxx() ) allora il sistema di persistenza su cui
sono state inserite viene automaticamente aggiornato (tramite chiamate di metodi
dell'ejb da parte del Container, rispettando il ciclo di vita deli ejb Entity)

Una volta che il dato è stato inserito per esempio su un database il Container è in grado
di assegnare istanze di uno specifico ejb a diversi client e tramite opportuni valori che
verranno descritti nei capitoli relativi alle transazioni, sa come far rispettare l'accesso
concorrente ai dati.

Nel capitolo degli EJB Stateful ho mostrato come prendere dei dati da un Database
tramite una query ed assegnare quei valori a delle proprietà di classe.
Molti client possono fare una create() di quell'ejb, ad ognuno di essi viene data una
istanza con gli stessi valori assegnati (se eseguono la stessa query), se nell'ejb
inserisco altri metodi in grado di inserire, modificare e cancellare valori sul database, mi
potrei trovare nella situazione che mentre faccio un Update di un valore un'altro client
magari ne fa la remove (accesso concorrente al dato non gestito).
Quest'aspetto non viene considerato e gestito negli ejb session da parte del Container e
potrebbe essere ovviato solo se il programmatore implementa del codice, cosa non
facile.

Nel ciclo di vita di un Entity è previsto che il container invochi il metodo ejbLoad() ogni
volta che interrogo una proprietà di classe, se ho String nome, e chiamo getNome(),
prima il container invoca la ejbLoad() che prende dal DB il valore presente.

88
Anche in seguito ad una ejbStore() viene richiamata la ejbLoad(), ma questi metodi li
vedremo meglio nei due capitoli successivi.

Principali Metodi degli EJB Entity

Inserisce il dato nel DB e crea una istanza dell'ejb, deve


assegnare i valori ricevuti in ingresso alle proprietà di classe.
ejbCreate() (INSERT).
Controlla che non ci siano Primary Key Duplicate, ritorna
l'istanza di una Primary Key.

La ejbCreate() esegue la insert, poi viene chiamata la


ejbPostCreate() subito prima della commit(), solo qui si può
ejbPostCreate() essere certi che tutto sia andato bene in fase di creazione, in
effetti se devo passare il mio ejb reference (EJBObject) ad
un'altro ejb è opportuno farlo qui e non nella ejbCreate().
crea una istanza di un EJB senza inserire dati nel DB ma
ejbFindByPrimaryKey()
facendo una SELECT. Metodo che ritorna una Primary Key
Altri finder generici. Metodo che ritorna una Collection di
ejbFind<xxx>()
riferimenti a Primary Key.
setEntityContext() Viene chiamato dal container dopo la ejbCreate()
Per dire al container di rimuovere l'istanza dal pool senza
unsetEntityContext()
cancellare il dato.
Come per gli Stateless, riinizializza l'ejb dopo una passivate,
ejbActivate()
rimette a disposizione l'ejb dal pool.
Come per gli Stateless, libera le risorse prima di venire
ejbPassivate()
rimesso nel pool.
Carica il dato dal DB e rivalorizza le proprietà di classe
ejbLoad()
dell'ejb (SELECT)
Inserisce il dato nel DB ogni volta che il dato è stato
ejbStore()
modificato (UPDATE)
Rimuove il dato dal DB e distrugge l'istanza dell'ejb.
ejbRemove()
(DELETE)

EJB Entity Primary Keys:

Ogni Entity ha un insieme di proprietà che sono univoche quando sono aggregate
insieme, l'aggregazione di queste proprietà sono chiamate primary key di un Entity EJB.
Uno dei concetti fondamentali degli Entity EJBs è che sono condivisibili da più clients.
Questo non significa che più clients devono accedere alla stessa istanza di ejb, ma
piuttosto che devono condividere gli stessi dati.
I clients devono quindi avere un meccanismo per identificare il dato a cui vogliono
accedere, questo meccanismo è la dichiarazione di una primary key all'interno di un
Entity ejb, che può essere paragonata alla chiave primaria di un database.
Quindi se le proprietà di un mio ejb entity mappano le colonne di una tabella, e se una
di queste colonne è un ID che è chiave primaria per la mia tabella, nel mio ejb avrò una
proprietà ID dello stesso tipo dichiarata come primary key.
L'importante non è partire dalle chiavi primarie delle tabelle, ma dalla dichiarazione di
chiavi primarie o di aggregazioni di chiavi primarie che siano univoche all'interno dei
miei ejb entity.
Due EJB Entity possono essere confrontati, confrontando il valore delle sue chiavi
primarie, se due primary key hanno lo stesso valore, allora abbiamo a che fare con due
Entity identici, cioè che mappano gli stessi dati, il che non significa che stiamo parlando
della stessa istanza.

Riassumendo, le Primary Keys:

89
- sono dichiarate nel deployment descriptor (xml) dell'ejb.
- vengono create ed inizializzate nel metodo ejbCreate(...).
- sono rappresentate da una proprietà di classe (tra le proprietà che devono essere
mappate sul sistema persistente) di un ejb entity.
- possono essere l'aggregazione di uno o più proprietà di classe tra quelle che devono
essere mappate sul sistema persistente.

Quando la primary key è il risultato di una aggregazione di proprietà di classe quali


campi persistenti, il valore di tale aggragazione deve essere univoco.

Se un Entity ejb viene creato con un valore che è già stato usato il client otterrà
l'eccezione DuplicateObjectException che indica che si sta cercando di creare un
entity con una identità che già esiste.

Le Primary Keys complesse, quelle che risultano composte da una aggregazione di


proprietà di classe quali campi persistenti, richiedono lo sviluppo di una classe che di
solito per convenzione ha il nome che termina con PK e che ha al suo interno tutte
quelle proprietà di classe che sono state indicate quale aggregazione per la Primary
Key, e di questo ne farò degli esempi nei capitoli successivi.

EJB Entity Finder Methods:

Tutti gli Entity ejb devono avere almeno un metodo di find, questi metodi vengono usati
dai clients per caricare uno o più ejb in memoria.
Un metodo di find, a seconda di come sia stato dichiarato, può ritornare un riferimento
ad un unico ejb oppure a più ejb.
Quindi dal metodo di find otterrò una remote interface (o local nella specifica 2.0)
oppure una java.util.Collection (oppure con il jdk1.1 una java.util.Enumeration) di
remote interface.
Tutti gli Entity devono avere nella home interface la
findByPrimaryKey(PrimaryKeyClass key).
Questi metodi devono iniziare con find<xxx>(...), devono fare il trow di
RemoteException nella remote interface ed in generale di FinderException.
Solo nei BMP ad ogni find<method>(...) deve corrispondere un ejbFind<method>(...)
nella Bean class.
L'ejbFind<method>(...) deve ritornare una istanza della chiave primaria oppure una
Collection di chiavi primarie, inoltre deve fare il throw di ObjectNotFoundException
se non è in grado di trovare alcun dato secondo i criteri di ricerca richiesti.
E' importante ricordare che non è compito del finder method di caricare i valori dei dati
nelle proprietà dell'ejb, il loro compito è solo quello di localizzare e caricare il valore
della chiave primaria che identificherà quei dati che si cercano.
Sarà il Container successivamente a richiamare il metodo ejbLoad() che è
responsabile della valorizzazione delle proprietà di classe dell'ejb con i valori trovati.

EJB Entity BMP [Bean Managed Persistence]:

La persistenza del dato è gestita da codice sviluppato dal programmatore ed inserito nei
vari metodi dell'ejb.

In questi EJB il sistema persistente in cui inserire il dato può essere qualsiasi, non
necessariamente un Database:

- File, TCP Socket, OS Pipe, altri EJB Entity, applicazioni, etc ...

Questi EJB devono essere usati quando il sistema persistente è diverso da un Database,

90
oppure nel caso di un database quando per gestire il dato devo utilizzare query
complesse che necessitano per esempio di Join.

In effetti i CMP sono in grado di mappare le proprietà di classe di un ejb semplicemente


rispetto ad una riga di una Tabella, problematica risolvibile con la specifica 2.0 degli
Ejb.

EJB Entity CMP [Container Managed Persistence]:

Si delega il controllo della persistenza del Dato al Container (non si deve sviluppare
codice), che riesce a farlo seguendo le regole del ciclo di vita degli Ejb Entity e
seguendo anche le regole scritte dallo sviluppatore nei Files xml descrittori dell'ejb che
per un Entity arrivano spesso a 3.

Un EJB Entity CMP è piu performate di un EJB Entity BMP, e se si può utilizzare è
sempre da preferire in quanto sfrutta tutte le funzionalità avanzate e specifiche del
Container inerenti il "Data Caching".

EJB Entity ciclo di Vita:

Un Entity EJB può essere in uno dei seguenti tre stati durante il suo ciclo di vita:
Does not exist, pooled, ready.

Gli EJB che si trovano nello stato Pooled, devono essere considerati degli oggetti, vuoti,
nudi. Sono oggetti che sono stati creati ed a cui è stato dato un contesto per lavorare
ma non hanno alcun valore assegnato e quindi nessuna Primary Key, quindi non hanno
una propria identità. Il Container in questo stato li considera tutti identici, come se
fossero degli Stateless EJB in Pool.

Tutte le azioni di create() , find() o altri metodi della home interface, vengono rivolte
dal container nei confronti di un qualsiasi EJB che non ha identità e che si trova nello
stato Pooled.

La chiamata di una find() non significa spostare un istanza di un EJB da pooled a ready.

91
Solo quando il container ha la necessità di avere una istanza valorizzata con una sua
identità e con una primary Key, allora in quel momento sposta una istanza da pooled a
ready.

Il processo legato allo spostamento da Pooled a Ready viene chiamato Attivazione.

La passivazione al contrario significa rimuovere l'identità da un ejb e spostarlo da ready


a pooled.

La attivazione e la passivazione, non hanno bisogno di persistenza di secondo livello,


come per esempio la serializzazione, in quanto le proprietà sono ovviamente già rese
persistenti sul sistema persistente a cui l'ejb è associato.

E' da notare che un ejb mosso in stato di ready e con una primary key associata, non è
detto che abbia subito le sue proprietà di classe valorizzate, questo dipende da come il
container è ottimizzato e da quando viene chiamato l'ejbLoad().

Il passaggio da "does not exist" a "pooled" dipende dal numero di istanze e dai client
che le richiedono, il Container controlla ed ottimizza il tutto.

Il container chiama i metodi ejbLoad() ed ejbStore() durante differenti fasi del ciclo di
vita dell'entity ejb.
Vengono chiamati tutte le volte che ritiene necessario sincronizzarsi con sistema di
persistenza, il che può dipendere dalle transazioni e dal sistema di isolamento nonchè
da altri fattori.

Negli esempi che seguiranno vedremo come minimizzare il numero delle chiamate a
questi metodi.

19.1 EJB ENTITÀ B.M.P.


Prima di iniziare con la spiegazione dell'esempio diamo una occhiata allo script di
creazione della tabella su cui andiamo a lavorare (il file CreaDB.cmd lancia lo script
DBSchema.ddl; questi file li trovate nella radice del dominio K-Tech scaricabile dalla
pagina "Esempi di EJB"):

DBSchema.ddl
DROP TABLE CLIENTE;

CREATE TABLE CLIENTE


(
NOME VARCHAR(25) ,
COGNOME VARCHAR(25) ,
COD_FISCALE VARCHAR(25) NOT NULL,
P_IVA VARCHAR(25)
);

ALTER TABLE CLIENTE


ADD CONSTRAINT PK Primary Key (COD_FISCALE);

INSERT INTO CLIENTE VALUES('Fabrizio', 'Marini', 'MRNFRZ68M29E463B',


'05671401007');

La tabella con cui lavoriamo è sempre la stessa degli esempi precedenti, e l'ejb entity
bmp che ho scritto altro non è che lo Stateful descritto in "Session Stateful" trasformato in
Entity, nel capitolo precedente vi ricordo che ho descritto i vantaggi di un Entity rispetto
ad un Session.

92
EntityBMP.ClienteBean EJB - Bean Class
package EntityBMP;

import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.*;
import java.util.*;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;
import java.util.Enumeration;

public class ClienteBean implements EntityBean{

private String nome;


private String cognome;
private String cod_fiscale;
private String p_iva;

private EntityContext context;


private DataSource ds = null;

// flag per determinare se il Bean ha la necessità di scrivere su DB


private transient boolean isDirty;

public boolean isModified() {


return isDirty;
}

public void setModified(boolean flag) {


isDirty = flag;
}

//implement methods from the EntityBean interface


public void ejbActivate() {System.out.println(">>> [ejbActivate]");}
public void ejbPassivate() {System.out.println(">>> [ejbPassivate]");}

public void setEntityContext(EntityContext ctx) {


System.out.println(">>> [setEntityContext]");
this.context = ctx;
}

public void unsetEntityContext() throws RemoteException {


System.out.println(">>> [unsetEntityContext]");
this.context = null;
}

public void ejbLoad() throws RemoteException{


System.out.println(">>> [ejbLoad]");
try {
refresh((String) context.getPrimaryKey());
} catch (FinderException fe) {
throw new RemoteException(fe.getMessage());
} catch (RemoteException re) {
throw new RemoteException(re.getMessage());
}
}

public void ejbStore(){


if (!isModified())
return;

93
else
{
System.out.println(">>> [ejbStore]");
Connection con = null;
PreparedStatement ps = null;
try {
con = ds.getConnection();
ps = con.prepareStatement("update Cliente set
NOME=?,
COGNOME=?, P_IVA=? where
COD_FISCALE=?");
ps.setString(1, getNome());
ps.setString(2, getCognome());
ps.setString(3, getP_iva());
ps.setString(4, getCod_fiscale());

int i = ps.executeUpdate();

} catch (SQLException sqe) {


throw new EJBException(sqe.getMessage());
}
finally {
try {
if (ps != null) ps.close();
if (con != null) con.close();
} catch (Exception ignore) {}
}
setModified(false);
}
}

public void ejbRemove() throws RemoveException {


System.out.println(">>> [ejbRemove]");

// we need to get the primary key from the context because


// it is possible to do a remove right after a find, and
// ejbLoad may not have been called.

Connection con = null;


PreparedStatement ps = null;
try {
con = ds.getConnection();
String pk = (String) context.getPrimaryKey();
ps = con.prepareStatement("delete from CLIENTE where
COD_FISCALE = ?");
ps.setString(1, pk);
int i = ps.executeUpdate();
if (i == 0) {
throw new RemoveException("ClienteBean (" + pk + ") not
found");
}
} catch (SQLException sqe) {
throw new RemoveException(sqe.getMessage());
}
finally {
try {
if (ps != null) ps.close();
if (con != null) con.close();
} catch (Exception ignore) {}
}
}

94
private void setDataSource(){
InitialContext initCtx = null;
try {
initCtx = new InitialContext();
ds =
(javax.sql.DataSource)initCtx.lookup("java:comp/env/jdbc/DSBMP");
} catch(NamingException ne) {
System.out.println("UNABLE to get a connection
from [java:comp/env/jdbc/DSBMP]");
throw new EJBException("Unable to get a connection
from [java:comp/env/jdbc/DSBMP]");
} finally {
try {
if(initCtx != null) initCtx.close();
} catch(NamingException ne) {
System.out.println("Error closing context: " + ne);
throw new EJBException("Error closing context" + ne);
}
}
}

//for each create there needs to be an ejbCreate and ejbPostCreate


public String ejbCreate(String nome,
String cognome,
String cod_fiscale,
String p_iva) throws CreateException {
System.out.println(">>> [ejbCreate]");

Connection con = null;


PreparedStatement ps = null;

try {
setDataSource();
con = ds.getConnection();
ps = con.prepareStatement("insert into CLIENTE
(NOME, COGNOME, COD_FISCALE, P_IVA)
values (?, ?, ?, ?)");
ps.setString(1, nome);
ps.setString(2, cognome);
ps.setString(3, cod_fiscale);
ps.setString(4, p_iva);

if (ps.executeUpdate() != 1) {
throw new CreateException("JDBC did not create any
row");
}
this.nome=nome;
this.cognome=cognome;
this.cod_fiscale=cod_fiscale;
this.p_iva=p_iva;

return cod_fiscale;

} catch (CreateException ce) {


throw ce;
} catch (SQLException sqe) {
throw new CreateException(sqe.getMessage());
}
finally {
try {
if (ps != null) ps.close();
if (con != null) con.close();

95
} catch (Exception ignore) {}
}
}

public void ejbPostCreate(String nome,


String cognome,
String cod_fiscale,
String p_iva) {
System.out.println(">>> [ejbPostCreate]");
}

//implement the findByPrimaryKey method


public String ejbFindByPrimaryKey(String pk)
throws FinderException, RemoteException {
System.out.println(">>> [ejbFindByPrimaryKey]");
if (pk == null)
throw new FinderException("primary key cannot be
null");

refresh(pk);
return pk;
}

//helper method for ejbFindByPrimaryKey(...)


private void refresh(String pk)
throws FinderException, RemoteException {
System.out.println(">>> [refresh]");
if (ds==null) setDataSource();
if (pk == null) {
throw new EJBException("primary key cannot be
null");
}
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = ds.getConnection();
ps = con.prepareStatement("select * from Cliente
where COD_FISCALE = ?");
ps.setString(1, pk);
rs = ps.executeQuery();
if (rs.next()) {
this.cod_fiscale=pk;
this.cognome=rs.getString("COGNOME");
this.nome=rs.getString("NOME");
this.p_iva=rs.getString("P_IVA");
} else {
throw new FinderException
("Refresh: ClientBean (" + pk + ") not
found");
}
} catch (SQLException sqe) {
throw new EJBException(sqe.getMessage());
}
finally {
try {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (con != null) con.close();
} catch (Exception ignore) {}
}
}

96
//(SET) implement business methods declared in Remote interface
public void setNome(String nome){
System.out.println(">>> [setNome]");
this.nome=nome;
setModified(true);
}

public void setCognome(String cognome){


System.out.println(">>> [setCognome]");
this.cognome=cognome;
setModified(true);
}

private void setCod_fiscale(String cod_fiscale){


System.out.println(">>> [setCod_fiscale]");
this.cod_fiscale=cod_fiscale;
setModified(true);
}

public void setP_iva(String p_iva){


System.out.println(">>> [setP_iva]");
this.p_iva=p_iva;
setModified(true);
}

//(GET) implement business methods declared in Remote interface


public String getNome(){
System.out.println(">>> [getNome]");
return nome;
}

public String getCognome(){


System.out.println(">>> [getCognome]");
return cognome;
}

private String getCod_fiscale(){


System.out.println(">>> [getCod_fiscale]");
return cod_fiscale;
}

public String getP_iva(){


System.out.println(">>> [getP_iva]");
return p_iva;
}
}

Trattandosi di un Entity BMP ho provveduto a mettere tutta la logica applicativa per la


persistenza dei dati nei vari metodi.

Per ottimizzare l'ejb ho inserito un flag isDirty gestito dai metodi isModified() ed
setModified(boolean flag) che a loro volta vengono richiamati nei set degli accesor
delle proprietà di classe e nel metodo ejbStore().
Operando in questa maniera evito inutili scritture su disco rindondanti dei dati, che
spesso avvengono anche se il dato non è stato modificato, ma per necessità di
sincronizzazione del container con il DB in situazioni di incertezza o simili.
Se andate alla fine del documento potrete vedere che cosa succede se commentiamo
tutte le righe di codice in blu, quelle relative al flag isDirty.

97
Nella create mi preoccupo di valorizzare le proprietà di classe.

EntityBMP.ClienteHome EJB - Home Inteface


package EntityBMP;

import javax.ejb.EJBHome;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.FinderException;

public interface ClienteHome extends EJBHome{

public Cliente create(String nome,


String cognome,
String cod_fiscale,
String p_iva) throws CreateException,
RemoteException;

public Cliente findByPrimaryKey(String cod_fiscale) throws


CreateException,

FinderException,

RemoteException;

Nella home devo avere la findByPrimaryKey(...) a cui corrisponde la


ejbFindByPrimaryKey(...) nella Bean class
EntityBMP.Cliente EJB - Remote Inteface
package EntityBMP;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Cliente extends EJBObject{

public void setNome(String nome) throws RemoteException;


public void setCognome(String cognome) throws RemoteException;
public void setP_iva(String p_iva) throws RemoteException;

public String getNome() throws RemoteException;


public String getCognome() throws RemoteException;
public String getP_iva() throws RemoteException;

Ecco i file xml descrittori dell'ejb:


ejb-jar.xml
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems,
Inc.//DTD Enterprise JavaBeans 2.0
//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">

<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>ClienteBean</ejb-name>
<home>EntityBMP.ClienteHome</home>

98
<remote>EntityBMP.Cliente</remote>
<ejb-class>EntityBMP.ClienteBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<resource-ref>
<res-ref-name>jdbc/DSBMP</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>ClienteBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

Le novità nell'ejb-jar.xml sono:


<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class
La prima indica il tipo di persistenza, Bean=BMP (Bean Managed Persistence)
La seconda indica di che tipo è la chiave primaria che nel nostro esempio è la proprietà
String cod_fiscale; corrispondente alla colonna COD_FISCALE della tabella CLIENTE.
weblogic-ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.
//DTD WebLogic 6.0.0 EJB
//EN' 'http://www.bea.com/servers/wls60/ejb20/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>
<weblogic-enterprise-bean>

<ejb-name>ClienteBean</ejb-name>
<reference-descriptor>
<resource-description>
<res-ref-name>jdbc/DSBMP</res-ref-name>
<jndi-name>TXDSCliente</jndi-name>
</resource-description>
</reference-descriptor>
<jndi-name>EntityBMP</jndi-name>

</weblogic-enterprise-bean>
</weblogic-ejb-jar>

Vedremo come nei CMP il codice si semplifica a fronte di XML descrittori molto più
complessi.

Client
package client;

import javax.naming.*;
import java.rmi.*;
import javax.rmi.*;

99
import EntityBMP.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

ClienteHome home = (ClienteHome)


PortableRemoteObject.narrow(ic.lookup("EntityBMP"),
ClienteHome.class);

System.out.println("Creo ...");
Cliente cli = (Cliente) home.create("Billy",
"Gates",
"GTSBLL58M29E463B",
"05578461129");

System.out.println("Stampo ...");
System.out.println(cli.getNome() +" - "+
cli.getCognome() +" - "+
cli.getP_iva());

System.out.println("Modifico il Nome ...");


cli.setNome("Bill");

System.out.println(cli.getNome() +" - "+


cli.getCognome() +" - "+
cli.getP_iva());

System.out.println("Rimuovo ...");
cli.remove();

System.out.println("findByPrimaryKey ...");
cli = (Cliente) home.findByPrimaryKey("MRNFRZ68M29E463B");

System.out.println("Stampo ...");
System.out.println(cli.getNome() +" - "+
cli.getCognome() +" - "+
cli.getP_iva());

}catch (Exception e){


System.out.println(e);
}
}
}

Se lanciamo il Client otteniamo come output sulla finestra da cui lo abbiamo lanciato:

100
Interessante è l'output che otteniamo sulla finestra del Server che ci fa capire quale è il
ciclo di vita di un Entity EJB e di come il Container interviene su esso:

Vediamo ora come cambia l'output sulla finestra del server se commentiamo tutte le
righe di codice in blu ( quelle relative al flag isDirty ) ricompiliamo l'ejb e rilanciamo il
client.

In realtà basta commentare le seguenti righe in ejbStore():


...

public void ejbStore(){


/*if (!isModified())
return;
else*/
{
System.out.println(">>> [ejbStore]");

...

101
Come potete notare la situazione è notevolmente diversa l'ejbStore viene chiamato
molto spesso ed incredibilmente anche dopo la get di una variabile :)

102
19.2 EJB ENTITY C.M.P.
Nel capitolo precedente Entiy BMP ho mostrato come trasformare uno Stateful in un
equivalente EJB Entity di tipo BMP, ovviamente ricordando che un Entity ha un ciclo di
vita e dei vantaggi diversi rispetto allo Stateful; in questo capitolo mostrerò come
trasformare l'EJB Entity BMP del capitolo precedente in EJB Entity CMP, ottimo esercizio
per paragonare i due tipi di EJB Entity.

Come sempre i sorgenti si trovano nel dominio K-Tech scaricabile dalla pagina "Esempi di
EJB".

Come possiamo vedere di seguito il codice è molto piu' semplice, non c'è traccia di
accessi al DataBase, si lavora solo rispetto alle proprietà di classe, è il container che
interviene rispettando il ciclo di vita e le chiamate ai metodi degli ejb Entity nel
momento in cui le proprietà di classe sono accedute e/o modificate.

Tutte le informazioni di cui ha bisogno il container per accedere al database sono scritte
nei file descrittori XML dell'ejb.
EntityCMP.ClienteCmpBean EJB - Bean Class
package EntityCMP;

import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.*;
import java.util.*;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;
import java.util.Enumeration;

public class ClienteCmpBean implements EntityBean{

private EntityContext ctx;

public String nome;


public String cognome;
public String cod_fiscale;
public String p_iva;

// flag per determinare se il Bean ha la necessità di scrivere su DB


private transient boolean isDirty;

public boolean isModified() {


return isDirty;
}

public void setModified(boolean flag) {


isDirty = flag;
}

public void ejbLoad() {System.out.println(">>> [ejbLoad]");}

public void ejbStore(){


if (!isModified())
return;
else{
System.out.println(">>> [ejbStore]");
setModified(false);
}
}

103
public String ejbCreate(String nome,
String cognome,
String cod_fiscale,
String p_iva) {
System.out.println(">>> [ejbCreate]");
this.nome=nome;
this.cognome=cognome;
this.cod_fiscale=cod_fiscale;
this.p_iva=p_iva;
return null;
}

public void ejbPostCreate(String nome,


String cognome,
String cod_fiscale,
String p_iva)
{System.out.println(">>> [ejbPostCreate]");}
public void ejbRemove() {System.out.println(">>> [ejbRemove]");}
public void ejbActivate() {System.out.println(">>> [ejbActivate]");}
public void ejbPassivate() {System.out.println(">>> [ejbPassivate]");}

public void setEntityContext(EntityContext ctx){


System.out.println(">>> [setEntityContext]");
this.ctx = ctx;
}

public void unsetEntityContext(){


System.out.println(">>> [unsetEntityContext]");
this.ctx = null;
}

//(SET) Metodi Set


public void setNome(String nome){
System.out.println(">>> [setNome]");
setModified(true);
this.nome=nome;
}

public void setCognome(String cognome){


System.out.println(">>> [setCognome]");
setModified(true);
this.cognome=cognome;
}

public void setP_iva(String p_iva){


System.out.println(">>> [setP_iva]");
setModified(true);
this.p_iva=p_iva;
}

//(GET) Metodi Get


public String getNome(){
System.out.println(">>> [getNome]");
return nome;
}

public String getCognome(){


System.out.println(">>> [getCognome]");
return cognome;
}

104
public String getP_iva(){
System.out.println(">>> [getP_iva]");
return p_iva;
}

Anche nei CMP posso aiutare il container per assicurarlo sull'effettiva necessità di
richiamare la ejbStore() usando il flag isDirty già descritto nel capitolo precedente
(Entity BMP EJB).
EntityCMP.ClienteCmpHome EJB - Home Inteface
package EntityCMP;

import javax.ejb.EJBHome;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.FinderException;

public interface ClienteCmpHome extends EJBHome{

public ClienteCmp create(String nome,


String cognome,
String cod_fiscale,
String p_iva) throws CreateException,
RemoteException;

public ClienteCmp findByPrimaryKey(String cod_fiscale) throws


CreateException,

FinderException,

RemoteException;

EntityCMP.ClienteCmp EJB - Remote Inteface


package EntityCMP;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface ClienteCmp extends EJBObject{

public void setNome(String nome) throws RemoteException;


public void setCognome(String cognome) throws RemoteException;
public void setP_iva(String p_iva) throws RemoteException;

public String getNome() throws RemoteException;


public String getCognome() throws RemoteException;
public String getP_iva() throws RemoteException;

Passiamo ora a descrivere i file descrittori XML per gli Entity CMP EJB che in WebLogic
(e molti altri AS) arrivano ad essere tre:
ejb-jar.xml

105
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems,
Inc.//DTD Enterprise JavaBeans 1.1//EN'
'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>ClienteCmpBean</ejb-name>
<home>EntityCMP.ClienteCmpHome</home>
<remote>EntityCMP.ClienteCmp</remote>
<ejb-class>EntityCMP.ClienteCmpBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field><field-name>nome</field-name></cmp-field>
<cmp-field><field-name>cognome</field-name></cmp-field>
<cmp-field><field-name>cod_fiscale</field-name></cmp-field>
<cmp-field><field-name>p_iva</field-name></cmp-field>

<primkey-field>cod_fiscale</primkey-field>

</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>ClienteCmpBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

Nei CMP devo dire quali sono le proprietà di classe che voglio rendere persistenti sul DB
e lo faccio, per esempio per la proprietà nome, con la seguente riga <cmp-
field><field-name>nome</field-name></cmp-field>

Devo indicare oltre al tipo di chiave primaria anche quale è la proprietà che la
identifica:
- <prim-key-class>java.lang.String</prim-key-class>
- <primkey-field>cod_fiscale</primkey-field>
weblogic-ejb-jar.xml
<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC


"-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"
"http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd" >

<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>ClienteCmpBean</ejb-name>
<entity-descriptor>
<persistence>
<persistence-type>
<type-identifier>WebLogic_CMP_RDBMS</type-identifier>
<type-version>5.1.0</type-version>
<type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-
storage>

106
</persistence-type>
<persistence-use>
<type-identifier>WebLogic_CMP_RDBMS</type-identifier>
<type-version>5.1.0</type-version>
</persistence-use>
</persistence>
</entity-descriptor>
<jndi-name>EntityCMP</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>

weblogic-cmp-rdbms-jar.xml
<!DOCTYPE weblogic-rdbms-jar PUBLIC
'-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB 1.1 RDBMS Persistence//EN'
'http://www.bea.com/servers/wls600/dtd/weblogic-rdbms11-persistence-
600.dtd'>

<weblogic-rdbms-jar>
<weblogic-rdbms-bean>
<ejb-name>ClienteCmpBean</ejb-name>
<data-source-name>TXDSCliente</data-source-name>
<table-name>CLIENTE</table-name>
<field-map>
<cmp-field>nome</cmp-field>
<dbms-column>NOME</dbms-column>
</field-map>
<field-map>
<cmp-field>cognome</cmp-field>
<dbms-column>COGNOME</dbms-column>
</field-map>
<field-map>
<cmp-field>p_iva</cmp-field>
<dbms-column>P_IVA</dbms-column>
</field-map>
<field-map>
<cmp-field>cod_fiscale</cmp-field>
<dbms-column>COD_FISCALE</dbms-column>
</field-map>
</weblogic-rdbms-bean>
<create-default-dbms-tables>False</create-default-dbms-tables>
</weblogic-rdbms-jar>

Come vedete il container ha tutte le informazioni per rendere persistenti sul DB i dati
infatti può ricavare dal weblogic-cmp-rdbms-jar.xml i seguenti dati:
- Nome Tabella su cui scrivere i dati: <table-name>CLIENTE</table-name>
- La tabella è raggiungibile attraverso uno specifico DataSource configurato
sull'Application Server: <data-source-name>TXDSCliente</data-source-name>
- I cmp Field dichiarati in ejb-jar.xml vengono mappati sulle colonne della Tabella
indicata attraverso le seguenti informazioni (esempio per la propietà nome e quindi
cmp field nome):
<field-map>
<cmp-field>nome</cmp-field>
<dbms-column>NOME</dbms-column>
</field-map>

Passiamo ora a vedere degli esempi:

In Client inserisco prima una riga sul DB tramite la create(), stampo i valori e rimuovo
la riga dal DB tramite la remove(), successivamente ricerco una riga esistente sul DB
tramite la findByPrimaryKey() e ne stampo i valori.

107
Client
package client;

import javax.naming.*;
import java.rmi.*;
import javax.rmi.*;
import EntityCMP.*;

class Client
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

ClienteCmpHome home = (ClienteCmpHome)

PortableRemoteObject.narrow(ic.lookup("EntityCMP"),ClienteCmpHome.class);

ClienteCmp cli = (ClienteCmp) home.create("Bill", "Gates",


"GTSBLL58M29E463B",
"05578461129");

System.out.println(cli.getNome() +" - "+


cli.getCognome() +" - "+
cli.getP_iva());
cli.remove();

cli = (ClienteCmp) home.findByPrimaryKey("MRNFRZ68M29E463B");

System.out.println(cli.getNome() +" - "+


cli.getCognome() +" - "+
cli.getP_iva());

}catch (Exception e){


System.out.println(e);
}
}
}

Output sulla finestra del Client:

Output sulla finestra del Server:

108
L'esempio seguente (MultyClient) simula un accesso multyclient ad uno stesso ejb
creato con la medesima chiave primaria i passi sono i seguenti:
1) (cli) crea una nuova riga nel DB con i dati ("Billy", "Gates", "GTSBLL58M29E463B",
"05578461129")
2) (cli_1), (cli_2), (cli_3) ottengono una istanza di un ejb ricercato con la chiave
primaria inserita da (cli) "GTSBLL58M29E463B"
3) (cli) modifica la proprietà di callse nome con il medodo accessor di quella proprietà
cli.setNome("Bill");
4) se stampo il valore delle proprietà di (cli_1), (cli_2), (cli_3) ottengo in nome il nuovo
valore inserito da (cli).

MultyClient
package client;

import javax.naming.*;
import java.rmi.*;
import javax.rmi.*;
import EntityCMP.*;

class MultyClient
{
public static void main(String args[]){
try{
InitialContext ic = new InitialContext();

ClienteCmpHome home = (ClienteCmpHome)

PortableRemoteObject.narrow(ic.lookup("EntityCMP"),ClienteCmpHome.class);

ClienteCmp cli = (ClienteCmp) home.create("Billy", "Gates",


"GTSBLL58M29E463B",
"05578461129");

System.out.println(cli.getNome() +" - "+


cli.getCognome() +" - "+
cli.getP_iva());

ClienteCmp cli_1 = (ClienteCmp)


home.findByPrimaryKey("GTSBLL58M29E463B");

109
ClienteCmp cli_2 = (ClienteCmp)
home.findByPrimaryKey("GTSBLL58M29E463B");
ClienteCmp cli_3 = (ClienteCmp)
home.findByPrimaryKey("GTSBLL58M29E463B");

cli.setNome("Bill");
System.out.println("Nome Modificato...");

System.out.println("[1] " + cli_1.getNome() +" - "+


cli_1.getCognome() +" - "+
cli_1.getP_iva());

System.out.println("[2] " + cli_2.getNome() +" - "+


cli_2.getCognome() +" - "+
cli_2.getP_iva());

System.out.println("[3] " + cli_3.getNome() +" - "+


cli_3.getCognome() +" - "+
cli_3.getP_iva());

cli.remove();

}catch (Exception e){


System.out.println(e);
}
}
}

Output sulla finestra del Client:

Output sulla finestra del Server:

110
111