11. Connexion à une base

11.1. Introduction

L'objet de ce dernier paragraphe consacré à la présentation de l'API Servlet est de balayer les différentes techniques, récentes et moins récentes, qui permettent d'obtenir une connexion à une base de données lorsque l'on est dans une application Web. Toutes ces techniques sont construites sur JDBC, et commencent par établir une connexion ou une source de données ( datasource ). On retrouve donc les mêmes éléments que pour l'établissement classique d'une connexion à une base de données :
  • une dépendance vers le driver JDBC, qui dépend de la base à laquelle on veut se conncter ;
  • la fourniture d'une URL d'accès, comportant notamment le nom de la machine qui héberge ce serveur ;
  • l'utilisation d'un identifiant de connexion et d'un mot de passe.
Comme nous allons le voir, le code technique d'établissement de la connexion peut être écrit explicitement dans une servlet, de différentes manières, ou être exécuté par le serveur Tomcat (ou autre), qui va ensuite l'exposer à notre application. Il faut bien sûr que le JAR qui contient notre driver JDBC soit disponble, soit dans le répertoire WEB-INF/lib de notre application, soit dans le répertoire lib de Tomcat.

11.2. Connexion manuelle

Se connecter manuellement consiste à écrire le code classique de connexion JDBC à une base de données dans une servlet. On peut imagine de le faire de deux manières.
  • Directement dans une méthode doGet(), ce qui va consister à établir une connexion à chaque requête. Cette façon de faire peut être utilisée à titre de test, mais en aucun cas ne doit être utilisée dans une application en production ! L'établissement d'une connexion est un processus coûteux, totalement incompatible avec les exigences de performance d'une application web. Cette façon de faire est absolument à proscrire.
  • En utilisant la méthode init() d'une servlet. Cette approche utilise le même code, et est un peu meilleure : au moins la connexion est établie une fois pour toutes, et peut être réutilisée à chaque requête.
Cette deuxième approche est meilleure, et à même constitué un pattern durant un certain temps. Voyons un code possible pour cette méthode init().

Exemple 25. Établissement d'une connexion : méthode init()

public  void init(ServletConfig config) {
	
   String url = config.getInitParameter("db-url") ;
   String login = config.getInitParameter("db-login") ;
   String passwd = config.getInitParameter("db-passwd") ;
	
   Connection con = DriverManager.getConnection(url, login, passwd) ;
	
   config.getServletContext().setAttribute("db-connection", con) ;
}

Les paramètres db-url, db-login et db-passwd sont écrits dans le fichier web.xml, ce qui permet de pouvoir les modifier sans avoir à recompiler notre application.

Exemple 26. Établissement d'une connexion : paramétrage dans web.xml

<context-param>
    <param-name>db-url</param-name>
    <param-value>jdbc:mysql::3306/db_test</param-value>
 </context-param>

 <context-param>
    <param-name>db-login</param-name>
    <param-value>scott</param-value>
 </context-param>

 <context-param>
    <param-name>db-passwd</param-name>
    <param-value>tiger</param-value>
 </context-param>

La connexion est ensuite attachée au contexte application . Elle devient donc disponible auprès de toutes les servlets de notre application. Une fausse bonne idée consisterait à enregistrer cette connexion dans un champ statique. Comme un serveur est censé charger les servlets dans l'ordre dans lequel elles sont déclarées dans le fichier web.xml, il suffit de mettre la servlet qui sait se connecter en premier pour garantir que toutes les servlets pourront lire cette connexion. On peut même, ultime raffinement, ne pas associer cette servlet à une URL. Cette servlet est donc "borgne", on ne peut pas faire de requête dessus, son seul rôle consiste à établir cette connexion. En fait, cette méthode n'est guère meilleure que la première, même si elle peut paraître plus séduisante. Effectivement, écrite de cette façon, elle ne crée qu'une unique connexion, que vont devoir utiliser toutes nos requêtes. Dans certains contextes, il serait plus sûr de créer une réserve de connexions, de façon à mieux gérer les montées en charge. Mettre un pool de connexions plutôt qu'une connexion unique serait donc meilleur, d'autant qu'un pool permettrait de gérer aussi le cycle de vie de ces connexions : notamment retirer les connexions mortes, et les remplacer par d'autres. Mais il reste tout de même un problème, dans le cas d'un serveur en cluster. Dans ce cas les contextes doivent être transmis de nœud en nœud, et les objets attachés à ces contextes doivent être sérialisés. Les objets de connexion ne sont pas sérializables, cette solution ne supportera donc pas le clustering. D'une façon générale, cette méthode convient aux anciennes installations, ou code legacy , mais ne doit plus être utilisée.

11.3. Connexion par utilisation de source de données

Cette dernière méthode est celle qui doit être utilisée dans le cadre de l'utilisation d'un serveur de servlets, type Tomcat ou Jetty. Des variantes plus efficaces sont disponibles dans les serveurs JEE complets. Elle consiste à déclarer une datasource au niveau de l'application web, et de la lire par requête JNDI dans les servlets qui ont besoin de se connecter à la base. Dans le cas de Tomcat, la déclaration de cette source de données peut se faire dans le fichier META-INF/Context.xml de l'application web, comme suit.

Exemple 27. Déclaration d'une source de données dans le fichier Context.xml

<Context>
    <Resource  name="jdbc/tp-servlet"  auth="Container"  type="javax.sql.DataSource"
              maxActive="20"  maxIdle="10"  maxWait="100000"
              username="scott"  password="tiger"  driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql:/db_test"
              validationQuery="select 1"   testOnBorrow="true"/>
 </Context>

On reconnaît les différents éléments constitutifs d'une connexion JDBC : le nom du driver , l'URL de connexion, l'identifiant de connexion et le mot de passe. Cette ressource possède un nom : jdbc/tp-servlet. C'est par ce nom logique que l'on va pouvoir lire cette ressource de l'intérieur du code d'une servlet. Enfin, on remarque quelques paramètres techniques, propres au gestionnaire de réserve de connexions utilisé par Tomcat : DBCP.
  • maxActive, maxIdle et maxWait : ces paramètres règlent le fonctionnement du pool proprement dit : le nombre maximal de connexions actives à un moment donné, le nombre maximal de connexions inactives (le surplus sera fermé), et un timeout .
  • Les paramètres validationQuery et testOnBorrow permettent de tester si une connexion est active avant qu'elle soit fournie au code appelant. Effectivement, certains serveurs de bases de données, MySQL entre autres, ont la fâcheuse tendance à fermer autoritairement une connexion qui n'a pas été utilisée pendant un certain temps. Le driver JDBC n'est pas informé de cette fermeture, ce n'est que lorsque l'on tente une requête SQL que l'on se rend compte que cette connexion est en fait fermée.
Il ne nous reste plus qu'à demander une connexion à ce pool une fois dans notre servlet.

Exemple 28. Obtention d'une connexion du pool

try {
    // lecture du contexte JDNI de notre servlet
   Context initContext =  new InitialContext() ;
    // initialisation de ce contexte
   Context envContext  = (Context)initContext.lookup("java:/comp/env") ;
    // lecture de la datasource définie par requête JNDI
   DataSource ds = (DataSource)envContext.lookup("jdbc/tp-servlet") ;
    // demande d'une connexion à cette datasource
   Connection conn = ds.getConnection();
   pw.print(" " + conn) ;

}  catch (NamingException e) {
    // gestion de l'exception
}  catch (SQLException e) {
    // gestion de l'exception
}

Le code qui précède peut être appelé d'une méthode doGet(). La connexion qu'il obtient est beaucoup plus sûre que dans les exemples des paragraphes précédents :
  • c'est une connexion qui a été établie dans le pool , le coût de son établissement a déjà été payé ;
  • elle vient d'être testée par DBCP, on est donc sûr qu'elle n'a pas été fermée par le serveur de base de données.
Cette méthode supporte parfaitement la clusterization : en cas de distribution de notre application sur plusieurs nœuds, Tomcat créera un pool de connexion par nœud, il n'y aura donc aucun problème de mauvaise transmission d'une connexion d'un nœud à l'autre. Enfin, cette approche est supportée par JSTL et JSF. Une requête SQL écrite en JSTL peut parfaitement s'adresser à une telle ressource.

Exemple 29. Utilisation d'une connexion dans une page JSP

<%@  taglib  uri="http://java.sun.com/jsp/jstl/sql"  prefix="sql"%>
 <%@  taglib  uri="http://java.sun.com/jsp/jstl/core"  prefix="c"%>


 <sql:query  var="rs"  dataSource="jdbc/tp-servlet">
   select id, nom, prenom from Marin
 </sql:query>

Java servlet et JSP
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction
1. Position de l'API Servlet
2. Présentation
Présentation de Tomcat
1. Un peu d'histoire
2. Organisation des répertoires de Tomcat
2.1. Répertoire bin
2.2. Répertoire conf
2.3. Répertoire lib
2.4. Répertoire log
2.5. Répertoire temp
2.6. Répertoire webapp
2.7. Répertoire work
3. Lancement de Tomcat
3.1. Lancement par défaut
3.2. Accéder à l'administration de Tomcat
3.3. Plusieurs instances de Tomcat
4. Configuration de Tomcat
4.1. Introduction
4.2. Élément Server
4.3. Élément Service
4.4. Élément Connector
4.5. Élément Engine
4.6. Élément Host
4.7. Élément Context
4.8. Élément GlobalNamingResources
4.9. Élément Realm
4.10. Élément Valve
5. Définition et chargement des applications web
5.1. Introduction
5.2. Prise en compte des éléments Context
5.3. Chargement et mise à jour à chaud
6. Utilisation de Tomcat avec Apache
API Servlet
1. Introduction
2. Une première servlet
2.1. Le code
2.2. Création de l'application web
2.3. Déploiement dans Tomcat
3. Concepts, cycle de vie
3.1. Requête
3.2. Réponse
3.3. Session
3.4. Application web
3.5. Contexte d'exécution
3.6. Cycle de vie
3.7. Filtre
4. Présentation générale de l'API
4.1. Introduction
4.2. Interfaces disponibles
5. Notion de servlet
5.1. Interfaces servlet
5.2. Cycle de vie d'une servlet
5.3. Paramètres d'initialisation d'une servlet
6. Notion de requête
6.1. Accès aux paramètres d'une requête
6.2. Accès aux éléments de l'en-tête HTTP
6.3. Accès aux éléments de l'URL
6.4. Accès aux paramètres du client
6.5. Accès aux informations de sécurité
6.6. Accès à la session, au contexte et aux informations d'initialisation
7. Notion de réponse
7.1. Contrôle du buffer de sortie
7.2. Contrôle de la réponse HTTP
8. Notion de session HTTP
9. Redirection ou inclusion d'une ressource
10. Listeners
10.1. Introduction
10.2. Événements de l'API Servlet
10.3. Ajout ou retrait d'un attribut
10.4. Création et destruction d'un contexte
10.5. Notification d'un objet attaché à un contexte
10.6. Déclaration d'un listener dans une application web
11. Connexion à une base
11.1. Introduction
11.2. Connexion manuelle
11.3. Connexion par utilisation de source de données
Filtrage
1. Filtrage de servlets
2. Mise en place d'un filtre
2.1. Écriture d'un filtre
2.2. Déclaration du filtrage
3. Filtrage d'une requête
4. Filtrage d'une réponse
4.1. Fonctionnement de ce filtrage
Java Server Pages
1. Introduction
2. Un premier exemple
2.1. Une première JSP statique
2.2. Une première JSP dynamique
2.3. Fonctionnement interne des JSP
3. JSP scriplet
3.1. Les expressions
3.2. Les déclarations
3.3. Variables prédéfinies
3.4. Scriplet de directives
4. Utilisation de beans
4.1. Introduction
4.2. Déclaration d'un bean existant
4.3. Création d'un nouveau bean
4.4. Utilisation des propriétés d'un bean
5. Inclure un contenu externe dans une JSP
5.1. Introduction
5.2. Inclusion au lancement de l'application
5.3. Inclusion au traitement de la requête
6. Utilisation de bibliothèques de tags
6.1. Introduction
6.2. Bibliothèque core
7. Internationalisation
7.1. Notion de bundle
7.2. Internationalisation de pages JSP
Projet exemple
1. Présentation du projet