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 JDBCRMI 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; Statement SQLStatement; ResultSet rsQuery;
// database connection // SQL statement // 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 import import import import import import import
javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*; javax.naming.*; java.sql.*; javax.sql.*; 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 regole di creazione di un File WAR (Web Archive).
MVC
seguiremo inoltre le
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 inoltre le regole di creazione di un File WAR (Web Archive).
MVC
seguendo
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 inoltre le regole di creazione di un File WAR (Web Archive). Dopo aver creato la Servlet ed il ServletResults.jsp: [download ServletResults.jsp]
JavaBean,
MVC
seguendo
passiamo a creare la JSP che chiameremo
43
risultato | <%=javaBean.getRisultato() %> |
Dobbiamo copiare la JSP ServletResults.jsp nella directory: c:\MioWar\ServletResults.jsp Soffermiamoci ora a parlare della riga:
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
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 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" %> <% 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" %> 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" %>
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
è 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 -- HTML + Servlet -- JavaBean -- JSP
MVC
, cioè :
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] Servlet mvc.Servlet ServletResults ServletResults.jsp Servlet Servlet ServletInput.html 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. <%! 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); %> <%! void componiIndirizzo(JspWriter out) throws java.io.IOException { out.println(""); out.println(nome + " " + cognome); out.println(indirizzo); out.println(citta+", "+cap); out.println("
"); } %> 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. <%! 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); } %> <%! void componiIndirizzo(JspWriter out) throws java.io.IOException { out.println(""); out.println(nome + " " + cognome); out.println(indirizzo); out.println(citta+", "+cap); out.println("
"); } %> 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] <% 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); %> <%! 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(""); out.println(dati.nome + " " + dati.cognome); out.println(dati.indirizzo); out.println(dati.citta+","+dati.cap); 53
out.println("
"); } %> 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 ConvBean ktech.ConvertitoreHome ktech.Convertitore ktech.ConvertitoreBean Stateless Container 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 ConvBean ConvEuro 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'ejbjar.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: ConvEuro 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 --> ConvEuro 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 import import import
javax.naming.*; java.rmi.*; javax.rmi.*; 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 Bea WebLogic 6.1
setEnv.cmd
- l'avvio automatico di Bea WebLogic 6.1
startServer.cmd
- la creazione dei DB (su cloudscape) necessari agli EJB, tramite file DDL.
CreaDB.cmd
- 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\KTech\esempi\StatefulJdbc\create_and_deploy_Ejb.cmd" 2) Lanciare il server tramite il file "VostroDisco:\bea\wlserver6.1\config\KTech\startServer.cmd" 3) Lanciare il Client tramite il file "VostroDisco:\bea\wlserver6.1\config\KTech\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 Stateless 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 import import import
javax.ejb.*; java.sql.*; javax.sql.*; 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 private private private
String String String String
cognome = null; nome = null; p_iva = null; 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 SFJdbcBean StatefulDb.StatefulJdbcHome StatefulDb.StatefulJdbc StatefulDb.StatefulJdbcBean Stateful Container 80
jdbc/DSCliente javax.sql.DataSource Container weblogic-ejb-jar.xml SFJdbcBean jdbc/DSCliente TXDSCliente SFJdbc 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 SFJdbcBean, questo perchè come vedremo in un jar posso metterci piu ejb ed utilizzare un solo ejb-jar.xml con piu sezioni ... 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 SFJdbcBean ed in weblogic-ejb-jar.xml dico che il JNDI name dell'ejb dichiarato nella sezione SFJdbcBean sarà SFJdbc. 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 import import import import
javax.ejb.*; java.util.*; java.sql.*; javax.sql.*; 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 StatelessJdbc.JdbcHome
JDBC 2.0
e
RowSet.
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 SLJdbcBean StatelessJdbc.JdbcHome StatelessJdbc.Jdbc StatelessJdbc.JdbcBean Stateless Container jdbc/DSCliente javax.sql.DataSource Container weblogic-ejb-jar.xml SLJdbcBean jdbc/DSCliente TXDSCliente SLJdbc I files xml descrittori sono identici a quelli dello Stateful, l'unica riga che li distingue è quella del file ejb-jar.xml: Stateless Client 86
package client; import import import import
javax.naming.*; java.rmi.*; javax.rmi.*; 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
ejbCreate()
Inserisce il dato nel DB e crea una istanza dell'ejb, deve assegnare i valori ricevuti in ingresso alle proprietà di classe. (INSERT). Controlla che non ci siano Primary Key Duplicate, ritorna l'istanza di una Primary Key.
ejbPostCreate()
La ejbCreate() esegue la insert, poi viene chiamata la ejbPostCreate() subito prima della commit(), solo qui si può 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().
ejbFindByPrimaryKey()
crea una istanza di un EJB senza inserire dati nel DB ma facendo una SELECT. Metodo che ritorna una Primary Key
ejbFind()
Altri finder generici. Metodo che ritorna una Collection di riferimenti a Primary Key.
setEntityContext()
Viene chiamato dal container dopo la ejbCreate()
unsetEntityContext()
Per dire al container di rimuovere l'istanza dal pool senza cancellare il dato.
ejbActivate()
Come per gli Stateless, riinizializza l'ejb dopo una passivate, rimette a disposizione l'ejb dal pool.
ejbPassivate()
Come per gli Stateless, libera le risorse prima di venire rimesso nel pool.
ejbLoad()
Carica il dato dal DB e rivalorizza le proprietà di classe dell'ejb (SELECT)
ejbStore()
Inserisce il dato nel DB ogni volta che il dato è stato modificato (UPDATE)
ejbRemove()
Rimuove il dato dal DB e distrugge l'istanza dell'ejb. (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(...), devono fare il trow di RemoteException nella remote interface ed in generale di FinderException. Solo nei BMP ad ogni find(...) deve corrispondere un ejbFind(...) nella Bean class. L'ejbFind(...) 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 import import import import import import import
java.io.Serializable; java.rmi.RemoteException; javax.ejb.*; java.util.*; java.sql.*; javax.naming.*; javax.sql.*; java.util.Enumeration;
public class ClienteBean implements EntityBean{ private private private private
String String String String
nome; cognome; cod_fiscale; 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, ps.setString(2, ps.setString(3, ps.setString(4,
getNome()); getCognome()); getP_iva()); 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 PreparedStatement
con = null; 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 import import import
javax.ejb.EJBHome; java.rmi.RemoteException; javax.ejb.CreateException; javax.ejb.FinderException;
public interface ClienteHome extends EJBHome{ public Cliente create(String String String String
nome, cognome, cod_fiscale, 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 ClienteBean EntityBMP.ClienteHome 98
EntityBMP.Cliente EntityBMP.ClienteBean Bean java.lang.String False jdbc/DSBMP javax.sql.DataSource Container ClienteBean * Required Le novità nell'ejb-jar.xml sono: Bean java.lang.String ClienteBean jdbc/DSBMP TXDSCliente EntityBMP 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 import import import import import import import
java.io.Serializable; java.rmi.RemoteException; javax.ejb.*; java.util.*; java.sql.*; javax.naming.*; javax.sql.*; java.util.Enumeration;
public class ClienteCmpBean implements EntityBean{ private EntityContext ctx; public public public public
String String String String
nome; cognome; cod_fiscale; 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 import import import
javax.ejb.EJBHome; java.rmi.RemoteException; javax.ejb.CreateException; javax.ejb.FinderException;
public interface ClienteCmpHome extends EJBHome{ public ClienteCmp create(String String String String
nome, cognome, cod_fiscale, 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
ClienteCmpBean EntityCMP.ClienteCmpHome EntityCMP.ClienteCmp EntityCMP.ClienteCmpBean Container java.lang.String False nome cognome cod_fiscale p_iva cod_fiscale ClienteCmpBean * Required 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 nome Devo indicare oltre al tipo di chiave primaria anche quale è la proprietà che la identifica: - java.lang.String - cod_fiscale weblogic-ejb-jar.xml ClienteCmpBean WebLogic_CMP_RDBMS 5.1.0 META-INF/weblogic-cmp-rdbms-jar.xml 106
WebLogic_CMP_RDBMS 5.1.0 EntityCMP weblogic-cmp-rdbms-jar.xml ClienteCmpBean TXDSCliente CLIENTE nome NOME cognome COGNOME p_iva P_IVA cod_fiscale COD_FISCALE False 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: CLIENTE - La tabella è raggiungibile attraverso uno specifico DataSource configurato sull'Application Server: TXDSCliente - 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): nome NOME 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 import import import
javax.naming.*; java.rmi.*; javax.rmi.*; 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 import import import
javax.naming.*; java.rmi.*; javax.rmi.*; 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