IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 15 - Informatique distribuée

pages : 1 2 3 4 5 6 7 8 9 10 11 12 

Dans cet exemple, il est intéressant de laisser les exceptions s'afficher sur la console :

//: c15:jdbc:CIDCreateTables.java
// Crée les tables d'une base de données pour la
// «community interests database».
import java.sql.*;

public class CIDCreateTables {
  public static void main(String[ « args)
  throws SQLException, ClassNotFoundException,
  IllegalAccessException {
    // Charger le driver (qui s'enregistrera lui-même)
    Class.forName(CIDConnect.dbDriver);
    Connection c = DriverManager.getConnection(
      CIDConnect.dbURL, CIDConnect.user,
      CIDConnect.password);
    Statement s = c.createStatement();
    for(int i = 0; i < CIDSQL.sql.length; i++) {
      System.out.println(CIDSQL.sql[i]);
      try {
        s.executeUpdate(CIDSQL.sql[i]);
      } catch(SQLException sqlEx) {
        System.err.println(
          "Probably a 'drop table' failed");
      }
    }
    s.close();
    c.close();
  }
} ///:~

Remarquons que les modifications de la base peuvent être contrôlées en changeant Strings dans la table CIDSQL, sans modifier CIDCreateTables.

La méthode executeUpdate( ) renvoie généralement le nombre d'enregistrements affectés par l'instruction SQL. Elle est très souvent utilisée pour exécuter des instructions INSERT, UPDATE, ou DELETEmodifiant une ou plusieurs lignes. Pour les instructions telles que CREATE TABLE, DROP TABLE, et CREATE INDEX, executeUpdate( )renvoie toujours zéro.

Pour tester la base, celle-ci est chargée avec quelques données exemples. Ceci est réalisé au moyen d'une série d'INSERTface="Georgia">suivie d'un SELECTafin de produire le jeu de données. Pour effectuer facilement des additions et des modifications aux données de test, ce dernier est construit comme un tableau d'Objectà deux dimensions, et la méthode executeInsert( ) peut alors utiliser l'information d'une ligne de la table pour construire la commande SQL appropriée.

//: c15:jdbc:LoadDB.java
// Charge et teste la base de données.
import java.sql.*;

class TestSet {
  Object[][ « data = {
    { "MEMBERS", new Integer(1),
      "dbartlett", "Bartlett", "David",
      "123 Mockingbird Lane",
      "Gettysburg", "PA", "19312",
      "123.456.7890",  "bart@you.net" },
    { "MEMBERS", new Integer(2),
      "beckel", "Eckel", "Bruce",
      "123 Over Rainbow Lane",
      "Crested Butte", "CO", "81224",
      "123.456.7890", "beckel@you.net" },
    { "MEMBERS", new Integer(3),
      "rcastaneda", "Castaneda", "Robert",
      "123 Downunder Lane",
      "Sydney", "NSW", "12345",
      "123.456.7890", "rcastaneda@you.net" },
    { "LOCATIONS", new Integer(1),
      "Center for Arts",
      "Betty Wright", "123 Elk Ave.",
      "Crested Butte", "CO", "81224",
      "123.456.7890",
      "Go this way then that." },
    { "LOCATIONS", new Integer(2),
      "Witts End Conference Center",
      "John Wittig", "123 Music Drive",
      "Zoneville", "PA", "19123",
      "123.456.7890",
      "Go that way then this." },
    { "EVENTS", new Integer(1),
      "Project Management Myths",
      "Software Development",
      new Integer(1), new Float(2.50),
      "2000-07-17 19:30:00" },
    { "EVENTS", new Integer(2),
      "Life of the Crested Dog",
      "Archeology",
      new Integer(2), new Float(0.00),
      "2000-07-19 19:00:00" },
    // Met en relation personnes et événements
    {  "EVTMEMS",
      new Integer(1),  // Dave est mis en relation avec
      new Integer(1),  // l'événement Software.
      new Integer(0) },
    { "EVTMEMS",
      new Integer(2),  // Bruce est mis en relation avec
      new Integer(2),  // l'événement Archeology.
      new Integer(0) },
    { "EVTMEMS",
      new Integer(3),  // Robert est mis en relation avec
      new Integer(1),  // l'événement Software...
      new Integer(1) },
    { "EVTMEMS",
      new Integer(3), // ... et
      new Integer(2), // l'événement Archeology.
      new Integer(1) },
  };
  // Utiliser les données par défaut:
  public TestSet() {}
  // Utiliser un autre ensemble de données:
  public TestSet(Object[][ « dat) { data = dat; }
}

public class LoadDB {
  Statement statement;
  Connection connection;
  TestSet tset;
  public LoadDB(TestSet t) throws SQLException {
    tset = t;
    try {
      // Charger le driver (qui s'enregistrera lui-même)
      Class.forName(CIDConnect.dbDriver);
    } catch(java.lang.ClassNotFoundException e) {
      e.printStackTrace(System.err);
    }
    connection = DriverManager.getConnection(
      CIDConnect.dbURL, CIDConnect.user,
      CIDConnect.password);
    statement = connection.createStatement();
  }
  public void cleanup() throws SQLException {
    statement.close();
    connection.close();
  }
  public void executeInsert(Object[ « data) {
    String sql = "insert into "
      + data[0 « + " values(";
    for(int i = 1; i < data.length; i++) {
      if(data[i « instanceof String)
        sql += "'" + data[i « + "'";
      else
        sql += data[i];
      if(i < data.length - 1)
        sql += ", ";
    }
    sql += ')';
    System.out.println(sql);
    try {
      statement.executeUpdate(sql);
    } catch(SQLException sqlEx) {
      System.err.println("Insert failed.");
      while (sqlEx != null) {
        System.err.println(sqlEx.toString());
        sqlEx = sqlEx.getNextException();
      }
    }
  }
  public void load() {
    for(int i = 0; i< tset.data.length; i++)
      executeInsert(tset.data[i]);
  }
  // Lever l'exception en l'envoyant vers la console:
  public static void main(String[ « args)
  throws SQLException {
    LoadDB db = new LoadDB(new TestSet());
    db.load();
    try {
      // Obtenir un ResultSet de la base chargée:
      ResultSet rs = db.statement.executeQuery(
        "select " +
        "e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME "+
        "from EVENTS e, MEMBERS m, EVTMEMS em " +
        "where em.EVT_ID = 2 " +
        "and e.EVT_ID = em.EVT_ID " +
        "and m.MEM_ID = em.MEM_ID");
      while (rs.next())
        System.out.println(
          rs.getString(1) + "  " +
          rs.getString(2) + ", " +
          rs.getString(3));
    } finally {
      db.cleanup();
    }
  }
} ///:~

La classe TestSet contient un ensemble de données par défaut qui est mis en oeuvre lorsqu'on appelle le constructeur par défaut ; toutefois, il est possible de créer au moyen du deuxième constructeur un objet TestSet utilisant un deuxième ensemble de données. L'ensemble de données est contenu dans un tableau à deux dimensions de type Object car il peut contenir n'importe quel type, y compris String ou des types numériques. La méthode executeInsert( ) utilise RTTI pour différencier les données String (qui doivent être entre guillemets) et les données non-String en construisant la commande SQL à partir des données. Après avoir affiché cette commande sur la console, executeUpdate( ) l'envoie à la base de données.

Le constructeur de LoadDB établit la connexion, et load( )parcourt les données en appelant executeInsert( )pour chaque enregistrement. Cleanup( )termine l'instruction et la connexion  ; tout ceci est placé dans une clause finally afin d'en garantir l'appel.

Une fois la base chargée, une instruction executeQuery( )produit un ensemble résultat. La requête concernant plusieurs tables, nous avons bien un exemple de base de données relationnelle.

On trouvera d'autres informations sur JDBC dans les documents électroniques livrés avec la distribution Java de Sun. Pour en savoir plus, consulter le livre JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). D'autres livres à propos de JDBC sortent régulièrement.

Les Servlets

Les accès clients sur l'Internet ou les intranets d'entreprise représentent un moyen sûr de permettre à beaucoup d'utilisateurs d'accéder facilement aux données et ressources [74]. Ce type d'accès est basé sur des clients utilisant les standards du World Wide Web Hypertext Markup Language (HTML) et Hypertext Transfer Protocol (HTTP). L'API Servlet fournit une abstraction pour un ensemble de solutions communes en réponse aux requêtes HTTP.

Traditionnellement, la solution permettant à un client Internet de mettre à jour une base de données est de créer une page HTML contenant des champs texte et un bouton « soumission ». L'utilisateur frappe l'information requise dans les champs texte puis clique sur le bouton « soumission ». Les données sont alors soumises au moyen d'une URL qui indique au serveur ce qu'il doit en faire en lui indiquant l'emplacement d'un programme Common Gateway Interface (CGI) lancé par le serveur, qui prend ces données en argument. Le programme CGI est généralement écrit en Perl, Python, C, C++, ou n'importe quel langage capable de lire sur l'entrée standard et d'écrire sur la sortie standard. Le rôle du serveur Web s'arrête là : le programme CGI est appelé, et des flux standard (ou, optionnellement pour l'entrée, une variable d'environnement) sont utilisés pour l'entrée et la sortie. Le programme CGI est responsable de toute la suite. Il commence par examiner les données et voir si leur format est correct. Si ce n'est pas le cas, le programme CGI doit fournir une page HTML décrivant le problème ; cette page est prise en compte par le serveur Web (via la sortie standard du programme CGI), qui la renvoie à l'utilisateur. Habituellement, l'utilisateur revient à la page précédente et fait une nouvelle tentative. Si les données sont correctes, le programme CGI traite les données de la manière appropriée, par exemple en les ajoutant à une base de données. Il élabore ensuite une page HTML appropriée que le serveur Web enverra à l'utilisateur.

Afin d'avoir une solution basée entièrement sur Java, l'idéal serait d'avoir côté client une applet qui validerait et enverrait les données, et côté serveur une servlet qui les recevrait et les traiterait. Malheureusement, bien que les applets forment une technologie éprouvée et bien supportée, leur utilisation sur le Web s'est révélée problématique car on ne peut être certain de la disponibilité d'une version particulière de Java sur le navigateur Web du client ; en fait, on ne peut même pas être certain que le navigateur Web supporte Java ! Dans un intranet, on peut exiger qu'un support donné soit disponible, ce qui apporte une certaine flexibilité à ce qu'on peut faire, mais sur le Web l'approche la plus sûre est d'effectuer tout le traitement du côté serveur puis de délivrer une page HTML au client. De cette manière, aucun client ne se verra refuser l'utilisation de votre site simplement parce qu'il ne dispose pas dans sa configuration du software approprié.

Parce que les servlets fournissent une excellente solution pour le support de programmation côté serveur, ils représentent l'une des raisons les plus populaires pour passer à Java. Non seulement ils fournissent un cadre pour remplacer la programmation CGI (et éliminer nombre de problèmes CGI épineux), mais tout le code gagne en portabilité inter plate-forme en utilisant Java, et l'on a accès à toutes les API Java (exceptées, bien entendu, celles qui fournissent des GUI, comme Swing).

Le servlet de base

L'architecture de l'API servlet est celle d'un fournisseur de services classique comportant une méthode service( ) appartenant au software conteneur de la servlet, chargée de recevoir toutes les requêtes client, et les méthodes liées au cycle de vie, init( )et destroy( ), qui sont appelées seulement lorsque la servlet est chargée ou déchargée (ce qui arrive rarement).

public interface Servlet {
  public void init(ServletConfig config)
    throws ServletException;
  public ServletConfig getServletConfig();
  public void service(ServletRequest req,
    ServletResponse res)
    throws ServletException, IOException;
  public String getServletInfo();
  public void destroy();
}

La raison d'être de getServletConfig( )est de renvoyer un objet ServletConfig contenant l'initialisation et les paramètres de départ de cette servlet. La méthode getServletInfo( )renvoie une chaîne contenant des informations à propos de la servlet, telles que le nom de l'auteur, la version, et le copyright.

La classe GenericServlet est une implémentation de cette interface et n'est généralement pas utilisée. La classe HttpServlet est une extension de GenericServlet, elle est explicitement conçue pour traiter le protocole HTTP. C'est cette classe, HttpServlet, que vous utiliserez la plupart du temps.

Les attributs les plus commodes de l'API servlet sont les objets auxiliaires fournis par la classe HttpServlet. En regardant la méthode service( )de l'interface Servlet, on constate qu'elle a deux paramètres : ServletRequest et ServletResponse. Dans la classe HttpServlet, deux objets sont développés pour HTTP : HttpServletRequest and HttpServletResponse. Voici un exemple simple montrant l'utilisation de HttpServletResponse  :

//: c15:servlets:ServletsRule.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ServletsRule extends HttpServlet {
  int i = 0; // «persistance» de Servlet
  public void service(HttpServletRequest req,
  HttpServletResponse res) throws IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    out.print("<HEAD><TITLE>");
    out.print("A server-side strategy");
    out.print("</TITLE></HEAD><BODY>");
    out.print("<h1>Servlets Rule! " + i++);
    out.print("</h1></BODY>");  
    out.close();    
  }
} ///:~

La classe ServletsRule est la chose la plus simple que peut recevoir une servlet. La servlet est initialisée au démarrage en appelant sa méthode init( ), en chargeant la servlet après que le conteneur de la servlet soit chargé. Lorsqu'un client envoie une requête à une URL qui semble reliée à une servlet, le conteneur de servlet intercepte cette demande et effectue un appel de la méthode service( ), après avoir créé les objets HttpServletRequest et HttpServletResponse.

La principale responsabilité de la méthode service( )est d'interagir avec la requête HTTP envoyée par le client, et de construire une réponse HTTP basée sur les attributs contenus dans la demande. La méthode ServletsRule se contente de manipuler l'objet réponse sans chercher à savoir ce que voulait le client.

Après avoir mis en place le type du contenu de la réponse (ce qui doit toujours être fait avant d'initialiser Writer ou OutputStream), la méthode getWriter( ) de l'objet réponse renvoie un objet PrintWriter, utilisé pour écrire les données en retour sous forme de caractères (de manière similaire, getOutputStream( )fournit un OutputStream, utilisé pour les réponses binaires, uniquement dans des solutions plus spécifiques).

Le reste du programme se contente d'envoyer une page HTML au client (on suppose que le lecteur comprend le langage HTML, qui n'est pas décrit ici) sous la forme d'une séquence de Strings. Toutefois, il faut remarquer l'inclusion du « compteur de passages » représenté par la variable i. Il est automatiquement converti en String dans l'instruction print( ).

En lançant le programme, on peut remarquer que la valeur de i ne change pas entre les requêtes vers la servlet. C'est une propriété essentielle des servlets : tant qu'il n'existe qu'une servlet d'une classe particulière chargée dans le conteneur, et jamais déchargée (sauf en cas de fin du conteneur de servlet, ce qui ne se produit normalement que si l'on reboote l'ordinateur serveur), tous les champs de cette classe servlet sont des objets persistants ! Cela signifie que vous pouvez sans effort supplémentaire garder des valeurs entre les requêtes à la servlet, alors qu'avec CGI vous auriez dû écrire ces valeurs sur disque afin de les préserver, ce qui aurait demandé du temps supplémentaire et fini par déboucher sur une solution qui n'aurait pas été inter-plate-forme.

Bien entendu, le serveur Web ainsi que le conteneur de servlet doivent de temps en temps être rebootés pour des raisons de maintenance ou après une coupure de courant. Pour éviter de perdre toute information persistante, les méthodes de servlet init( ) et destroy( ) sont appelées automatiquement chaque fois que la servlet est chargée ou déchargée, ce qui nous donne l'opportunité de sauver des données lors d'un arrêt, puis de les restaurer après que la machine ait été rebootée. Le conteneur de la servlet appelle la méthode destroy( ) lorsqu'il se termine lui-même, et on a donc toujours une opportunité de sauver des données essentielles pour peu que la machine serveur soit intelligemment configurée.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Jean-Pierre Vidal, Alban Peignier ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java