MokaByte Numero 16 - Febbraio 1998
Servlet & Database 
di 
Domenico 
Di Girolamo 
Un esempio pratico di questa tecnologia

 


Con l'articolo di Fabrizio Giudici di Settembre  sono stati introdotti i Servlet, spiegandone le principali caratteristiche. In questo articolo si vogliono rilevare ed approfondire alcune di queste caratteristiche, dimostrando come nella gestione di una problematica molto diffusa nel Web, quale quella dell'accesso ai Database dal client/Browser, si possono ottenere, oltre a tutti i vantaggi di semplicita' e portabilita' propri dei Servlet, anche fondamentali incrementi di prestazioni.
 

Introduzione
 

Ricordo che un Web server quando riceve una richiesta CGI, deve eseguire un programma completamente diverso e restituire il risultato del processo al Web browser.
Bisogna considerare che i CGI sono stati concepiti poco dopo la nascita del Web, in un periodo in cui i Web server ricevevano un numero d’accessi limitato e quindi la velocita' di risposta ad ogni richiesta dei client, non era una condizione cosi' rilevante.
Con l'aumento della "popolazione" Internet, i Web server devono soddisfare un numero sempre crescente di richieste e spesso devono servire svariati accessi contemporaneamente, facendo si', che la velocita' di risposta, diventa una condizione non trascurabile.
I Servlet, ci permettono di ottenere delle performance di esecuzione notevolmente migliori rispetto ai CGI, poiche', come ci ha detto Fabrizio Giudici l'attivazione di un Servlet non richiede la creazione di un nuovo processo per ogni richiesta, ma e’ eseguito in parallelo all'interno dello stesso processo del web-server.
Approfondiamo questa caratteristica importante dei Servlet, che li contraddistinguono dai CGI e cioe' quella di non richiedere l'attivazione di un nuovo processo.
Un Servlet, facendo parte integrante dello stesso processo del Web server, si puo' immaginare che sia una procedura dello stesso server.
Questo perche’ il Web server, effettua un caricamento dinamico della classe Servlet, durante lo start-up, oppure come conseguenza della prima richiesta al Servlet da parte di un client, dopodiche’ questi rimane residente in memoria ed a disposizione delle successive richieste.
Poiche’ il tempo di attivazione di un Servlet, in pratica e’ quello del caricamento dinamico di una qualunque classe e poiche’ una volta effettuato, non deve essere piu’ ripetuto, si puo’ capire perche’, il tempo di ‘chiamata’ di un Servlet, e' assimilabile a quello di una chiamata ad una procedura all'interno di un processo.
Per comprendere meglio la differenza con i CGI, si consideri che il tempo di attivazione ed inizializzazione di un CGI deve essere calcolato per ogni richiesta del client, mentre il tempo necessario al caricamento ed all'inizializzazione di un Servlet, di per se' minimo, essendo effettuato una sola volta, e’ ripartito per tutte le successive richieste dei client. Lascio immaginare a voi quali possono essere i guadagni, in termini di prestazioni che si ottengono.
Non e' tutto, perche’, essendo il metodo init() del Servlet chiamato solamente in fase di inizializzazione, tutte quelle operazioni che non necessitano di esecuzione ad ogni richiesta dei client, si puo’ inserirle proprio in questo metodo.
Quindi, l’incremento di prestazioni, confrontato con i CGI, non si limita ai soli tempi di attivazione.
La persistenza del Servlet dopo la sua inizializzazione ci permette, infatti, di gestire molte problematiche in modo piu’ efficiente.
Se si pensa, infatti, alla necessita' da parte dei clients di fare accesso ai Database, si puo' subito intuire che l'operazione di connessione al Database, che comporta un rilevante dispendio di tempo e che con i CGI e’ effettuata ad ogni richiesta dei clients, con i Servlet e' sufficiente inserirla nel metodo init(), facendo si' che non venga piu' ripetuta per tutte le successive richieste.
E’ evidente, che questa gestione, e’ piu' 'pulita' e ci permette di ottenere risposte alle richieste dei clients motlo veloci.
La deallocazione di un Servlet avviene in genere allo shut-down del Web server, in conseguenza del quale e’ chiamato il metodo destroy().
Inoltre la deallocazione del Servlet, avviene anche quando si elimina il suo Alias dalla tabella di configurazione dei "Servlet Alias" ed infine, quando si preme esplicitamente il button "Unload" dal menu' di gestione Servlet del Web server, funzionalita' che ci permette di modificare un Servlet e attivarlo senza fare lo shut-down del Web server.
Il Web server che ho utilizzato e' il Java Web Server 1.01 (SUN) e quindi la tabella di Servlet Alias ed il buttono "Unload" sono caratteristiche di questo Web Server. Per quanto riguarda altri Web server che implementano i Servlet, bisogna fare riferimento alle rispettive documentazioni.
Un'ultima considerazione, ci si chiedera' cosa avviene quando diverse richieste accedono allo stesso Servlet ?
Ogni richiesta ad un Servlet e’ gestita da un "handler Threads", quindi ci si puo' trovare nella situazione in cui piu' "handler Threads" fanno uso concorrente dello stesso Servlet.
In pratica, ogni richiesta del client attiva un Servlet Threads e molti metodi service() possono essere contemporaneamente attivi.
L'interfaccia Servlet, non assicura una protezione dagli accessi concorrenti e quindi e' opportuno, implementare il metodo service(), in modo da proteggere i dati da accessi concorrenti, utilizzando la parola chiave synchronized.
La soluzione piu' semplice e' definire il metodo service come synchronized, ma nel nostro caso, non ne abbiamo la necessita'.
Infatti, dovendo effettuare una connessione al Database, potremmo trovarci in una situazione di concorrenza se dovessimo definire l'oggetto Connection, come variabile di instanza della classe ed effettuare la connessione al Database, all'interno del metodo service().
Poiche' la connessione al Database e’ effettuata nel metodo init() ed essendo quest'ultimo chiamato una sola volta, non abbiamo problemi di concorrenza

 

 
Esempio.
Vediamo quindi un esempio di Servlet, che gestisce la connessione ad un Database tramite il driver JDBC e che genera dinamicamente una pagina HTML, utilizzando il risultato di una query al Database.
Questo esempio e’ fornito con il Java Web Server 1.01 (SUN).
Commentero' le parti piu' importanti ed indichero’ le modifiche da apportare per raggiungere gli obiettivi proposti in questo articolo.

/*

 * @(#)DBServlet.java   1.6 97/06/13

 *

 * Copyright (c) 1995-1997 Sun Microsystems, Inc. All Rights Reserved.

 *

 * This software is the confidential and proprietary information of Sun

 * Microsystems, Inc. ("Confidential Information").  You shall not

 * disclose such Confidential Information and shall use it only in

 * accordance with the terms of the license agreement you entered into

 * with Sun.

 *

 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE

 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE

 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR

 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES

 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING

 * THIS SOFTWARE OR ITS DERIVATIVES.

 *

 * CopyrightVersion 1.0

 */
 
 

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.net.*;

import java.sql.*;
 
 

/*

 * DBServlet

 *

 * This servlet demonstrates how to use JDBC stack

 * and access database using Jeeves servlet.  In order to run this

 * servlet, CLASSPATH, LD_LIBRARY_PATH, ODBC_INI, and other related

 * environment variables must be set prior to running the Jeeves

 * server since the name resolution for JDBC stack library happens

 * during the run time.  Part of the code was contributed by

 * Donald E. McCollam 

 *

 * @author   Jongyoon Lee

 * @version  1.6, 06/13/97

 */
 
 

// Evidenziero' le modifiche da effettuare con il carattere '$' e le

// le parti da sostituire con il carattere  '#'.

// Bisogna considerare che nella versione originale del servlet la connessione viene

// effettuata nel metodo service(), poiche' si ha la necessita' di ottenere i dati

// necessari alla connessione dal client e quindi

// inevitabilmente la connessione deve essere effettuata dopo aver acquisito

// i valori dei parametri.

// Volutamente non cancello la parte di codice originale, poiche'

// ci si puo' trovare nella situazione di dover acquisire i parametri per

// la connessione dal client.

// Quindi si potra' scegliere una soluzione o l'altra semplicemente eliminando

// le parti di codice contrassegnate dal carattere '#', oppure '$'.
 
 

public class DBServlet extends HttpServlet {
 
 

// Ecco il metodo init() dove, come ho spiegato nell'articolo,

// e' possibile effettuare l' operazione di connessione al Database.

// Per fare cio' e' necessario dichiarare l' oggetto Connection come

// variabile di istanza della classe.

// L' oggetto Connection, viene dichiarato qui', anziche' nel blocco del

// metodo service()

$   Connection con;
 
 
 
 

// Questo e' il metodo init() originale.
 
 

#   /*

#    * init method

#    */

#   public void init(ServletConfig servletConfig) throws ServletException {

#       super.init(servletConfig);

#   }
 
 

// Questo e' il metodo init() con la connessione al DB.

// Si noti che l'output degli errori viene inviato allo

// standard output, anziche' al client, poiche', a questo

// punto non si e' in possesso dei canali di I/O per inviare

// informazioni al client.

// I canalidi I/O vengono acquisiti nel metodo service().
 
 

$   /*

$    * init method

$    */
 
 

$   public void init(ServletConfig servletConfig) throws ServletException {

$       super.init(servletConfig);

$       try

$       {

$           Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

$           con = DriverManager.getConnection("jdbc:odbc:MIODB", "MIAUSER", "MIAPASSWORD");

$       }

$       catch (SQLException ex) {

$           System.out.println ("\n*** SQLException caught ***\n");

$           while (ex != null) {

$               System.out.println ("SQLState: "" + ex.getSQLState ());

$               System.out.println ("Message:  "" + ex.getMessage ());

$               System.out.println ("Vendor:   "" + ex.getErrorCode ());

$               ex = ex.getNextException ();

$               System.out.println ("");

$           }

$       }

$       catch (java.lang.Exception jle) {

$           jle.printStackTrace();

$       }

$   }
 
 

// E' necessario, implementare il metodo destroy(), per poter

// effettuare la chiusura della connessione col Database.

// Bisogna considerare che anche quando viene chiamato il metodo destroy(),

// non siamo in possesso dei canali di I/O con il client, poiche'

// il metodo, non viene chiamato in conseguenza di una richiesta del client,

// ma nei casi che ho evidenziato nell' articolo.

// Quindi le eventuali informazioni di errore non devono essere inviate a clients.
 
 

$   public void destroy()

$   {

$       try

$       {

$           con.close();

$       }

$       catch (SQLException ex) {

$           System.out.println ("\n*** SQLException caught ***\n");

$           while (ex != null) {

$               System.out.println ("SQLState: " + ex.getSQLState ());

$               System.out.println ("Message:  " + ex.getMessage ());

$               System.out.println ("Vendor:   " + ex.getErrorCode ());

$               ex = ex.getNextException ();

$               System.out.println ("");

$           }

$       }

$   }
 
 

    /*

     * service method

     */

    public void service(HttpServletRequest req, HttpServletResponse res)

        throws ServletException, IOException

    {
 
 

   String stack, username, password, query;
 
 

// L' oggetto Connection, e' stato spostato, ed e' stato dichiarato come

// variabile di istanza della classe DBServlet.
 
 

#   Connection con;
 
 

    PrintStream out;
 
 

    // fetch the parameters

    stack = req.getParameter("stack");

    username = req.getParameter("username");

    password = req.getParameter("password");

    query = req.getParameter("query");
 
 

    res.setContentType("text/html");

    out = new PrintStream(res.getOutputStream());

    printPageHeader(out);
 
 

    // if no parameters, just print the form

    if (stack == null || username == null || password == null ||

        query == null) {

        printPageFooter(out);

        return;

    }
 
 

    String url   = "jdbc:odbc:"+stack;
 
 

    out.println("<hr><h3>Previous Query</h3>"");

    out.println("<pre>");

    out.println("Database Stack : "+stack);

    out.println("       User ID : "+username);

    out.println("      Password : "+password);

    out.println("         Query : "+query);

    out.println("</pre>");

    try {

// find the jdbc stack

// Il caricamento del bridge JDBC-ODBC e la connessione

// sono stati spostati nel metodo init()

#         Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

#

        //Class.forName("jdbc.odbc.JdbcOdbcDriver");

#

#

// get the connection to the database

# con = DriverManager.getConnection(url, username, password);

        out.println("<hr>");

        out.println("<h3>ODBC Driver and Database Messages</h3>");

        checkForWarning(con.getWarnings(), out);

        DatabaseMetaData dma = con.getMetaData();

        out.println("Connected to " + dma.getURL() + "<br>");

        out.println("Driver " + dma.getDriverName() + "<br>");

        out.println("Version " + dma.getDriverVersion() + "<br>");

        // create and execute the query

        Statement stmt = con.createStatement();

        ResultSet rs = stmt.executeQuery(query);

        // print out the result

        dispResultSet(rs, out);

        rs.close(); stmt.close();

    // La chiusura della connessione viene effettuata nel metodo destroy()

# con.close();

        out.println("<hr>");

    } catch (SQLException ex) { out.println("<hr>*** SQLException caught ***<p>");

        while (ex != null) { out.println("SQLState: " + ex.getSQLState() + "<br>");

            out.println("Message: " + ex.getMessage() + "<br>");

            out.println("Vendor: " + ex.getErrorCode() + "<br>");

            ex = ex.getNextException();

        }

    } catch (java.lang.Exception ex) { ex.printStackTrace(); }
 
 

    printPageFooter(out); }

    /*

    * returns the servlet info

    */

    public String getServletInfo() {

        return "This is a simple servlet that shows how to use JDBC";

    }

    /*

    * check if the database server has anything to say

    */

    private void checkForWarning(SQLWarning warn, PrintStream out) throws SQLException {

        boolean rc = false;

        if (warn != null) {

            out.println("<hr>*** Warning ***<p>");

            rc = true;

            while (warn != null) {

                out.println("SQLState: " + warn.getSQLState() + "<br>");

                out.println("Message: " + warn.getMessage() + "<br>");

                out.println("Vendor: " + warn.getErrorCode() + "<br>");

                warn = warn.getNextWarning();

            }

        }

    }

    /*

    * display the result in html table format

    */

    private void dispResultSet(ResultSet rs, PrintStream out)

       throws SQLException

    {

       int i;
 
 

       // metadata can supply information about the schema

       ResultSetMetaData rsmd = rs.getMetaData();

       int numCols = rsmd.getColumnCount();

       out.println("<hr>");

       out.println("<h3>Database Columns and Data</h3>");

       out.println("<table border=3>");

       out.println("<tr>");

       for (i=1; i<=numCols; i++) {

           out.println("<th>" + rsmd.getColumnLabel(i) + "</th>");

       }

       out.println("</tr>");

       // for entire data

       while (rs.next()) {

           out.println("<tr>");

            // for one row

           for (i=1; i<=numCols; i++) {

               dispElement(rs, rsmd.getColumnType(i), out, i);

           }

           out.println("<tr>");

       }

       out.println("</table>");

    }

    // print one element

    private void dispElement(ResultSet rs, int dataType, PrintStream out, int col) throws SQLException {

        // ask for data depending on the datatype

        switch(dataType) {

            case Types.DATE:

                java.sql.Date date = rs.getDate(col);

                out.println("<th>" + date.toString() + "</th>");

                break;

            case Types.TIME:

                    java.sql.Time time = rs.getTime(col);

                    out.println("<th>" + time.toString() + "</th>");

                    break;

            case Types.TIMESTAMP:

                    java.sql.Timestamp timestamp = rs.getTimestamp(col);

                    out.println("<th>" + timestamp.toString() + "</th>");

                    break;

            case Types.CHAR:

            case Types.VARCHAR:

            case Types.LONGVARCHAR:

                    String str = rs.getString(col);

                    out.println("<th>" + str + "</th>");

                    break;

            case Types.NUMERIC:

            case Types.DECIMAL:

                    java.math.BigDecimal numeric = rs.getBigDecimal(col, 10);

                    out.println("<th>" + numeric.toString() + "</th>");

                    break;

            case Types.BIT:

                    boolean bit = rs.getBoolean(col);

                    out.println("<th>" + new Boolean(bit) + "</th>");

                    break;

            case Types.TINYINT:

                    byte tinyint = rs.getByte(col);

                    out.println("<th>" + new Integer(tinyint) + "</th>");

                    break;

            case Types.SMALLINT:

                    short smallint = rs.getShort(col);

                    out.println("<th>" + new Integer(smallint) + "</th>");

                    break;

            case Types.INTEGER:

                    int integer = rs.getInt(col);

                    out.println("<th>" + new Integer(integer) + "</th>");

                    break;

            case Types.BIGINT:

                    long bigint = rs.getLong(col);

                    out.println("<th>" + new Long(bigint) + "</th>");

                    break;

            case Types.REAL:

                    float real = rs.getFloat(col);

                    out.println("<th>" + new Float(real) + "</th>");

                    break;

            case Types.FLOAT:

            case Types.DOUBLE:

                    double longreal = rs.getDouble(col);

                    out.println("<th>" + new Double(longreal) + "</th>");

                    break;

            case Types.BINARY:

            case Types.VARBINARY:

            case Types.LONGVARBINARY:

                    byte[] binary = rs.getBytes(col);

                    out.println("<th>" + new String(binary, 0) + "</th>");

                    break;

        }

    }

    private void printPageHeader(PrintStream out) {

        out.println("<html>");

        out.println("<head>");

        out.println("<tltle>Jeeves Servlet Test</title>");

        out.println("</head>");

        out.println("<body>");

        out.println("<center><font size=5>" + "<b>Jeeves Database Servlet</b>" + "</font></center>");

        out.println("<hr>");

        out.println("<form action=\"/servlet/dbServlet\" method=\"get\">");

        out.println("<pre>");

        out.println("Database Stack : <INPUT type=textarea name=stack>");

        out.println("       User ID : <INPUT type=textarea name=username>");

        out.println("      Password : <INPUT type=textarea name=password>");

        out.println("         Query : <INPUT type=textarea name=query>");

        out.println("/PRE");

        out.println("<INPUT type=submit>");

        out.println("</FORM>");

    }

    private void printPageFooter(PrintStream out) {

        out.println("</body>");

        out.println("</html>");

        out.flush();

    }

}

Bibliografia.

"Developing Java Servlets" (William Crawford)
Java Web Server 1.01
Documentation Software: Java Web Server 1.x/2.x (SUN) http://java.sun.com/products/java-server/webserver/index.html
WebSite 2.0 (O'Reilly & Associates, Inc.) http://software.ora.com/
Java Servlet Development Kit (SUN) http://java.sun.com/products/java-server/webserver/index.html
ServletExpress(IBM) http://www.ibm.com/java/servexp/
JRUN (LiveSoftware) http://www.livesoftware.com
 



 
Domenico di Girolamo - Freelance Software Developer - mdg@iol.it 

“Il mio interesse per i computers e’ inizato, quando, ancora fanciullo, dopo aver giocato una partita a “frogger” nella sala giochi di turno incappai nella vetrina di un negozio di elettronica, dove rimasi affascinato e incredulo nel vedere che lo stesso gioco che mi aveva costretto poco tempo prima ad inserire preziose monetine in un buco senza ritorno, era ‘proiettato’ sullo schermo di un normale televisore, da uno scatolotto assomigliante ad una macchina da scrivere. 
Il mio stupore aumento’ quando entrando nel negozio mi accorsi che il ranocchio non solo era ‘proiettato’ sul televisore, ma poteva anche essere manovrato esattamente come facevo poco tempo prima con l’armadio della sala giochi. 
Lo stupore divento’ euforia quando dopo aver giocato una partita con lo scatolotto, mi accorsi che la temuta scritta GAME OVER era diventata del tutto innocua, poiche’ mi basto’ premere un tasto e la partita ricomincio’ senza dover inserire una nuova preziosa monetina. 
Lo scatolotto aveva una scritta allora a me sconosciuta ed ora probabilmente sconosciuta a qualcuno , COMMODORE 64. 
Nel giro di qualche giorno, lo scatolotto si trasferi’ dal negozio alla mia scrivania. 
Ho maturato diversi anni di attivita’ in vari settori, principalmente in quello dell’automazione industriale ed ho utilizzato come linguaggio di sviluppo soprattutto il “C”. 
Attualmente il mio interesse si e’ diretto verso la tecnologia JAVA e sono socio JIA (Java Italian Association) nonche’ moderatore della Mailing List jia@infomedia.it.” 


 
 


MokaByte Web  1998 - www.mokabyte.it

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