Introduzione
Le versioni dei prodotti Sun utilizzate per lo sviluppo sono JDK 1.1.6, JDBC 1.2 e JSDK 1.2, tutte scaricabili gratuitamente dal sito http://www.java.sun.com/products/.
L'esempio proposto estrae da un database i dati relativi a una distinta base richiesta dall'utente (tramite una semplice form HTML) e li visualizza sul browser in formato tabella (<table>); la pagina HTML generata è dinamica, cioè "on the fly".
Sotto vediamo il codice HTML della form con un semplice JavaScript (il fratello minore di Java), che esegue alcuni controlli utilizzando delle classi. L'oggetto document rappresenta l'attuale documento nella finestra attiva ed è figlio, cioè eredita metodi e attributi, della classe window. L'oggetto forms[] è una matrice che identifica tutte le window (dette formulari) presenti nella finestra principale caricata all'avvio del browser. Indichiamo poi con il nome assegnato nella form HTML il campo o variabile che vogliamo controllare: gli altri controlli mi sembrano abbastanza comprensibili. Assegniamo ora all'attributo action del formulario (oggetto forms[] di classe window) http://localhost:8000/servlet/SqlServlet, il "programma" (serie di classi) da eseguire a cui verranno sottoposti i dati delle variabili della form HTML (<input type=...>).
Avendo indicato con la keyword method la modalità POST (<form method=POST ...>), vengono passati al programma chiamato (attributo action) in modo implicito nel formato nome variabile=valore variabile. Il recupero di questi parametri è implementato nel metodo ExtractInput() della classe SqlDB, leggendo la struttura Java Enumeration, simile a una hash table.
La hash table è una struttura dati che permette, associando a un nome uno o più valori, di minimizzare il tempo di recupero dei valori dato il nome.
Da notare che lo script viene invocato solo quando si preme il bottone che ha come value="submit" e che fa riferimento alla keyword HTML <form ... onSubmit="return ...">.
In questo modo riusciamo a ottenere un semplice programma che controlla ed esegue il servlet proprio come se fosse un programma tradizionale. Il servlet viene lanciato sul computer locale alla porta 8000, che è seguita dalla keyword servlet e dal nome del servlet da eseguire.
<html>
<script language="JavaScript">
// controlla la correttezza dei dati inseriti e chiama il Servlet
function testForm() {
if (document.forms[0].snumber.value == "") {
document.forms[0].snumber.value = "0";
}
if (document.forms[0].snumber.value > "0" &&
document.forms[0].snumber.value < "9999") {
} else {
document.forms[0].snumber.value = "0";
}
if (document.forms[0].code.value == "") {
alert("Insert a valid Table code.");
}
if (document.forms[0].code.value != "") {
document.forms[0].action = "http://localhost:8000/servlet/SqlServlet";
}
}
</script>
<form method="POST" onSubmit="return testForm()">
<center>
<br><br>Table <input type="text" name="code" size="15" maxlength="15">
<br><br>Serial Number <input type="text" name="snumber" size="5" maxlength="5">
<br><br><br><input type="submit" value="Submit">
<input type="reset" value="Reset">
</center>
</form>
</body>
</html>
Configurazione ODBC
Bisogna innanzitutto inserire la voce relativa alla "fonte dati" ODBC andando nel Pannello di controllo, ODBC (Open Database Connectivity).
ODBC è uno standard divulgato da Microsoft e consente di usare una sola interfaccia, indipendente dal tipo di database e dal formato dei dati, per accedere a database di tipo diverso e creati mediante differenti applicativi di gestione. Utilizzando ODBC è possibile scrivere applicazioni che usano sempre il medesimo codice per leggere record da un file dBase oppure da un file Access. Per eseguire qualsiasi operazione sul database i driver ODBC usano, al proprio interno, una forma di SQL derivata dall'interfaccia CLI (Call Level Interface) dell'SQL Access Group. Definire una fonte dati significa rimappare il file .mdb (il database) con un nome logico che non bisognerà più cambiare, perché i programmi faranno riferimento al nome logico. Quindi, anche se il file .mdb viene rinominato o se viene modificata qualche struttura al suo interno, basterà cambiare l'aggancio sulla fonte ODBC e i programmi che lo usano non dovranno essere toccati.
Gestione del servlet
Ora cominciamo con la parte servlet. Questi, come sappiamo, sono "programmi" (classi invocate da JVM) che girano su Web Server in attesa di richieste da parte di qualche client. In altre parole il programma deve essere lanciato sul server e interagirà con esso spedendogli, in modalità GET o POST, i dati di form HTML.
Per poter eseguire delle classi servlet occorre avere un supporto che quasi tutti i Web Server hanno. Purtroppo IIS di Microsoft non lo ha; in questo caso si può utilizzare un prodotto di terze parti, come JRun della LiveSoftware, http://www.livesoftware.com/, del quale è possibile scaricare una versione trial. Esiste anche un tool freeware compreso nel JSDK di Sun: ServletRunner.exe, posto nella cartella bin. Questo simula un Web Server per poter provare e fare il debug dei propri servlet e si deve lanciare localmente. Più informazioni si possono trovare tra i documenti HTML compresi nell'installazione del kit.
Per chi non volesse o non potesse usare i servlet basterà sostituire la parte relativa a essi con il metodo main(). Invece di generare la pagina HTML si può fare un System.out.println(...) su console e lanciare il bytecode generato con il tool compreso nel JDK nel seguente modo, java nome.class, per poter visualizzare il risultato della query SQL (Structured Query Language). Questo dimostra quanto la soluzione servlet sia efficiente e applicabile utilizzando classi sviluppate non appositamente per i servlet; anche l'implementazione di un servlet utilizzando classi già esistenti e funzionanti (cioè testate) può risultare abbastanza banale.
Qui sotto è inserita la parte di codice relativa alla gestione servlet. Al metodo SqlDB, che eseguirà il passo successivo della richiesta, vengono passate le informazioni sulla comunicazione (req), il destinatario (toClient) e l'elenco dei parametri passati (values). La connessione viene aperta una sola volta al database nel metodo init() e chiusa nel metodo destroy(); inoltre viene gestita la concorrenza sull'accesso al database in modo che un solo client alla volta utilizzi il "canale" di connessione. In questo modo l'accesso risulta più veloce, sicuro e controllato.
import javax.servlet.*;
// necessari per ottenere la disponibilità delle classi Servlet
import javax.servlet.http.*;
public class SqlServlet extends HttpServlet {
PrintWriter toClient; // destinazione del client in output
Enumeration values; // elenco dei parametri passati in output
DrvMsAccess Sqlac; // istanza per aprire il database SQL
/*
* Inizializza il servlet. Override del metodo init(),
* viene chiamato solo all'apertura del servlet.
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
Sqlac = new DrvMsAccess("NomeFonteDati");
}
/*
* Funzione che intercetta le richieste e risponde alla form HTML.
* Override del metodo doPost().
*/
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// negozia il MIME per pagine HTML, header della risposta
res.setContentType("text/html");
// recupera il PrinterWriter del client che ha eseguito la richiesta
toClient = res.getWriter();
// parametri passati dalla form
values = req.getParameterNames();
// evade la richiesta del client, esegue la logica utente
SqlDB aaa = new SqlDB(req, toClient, values);
aaa.run();
}
/*
* Funzione richiesta dalla form HTML.
* Override del metodo doGet().
*/
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// negozia il MIME per pagine HTML, header della risposta
res.setContentType("text/html");
// recupera il client che ha eseguito la richiesta
toClient = res.getWriter();
values = req.getParameterNames();
// evade la richiesta del client
sqlDB aaa = new SqlDB(req, toClient, values);
aaa.run();
}
/* Override: distrugge il servlet. */
public void destroy() {
Sqlac.CloseCon(); // chiusura connessione DB SQL
}
}
Estrazione dei dati e generazione della pagina HTML
Ora vediamo come estrarre i dati richiesti dal client con la form, formattarli in una pagina HTML e rispedirli al richiedente. La cosa è molto semplice: bisogna prima estrarre i valori delle variabili per la ricerca (contenuti nella struttura di enumerazione values), lanciare la query SQL istanziando la classe DrvMsAccess e da ultimo formattare una semplice pagina HTML. Quando eseguiremo la close del PrintWriter, i dati così preparati verranno spediti al richiedente senza che noi dobbiamo preoccuparcene.
/* Classe per il servizio di accesso al database SQL tramite ODBC. */
class SqlDB {
// Sincronizzazione dell'handler thread per la concorrenza sulla risorsa "con".
// static perché è unica a fronte di più istanze della stessa classe.
static Object critsect = new Object();
String query = new String();
// codice distinta in input con valore di default
String Code = new String("?");
// matricola in input con valore di default
Integer SNumber = new Integer(0);
PrintWriter toClient; // destinazione del client
Enumeration values; // elenco dei parametri passati
HttpServletRequest req; // definisce la richiesta client
/* Costruttore della classe di estrazione dei dati richiesti. */
public SqlDB(HttpServletRequest req, PrintWriter toClient, Enumeration values) {
this.req = req;
this.values = values;
this.toClient = toClient;
}
/* Overload del metodo run() se la classe estende Thread. */
public void run() {
// estrae i parametri di input da passare nella query SQL
ExtractInput();
// estrae parametri passati ed esegue la chiamata di accesso DB
PrepareQuery();
// sincronizza l'accesso alla risorsa "con"
synchronized (critsect) {
DrvMsAccess Sqlac = new DrvMsAccess("NomeFonteDati");
Sqlac.ExecStmt(query);
WriteHtml(Sqlac); // compone l'HTML da restituire al client
Sqlac.CloseStmt(); // reset dello statement SQL
}
}
/* Estrae i parametri passati dalla form o dalla richiesta. */
public void ExtractInput() {
while (values.hasMoreElements()) {
String name = (String) values.nextElement();
// String value = req.getParameterValues(name)[0];
if (name.compareTo("code") == 0) {
Code = req.getParameterValues(name)[0];
}
if (name.compareTo("snumber") == 0) {
SNumber = new Integer(req.getParameterValues(name)[0]);
}
}
}
/* Prepara la query usando i parametri passati dalla form. */
public void PrepareQuery() {
query = "SELECT DISTINTE.Pos, DISTINTE.codPezzo, PEZZI.descITA, " +
"PEZZI.UM, DISTINTE.Qta, DISTINTE.DA, DISTINTE.A " +
"FROM PEZZI INNER JOIN DISTINTE ON PEZZI.codPezzo = DISTINTE.codPezzo " +
"WHERE (DISTINTE.codTavola='" + Code.toUpperCase() + "'";
if (SNumber.intValue() != 0) {
query = query +
") AND (DISTINTE.DA<=" + SNumber.intValue() +
") AND (DISTINTE.A>=" + SNumber.intValue();
}
query = query + ")";
}
/* Scrive la pagina HTML da inviare al client. */
void WriteHtml(DrvMsAccess Sqlac) {
toClient.println("<html>");
toClient.println("<title>Dati MsAccess</title>");
toClient.println("<CENTER><TABLE BORDER=1>");
toClient.println("<TR><TD><H3>" +
Code.toUpperCase() + " S/N " + SNumber.intValue() +
"</H3></TD></TR>");
toClient.println("</TABLE><HR WIDTH=80%><BR>");
toClient.println("<TABLE BORDER=1>");
try {
int numberOfColumns = Sqlac.rsmd.getColumnCount();
while (Sqlac.rs.next()) {
toClient.print("<TR>");
for (int i = 1; i <= numberOfColumns; i++)
toClient.print("<TD><H5>" + Sqlac.rs.getString(i) + "</H5></TD>");
toClient.println("</TR>");
}
} catch (SQLException ex) {
System.err.print("SQLException: ");
System.err.println(ex.getMessage());
}
toClient.println("</TABLE></CENTER>");
toClient.println("</html>");
// chiude il writer: la risposta è completa
toClient.close();
}
}
Connessione JDBC-ODBC
Come ultima parte di codice vediamo la classe DrvMsAccess, che permette di reperire i dati da un database tramite connessione ODBC; il costruttore della classe richiede la "fonte dati ODBC" per aprire la connessione, poi chiamando il metodo CreateStmt("stringa QUERY SQL") verranno estratti i dati. Come si può notare nel codice precedente, una volta istanziata la classe bisogna richiamare il metodo CloseStmt() per chiudere lo statement SQL in modo da non lasciarlo appeso e ritrovarsi con una SQLException.
Molto importante è anche la gestione della concorrenza sulla risorsa con; per gestirla, nella classe SqlDB è stata inserita la sincronizzazione delle istruzioni relative all'accesso alla classe DrvMsAccess, in modo che solo un client alla volta possa accedervi. Questo si è reso necessario perché i thread condividono sia le risorse sia gli spazi di indirizzamento e quindi è molto facile fare confusione.
import java.io.*;
import java.util.*;
import java.sql.*;
public class DrvMsAccess {
// static perché è unica (globale) a fronte di più istanze della classe
static String fonte; // fonte dati ODBC in input
static Connection con; // driver di connessione JDBC
String query; // query SQL in input
Statement stmt; // statement SQL ODBC
ResultSet rs; // risultato della chiamata SQL in output
ResultSetMetaData rsmd; // caratteristiche dei dati in output
/* Costruttore di classe: apertura connessione. */
public DrvMsAccess(String fonte) {
try {
// se la connessione è chiusa, apre la connessione
if ((con == null) || (con.isClosed())) {
this.fonte = fonte;
String url = "jdbc:odbc:" + fonte;
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Con = DriverManager.getConnection(url);
}
} catch (SQLException ex) {
System.out.println("\n*** SQLException catturata ***\n");
while (ex != null) {
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("Message: " + ex.getMessage());
System.out.println("Code: " + ex.getErrorCode());
ex = ex.getNextException();
System.out.println("");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* Esegue la query SQL: richiede la query e la fonte dati. */
public void ExecStmt(String query) {
this.query = query;
try {
stmt = con.createStatement();
rs = stmt.executeQuery(query);
rsmd = rs.getMetaData();
} catch (SQLException ex) {
System.out.println("\n*** SQLException catturata ***\n");
while (ex != null) {
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("Message: " + ex.getMessage());
System.out.println("Code: " + ex.getErrorCode());
ex = ex.getNextException();
System.out.println("");
}
}
}
/* Chiusura statement SQL. */
public void CloseStmt() {
try {
rs.close();
stmt.close();
} catch (SQLException ex) {
System.out.println("\n*** SQLException catturata ***\n");
while (ex != null) {
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("Message: " + ex.getMessage());
System.out.println("Code: " + ex.getErrorCode());
ex = ex.getNextException();
System.out.println("");
}
}
}
/* Chiusura connessione JDBC-ODBC. */
public void CloseCon() {
try {
con.close();
} catch (SQLException ex) {
System.out.println("\n*** SQLException catturata ***\n");
while (ex != null) {
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("Message: " + ex.getMessage());
System.out.println("Code: " + ex.getErrorCode());
ex = ex.getNextException();
System.out.println("");
}
}
}
}
Bibliografia
- [1] *Java La Grande Guida*, Patrick Naughton, Herbert Schildt, ed. McGraw-Hill.
- [2] *Java Restaurant*, F. Tisato, L. Nigro, ed. Apogeo.
- [3] Articoli su
- [http://www.mokabyte.it/](http://www.mokabyte.it/):
- JDBC - La pratica di Giovanni Puliti, MokaByte 16, febbraio 1997;
- JDBC API di Giovanni Puliti, MokaByte 9, 10, 11, giugno-settembre 1997;
- Le Servlet API di Fabrizio Giudici, MokaByte 11, settembre 1997;
- Servlet & Database di Domenico Di Girolamo, MokaByte 16, febbraio 1997;
- Servlet, JDBC e JavaServer di Giovanni Puliti, MokaByte 20, giugno 1998.
- [4] *Tips, Techniques and Sample Code* di Sun, e-mail `JDCTechTips@sun.com` oppure
- [http://developer.javasoft.com/developer](http://developer.javasoft.com/developer).
