ePrometeusCorsoJavaJava
testi articoli
Testi Articoli  Download
Home | Eshop | Java | Tools | Web | 
CorsoJava è ora Video! Free for all!
Clicca Qui!

FRAMEWORK SERVLET JAVA
Un framework per applicazioni Web in Java
Usare le Servlet
Interfaccia delle HttpServlet
Un compilatore di HTML
Semplificare le query
L'Agenda Web
Conclusioni
Bibliografia e Listati
L'autore

<<< Semplificare le query >>>

L'ultimo tassello del mosaico del nostro Framework è quello di rendere semplice l'accesso al database. La API messa a disposizione da Java è il JDBC, che richiede di creare una connessione ad database prima di poter eseguire degli stament. Il posto logico più logico dove creare la connessione è nella init della nostra Servlet. Successivamente si possono eseguire statement SQL (stat.executeQuery("select * from Utenti"). Questo metodo però ha due svantaggi: il primo è che quasi tutti i database, prima di eseguire uno statement lo compilano in un formato interno e poi lo eseguono, quindi su ha un rallentamento per ogni query, che si potrebbe evitare preparando prima tutti gli statement. Il secondo svantaggio è che devo fornire degli statement sintatticamente corretti, per cui devo preoccuparmi di dettagli come il quoting di una stringa o la formattazione di una data.

Il JDBC supporta la nozione di PreparedStatemet, ovvero di statement precompilato che può essere eseguito più volte cambiando i parametri. Per esempio per inserire una stringa in una tabella posso usare un PreparedStatement pstat = con.prepareStatement("insert into mytable values(?)"), creato una volta e per tutte all'inizializzazione della servlet, che può essere rieseguito quante volte si vuole cambiando i parametri: pstat.setString(1, val); pstat.executeQuery();. Notare che in quest'ultimo caso, poiché specifichiamo che il tipo del parametro è una stringa, il quoting diventa un problema del database server e non più nostro. Vediamo dunque che il modello delle nostre servlet comincia a prendere forma:


class MiaServelet extends javax.servlet.http.HttpServlet {

// Statement SQL

private static final String INSERT = "insert into MyTable values (?)";

private String driver, dburl, username, password;

public void init(ServletConfig cfg) {

// leggi dai parametri i parametri della connessione

...

initConnection();

}

// inizializziamo la connessione

private Connection con;

private PreparedStatement insert;

private void initConnection() {

Class.forName(driver);

con = DriverManager.createConnection(con);

insert = con.prepareStatement(INSERT);

}


// serviamo le richieste

public void service(HttpServletRequest req, HttpServletResponse res) {

...

}

}


Notare come abbiamo isolato gli statement SQL in costanti modo da isolarli nel codice, anche se forse sarebbe opportuno porli addirittura in un file di risorse. Per quanto i database siano tra di loro teoricamente compatibili, all'atto pratico qualche problema che costringe a mettere mano all'SQL si incontra, anche se devo dire dopo un po' si impara a scrivere statement SQL che funzionano indifferentemente con vari database come Oracle, Interbase e SQLServer (sempre tenendosi molto bassi e facendo finta di essere molto ingnoranti di SQL…). I problemi maggiori si presentano nella creazione delle tabelle e nei tipi di dato, che tendono ad essere diversi da database a database.

Un altro accorgimento importante è l'isolamento delle operazioni di inizializzazione del database in un metodo initConnection per rendere il codice più robusto. Infatti database e web server sono due programmi diversi e separati: in pratica succede, e anche abbastanza spesso, che il database cada o divenga indisponibile per qualche ragione, bloccando irrimediabilmente la servlet, finché non viene reinizializzata. Per cui è opportuno scrivere il codice in modo da resettare la connessione quanto più spesso possibile. Io solitamente resetto la connessione al database ogni volta scatta una eccezione imprevista. In pratica nove volte su dieci si tratta di qualche eccezione relativa a problemi di connessione con il database. Questo piccolo accorgimento è la differenza tra una servlet che può girare per mesi senza problemi e una servlet che si blocca ogni due giorni e va monitorata costantemente…

Quando si effettua un inserimento, i PreparedStatement hanno uno svantaggio: si devono trasferire a mano tutti i dati dalla form allo statement. Quindi un metodo doInsert che salva i dati specificati un una form sara qualcosa come:


void doInsert(HttpData data) {

insert.setString("nome", data.get("nome"));

insert.setInt("eta", data.get("eta"));

insert.setString("email", data.get("email"));

...

insert.executeUpdate();

}


Questa incombenza è sufficientemente frequente da indurmi a cercare un modo di evitare di copiare i dati da una form ad un PreparedStatement: per questa ragione ho inventato il MarkedStatement del listato 2.


package agenda;

import java.sql.*;
import java.util.*;

public class MarkedStatement {

  private Hashtable keys = new Hashtable();

  private String stat;
  private PreparedStatement prep;
  public PreparedStatement getStat() { return prep; }

  public MarkedStatement(Connection con, String stat)
    throws SQLException
  {
    this.stat = stat;
    int max = stat.length();
    boolean inQuote = false;
    StringBuffer res = new StringBuffer();
    String tmp; char c; int j; int n =0;
    for(int i=0; i<max; ++i)
      switch(c=stat.charAt(i)) {
        case '\'':
          inQuote = !inQuote;
        break;
        case '$':
        case '%':
        case '!':
          if(inQuote)
            res.append(c);
          else {
            for(j=i+1; j<max && Character.isLetterOrDigit(stat.charAt(j)); j++);
            if(j>i+1) {
              ++n;
              res.append('?');
              tmp =stat.substring(i,j).toUpperCase();
              keys.put(tmp.intern(), new Integer(n));
              i=j-1;
            }
          }
        break;
        default:
          res.append(c);
        break;
      }
      System.err.println("preparing statement :\n"+res);
      try {
        prep = con.prepareStatement(res.toString());
      } catch(Exception ex) {
        ex.printStackTrace();
      }
  }

  public void copyFrom(HttpData data)
    throws SQLException
  {
    int i;
    String k, v;
    int nfields = keys.size();
    String[] fields = new String[nfields];

    Enumeration e = keys.keys();
    while(e.hasMoreElements()) {
      k = (String)e.nextElement();
      i = ((Integer)keys.get(k)).intValue();
      fields[i-1]=k;
    }

    for(i=0; i<nfields; ++i) {
      k = fields[i];
      switch(k.charAt(0)) {
        case '$':
          prep.setString(i+1, v = data.getString(k.substring(1)));
          System.err.println("<br>copying string "+k+"="+v+" in "+(i+1));
        break;
        case '%':
          int vi;
          prep.setInt(i+1, vi = data.getInt( k.substring(1)));
          System.err.println("<br>copying int "+k+"="+vi+" in "+(i+1));
        break;
        case '!':
          double vd;
          prep.setDouble(i+1, vd=data.getDouble(k.substring(1)));
          System.err.println("<br>copying double "+k+"="+vd+" in "+(i+1));
        break;
        default:
          System.err.println("unknown type field "+k);
        break;
      }
    }
  }

  public ResultSet executeQuery() throws SQLException {
    return prep.executeQuery();
  }

  public int executeUpdate() throws SQLException {
    return prep.executeUpdate();
  }

  private String quote(String s) {
    StringBuffer res = new StringBuffer("'");
    char c;
    for(int i=0; i<s.length(); ++i) {
      c=s.charAt(i);
      if(c=='\'')
        res.append("''");
      else
        res.append(c);
    }
    res.append('\'');
    return res.toString();
  }
}


Si tratta semplicemente di un PreparedStatement che ricorda il tipo dei suoi campi ed ha la capacità di trasferire automaticamente i campi di una HttpData in campi omonimi del PreparedStatement. Nell'esempio di prima, si costruisce all'inizializzazione un MarkedStatemente insert = new MarkedStatement("insert into Utenti values($nome, %eta, $email"). Il prefisso $ indica campi stringa, il prefisso % quelli interi e il prefisso ! i campi di tipo float. Adesso è possibile trasferire i valori dalla form automaticamente con un insert.copyFrom(data) evitando di specificarli ad uno ad uno. Il problema inverso, ovvero di trasferire i dati dal risultato di una query a una form HTML è stato già risolto tramite l'uso di variabili nei template HTML ottenuti con il JSPCompiler.

ePrometeus s.r.l. - Web Software House & Open Source System Integrator
MILANO - SAN BENEDETTO DEL TRONTO(AP)
Contatti: info@eprometeus.com