MokaByte Numero 28  -  marzo  1999
Servlet, JDBC, 
Ms Access e Ms IIS
di 
Martino Pezzoli
Un esempio completo di accesso a basi dati

Introduzione
 

Le versioni dei prodotti SUN utilizzate per lo sviluppo sono JDK 1.1.6 , JDBC 1.2 , JDSK 1.2 , tutte scaricabili gratuitamente dal sito http://www.java.sun.com/products/
L’ esempio proposto estrae da un database i dati relativi ad 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 ed attributi, della classe window. L' oggetto forms[ ] è una matrice che identifica tutte le windows (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. Assegnamo 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 Enumaration simile ad una hash-table.
La hash-table è una struttura dati che permette, associando ad 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 ad ottenere un semplice programma che controlla ed esegue la servlet proprio come se fosse un programma tradizionale. La servlet viene lanciata sul computer locale alla porta 8000 che è seguita dalla keyword servlet e dal nome del servlet da eseguire.

<html>
 
 

  <script language="JavaScript">
 
 

// controlla la corretezza 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>

Bisogna innanzi tutto inserire la voce relativa alla "fontedati" ODBC andando in pannello di controllo, ODBC (Open DataBase Connectivity).
L' ODBC è uno standard divulgato da Microsoft, 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 (vedi) derivata dall'interfaccia CLI (Call Level Interface) dell'SQL Access Group. Definire una fontedati 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. 

 

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 (che vedremo) 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 gli web server hanno. Purtroppo l' IIS di Microsoft non l' ha, in questo caso si può utilizzare un prodotto di terze parti, come quello che io ho utilizzato JRun della LiveSoftware http://www.livesoftware.com , del quale è possibile scaricare una versione trial. Esiste anche un tool freeware compreso in JSDK di SUN è il ServletRunner.exe posto nella cartella "bin". Questo simula un web server per poter provare e fare il debug dei propri servlets 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 le Servlets basterà sostituire la parte relativa ad esse con il metodo main(). Piuttosto di generare la pagina HTML fare un system.out.println(….) su consolle e lanciare il bytecode generato con il tool compreso in 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 ed applicabile utilizzando classi sviluppate non appositamente per le servlet ; ancora l' implementazione di un servlet utilizzando classi già esistenti e funzionanti (cioè testate) può risultare abbastanza banale. 
 

Qui sotto è inserito la parte di codice relativo alla gestione Servlet. Al metodo SqlDB, che eseguirà il passo successivo della richiesta, gli 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 DB nel metodo init() e chiusa nel metodo destroy(); inoltre viene gestita la concorrenza sull' accesso al DB 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;   // istanzia fittizia per aprire il DBase SQL
 
 

        /*

         * Inizializza il Servlet Overriding del metodo init(), 

                   * viene chiamato solo all' apertura della 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

           * Overriding del metodo doPost() 

          */

           public void doPost(HttpServletRequest req, HttpServletResponse res) 

                throws ServletException, IOException {

                    // negozia il MIME per pagine HTML "content type" 

                    //header of the response

                    res.setContentType("text/html"); 

                    // recupera il "nome" come 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();
 
 

        }  // end doPost()
 
 
 
 

        /*

         * Funzione rchiesta dalla FORM html

         * Overriding del metodo doGet()

         */

         public void doGet(HttpServletRequest req, HttpServletResponse res) 

                throws ServletException, IOException {

                // negozia il MIME per pagine HTML "content type" 

                //header of the response

                res.setContentType("text/html"); 
 
 

                // recupera il "nome" del 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();
 
 

        }  // end doGet()
 
 
 
 

        /* 

         * Overriding -distrugge il servlet 

         */

        public void destroy() {

                Sqlac.CloseCon();   // chiusura connessione DB SQL

        }  // end destroy()

}   // end Class SqlServlet

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 PrinterWriter i dati, così preparati, verranno spediti al richiedente senza che noi dobbiamo preoccuparcene.

 *  classe per SERVIZIO accesso al DBase 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();  // oggetto generico
 
 
 
 

        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 classe estrazione dati richiesti

        */

        public SqlDB(HttpServletRequest req, PrintWriter toClient, Enumeration values) {
 
 

                this.req=req; 

                this.values=values;

                this.toClient=toClient; 

        }  // end SqlDB()
 
 
 
 

        /* 

         * Overloading del metodo run() se la classe extend 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(); 

              // Sincronizzazione dell' accesso alla risorsa "con" 

              // per evitare risultati e/o errori indesiderati

              synchronized (critsect) {

              // istanzia la classe driver per ODBC MsAccess

              DrvMsAccess Sqlac = new DrvMsAccess("NomeFonteDati");

              Sqlac.ExecStmt(query);

              WriteHtml(Sqlac);   // compone l' HTML da restituire al client richiedente

              Sqlac.CloseStmt();  // reset statement query SQL

           }   // fine sincronizzazione 
 
 

        }  // end run()
 
 
 
 
 
 

        /* 

         * Estrazione da outstream parametri 

         * passati dalla form (doPost) o dalla richiesta (doGet)

         */

         public void ExtractInput() {
 
 

               // estrae i parametri passati dalla form dall' outstream

               while(values.hasMoreElements()) {

                  String name = (String)values.nextElement(); 

                  //String value = req.getParameterValues(name)[0];

                   if (name.compareTo("code") == 0) { // codice distinta

                      Code = req.getParameterValues(name)[0]; 

                   }

                   if (name.compareTo("snumber") == 0) {  // matricola

                      SNumber = new Integer(req.getParameterValues(name)[0]);

                   }

               }

        } // end ExtractInput()
 
 
 
 
 
 

        /* 

         * Estrazione da outstream parametri passati dalla form (doPost) o 

         * dalla richiesta (doGet)

         */

          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 + ")";
 
 

        }  // end ExtractData()
 
 
 
 
 
 

        /*

        * Scrittura pagina HTML da inviare al Client

        */

        void WriteHtml(DrvMsAccess Sqlac) {
 
 

          // Respond to client with a thank you

          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>");
 
 

            // Close the writer; the response is done.

            toClient.close();
 
 

        }  // end WriteHtml()
 
 
 
 

}  // end Class SqlDB

Come ultima parte di codice vediamo la classe DrvMsAccess che permette di reperire i dati da un data base tramite connessione ODBC; il costruttore della classe richiede la "fonte dati ODBC" per aprire la connessione, poi chiamando il metodo CreateStmt("stringa QUERY SQL" ) verrano 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 un SQL Exception.
Molto importante è anche la gestione della concorrenza sulla risorsa "con" ; per gestirla nella classe SqlDB è stato 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 Threads condividono sia le risorse che 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;    // fontedati ODBC in input

   static Connection con;   // driver di connessione JDBC
 
 

   String query;      // query SQL in input

   Statement stmt;    // prepare statement SQL ODBC

   ResultSet rs;                  // risultato della chiamata SQL in output (Dati)

   ResultSetMetaData rsmd;    // caratteristiche dei dati in output
 
 

    /*
 
 

    *  Costruttore di classe appertura connessione 

    */

    public DrvMsAccess(String fonte) {
 
 

        try {

              // se la connection è close open connessione

              if ((con == null) || (con.isClosed())) { 

                 // fonte dati ODBC

                 this.fonte=fonte; 

                 // definisco tipo e fonte dati

                 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 ("");

             } 

         }    // errori SQL
 
 

         catch (Exception e) {

                e.printStackTrace();

         }   // errori generici
 
 

     }  // end DrvMsAccess()
 
 
 
 

        /*

        *  Esegue la query SQL  - è richiesto la stringa della query e della fonte dati 

        */

        public void ExecStmt(String query) {

               this.query=query;
 
 

               try {

                stmt = con.createStatement();   // prepare dello statement

                rs = stmt.executeQuery(query);  // esegue statment e restituisce i dati

                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 ("");

                  }

                }
 
 

        }   // end ExecStmt()
 
 
 
 

        /*

        *  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 ("");

                } 

             } 

          }    // end CloseStmt() 
 
 
 
 

        /*
 
 

        *  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 ("");

                 }

             }

        }   // end CloseCon()
 
 

} // end Class

Bibliografia
 

[1] "Java La Grande Guida", Patrick Naughton, Herbert Schildt, ed. McGrawHill . 
[2] "Java Restaurant", F.Tisato, L.Nigro, ed. Apogeo. 
[3] Articoli su http://www.mokabyte.it :

"JDBC - La pratica" di Giovanni Puliti _ Mokabyte 16, Feb97. 
"JDBC API" di Giovanni Puliti _ Mokabyte 9,10,11, Giu-Set 97 
"Le Servlet Api" di Fabrizio Giudici _ Mokabyte 11, Set97 
"Servlet & Database" di Domenico Di Girolamo _ Mokabyte 16, Feb97. 
"Servlet, JDBC e JavaServer" di Giovanni Puliti _ Mokabyte 20, Giu98. 
[4] TIPS, TECHNIQUES AND SAMPLE CODE di SUN 
e-mail JDCTechTips@sun.com oppure http://developer.javasoft.com/developer

 

MokaByte Web  1999 - www.mokabyte.it

MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it