«Anatomie» d une application web Servlets JSP JDBC (utilisation de DataSources) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 L application histogramme de notes serveur Web Serveur SGBD Quelle architecture pour le tier web? des notes image gif Page HTML avec fréquences des notes dans un tableau Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 2
Rappel architecture MVC Bonne séparation des différents composants d une application Web Modularité réutilisation évolutivité Séparation des compétences Modèle Model View Controler (Modèle 2 pour applications JSP) Contrôle Analyse des requêtes de l utilisateur Mise en place des traitements Mise en place de la présentation Modèle Effectue les traitements couche métier Vue Présentation des résultats Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 3 Rappel Architecture MVC Serveur Internet (Serveur HTTP + Serveur Servlet/JSP) Serveur SGBD Requête HTTP Controleur Controleur Construit Modifie 2 Transfert Modèle Modèle (Java (Java Bean) Bean) Driver JDBC Réponse HTTP Vue Vue (JSP) (JSP) 3 Consulte Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 4
L application histogramme de notes Controleur Controleur (JavaBean) (JavaBean) Serveur SGBD index.html HistogramImager HistogramImager Tableau.jsp Tableau.jsp (JSP) (JSP) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 5 2 9 8 9 2 25 3 2 - String matiere - int annee - int[] freq = new int[2] + () + String getmatiere() + void setmatiere(string m) + int getannee() + void setannee(int a) + int getfreq(int n) + void setfreq(int n, int freq) Le modèle Modèle (objet métier) : classe qui représente une des entité du domaine qui pourra ensuite être utilisée par les composants Web Par exemple ici une classe qui représente un histogramme de notes POO propriétés constructeur sans paramètres accesseurs (getters) et modifieurs (setters) pour les différentes propriétés que l'on veut exposer + int noteplusfrequente() + double moyenne() + double ecarttype() Des méthodes spécifiques (métier) Pour faciliter son utilisation par les composants web (en particulier pages JSP via EL) le modèle doit être sous la forme d'un java bean Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 6
Le modèle Aller chercher dans la Bd les fréquences des notes pour une matière et une année donnée Serveur SGBD POO notes 28 matière="poo" 2 9 8 9 2 3 2 année=28 Construire un tableau des fréquences qui pourra ensuite être consulté par les composants web Année 23 23 28 28 28 28 25 note 7 4 2 8 6 Matière bd ihm poo ihm bd poo poo noetud 2234 24 2345 24 2234 2234 247 Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 7 Le modèle Aller chercher dans la Bd les fréquences des notes pour une matière et une année donnée Serveur SGBD POO notes 28 matière="poo" 2 9 8 9 2 3 2 année=28 ) Faire requête SQL via JDBC select note, count(*) from notes where annee=25 and matiere='poo poo' group by note 2 note 2 9 8 count 3 2 Année 23 23 28 28 28 28 25 note 7 4 2 8 6 Matière bd ihm poo ihm bd poo poo noetud 2234 24 2345 24 2234 2234 247 2) Parcourir le ResultSet pour remplir le tableau des fréquences Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 8
Le modèle Aller chercher dans la Bd les fréquences des notes pour une matière et une année donnée Serveur SGBD POO notes 28 matière="poo" 2 9 8 9 2 3 2 année=28 code JDBC Où mettre ce code JDBC? dans le code qui utilise le modèle (ici la Servlet)? Année 23 23 28 28 28 28 25 note 7 4 2 8 6 Matière bd ihm poo ihm bd poo poo noetud 2234 24 2345 24 2234 2234 247 dans le modèle? ailleurs? souvent on rajoute une couche DAO(Data Acces Objects) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 9 Architecture en couches présentation métier Accès aux données Navigateur Servlets Client ClientDAO Pages JSP Compte CompteDAO Serveur d'application Java JDBC SGBD Relationnel Les objets DAO (Data Access Object) isolent tout le code lié à la persistance des données Quand l'application a besoin d'effectuer une opération lié à la persistance elle fait appel à un objet DAO L'interface des objets DAO est indépendante du support de persistance Le reste de l'application utilise les DAO uniquement au travers de leur interface abstraite Par exemple si on change d'implémentation de DAO pour passer d'une base Oracle à des fichiers XML le reste de l'application demeure inchangé. Chaque classe d'objet métier a son propre type de DAO (IClientDAO, ICompteDAO) Le même objet DAO peut être utilisé pour les objets d'une même classe d'objet métier Patrick REIGNIER - Philippe GENOUDUJF Janvier 29
L application histogramme de notes l'objet NotesDAO construit l'objet Controleur Controleur NotesDAO NotesDAO (JavaBean) (JavaBean) JDBC Serveur SGBD index.html HistogramImager HistogramImager Tableau.jsp Tableau.jsp (JSP) (JSP) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 ex : objet DAO pour les notes <interface> INotesDAO void createnote(note n) Note getnote(int numetud, int annee, String matiere) get(int annee, String matiere) Collection<Note> notesetudiant(int numetud) Collection<Note> notesetudiant(int numetud, int annee) Opérations CRUD Create Retreive Collection<Note> notesetudiant(int numetud, String matiere) void updatenote(note n) void deletenote(note n) Update Delete OracleNotesDAO MySQLNotesDAO XMLNotesDAO Des classes d'implémentation mentation Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 2
ex : objet DAO pour les notes <interface> INotesDAO void createnote(note n) Note getnote(int numetud, int annee, String matiere) get(int annee, String matiere) Collection<Note> notesetudiant(int numetud) Collection<Note> notesetudiant(int numetud, int annee) Opérations CRUD Create Retreive Collection<Note> notesetudiant(int numetud, String matiere) void updatenote(note n) void deletenote(note n) Update Delete le code d'implémentation de ces méthodes effectue des entrées/sorties (dans notre cas accès à la BD) il peut donc lever des exceptions (SQLException) Que faire de ces exceptions? Les attraper? try { catch (SQLException e) { Les faire remonter? throws SQLException mais si on veut pouvoir changer facilement de DAO il ne faut pas lier les exceptions à un type de DAO particulier Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 3 ex : objet DAO pour les notes création d'une classe d'exception indépendante du type de DAO (DAOException) les méthodes d'implémentation des DAO attrapent les exceptions particulières (par exemple SQLException) et relancent des DAOException les exceptions d'origine sont chaînées aux DAOException public get(int annee, public get(int annee, String matiere) throws DAOException { String matiere) { try { peuvent lancer des fait requête JDBC SQLException création d'un objet parcours du résultat r de la requête pour remplir le tableau des fréquences de l'objet retourne l'objet catch (SQLException ( e) { throw new DAOException(" ("pb recupération ration histogramme", e); Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 4
ex : objet DAO pour les notes public get(int annee, String matiere) throws DAOException { try { connexion JDBC à la BD Où est quand créer (ouvrir) la connexion? stmt = conn.createstatement() ; Quand la libérer (fermer)? fait requête JDBC result = stmt.executequery("select note, count(*) from notes" + " where annee = " + annee + " and matiere='" + matiere + "' group by note") ; hist = new (); hist.setmatiere(matiere); création d'un objet Dans le code qui utilise les objets DAO? hist.setannee(annee); moyen ce code doit être le plus while parcours (result.next()) du résultat de { la requête pour remplir le indépendant tableau possible des DAO des note fréquences = result.getint(); de l'objet hist.setfreq(i,result.getint(2)); Dans le code des DAO? rs.close(); stmt.close(); au début et à la fin de chaque méthode nécessitant une connexion? return retourne hist; l'objet connexion attribut de l'objet DAO, accessible depuis toutes les méthodes? catch (SQLException e) { throw new DAOException("pb histogramme ", e); Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 5 ex : objet DAO pour les notes <interface> INotesDAO void createnote(note n) throws DAOException Note getnote(int numetud, int annee, String matiere) throws DAOException get(int annee, String matiere) throws DAOException Collection<Note> notesetudiant(int numetud) throws DAOException Collection<Note> notesetudiant(int numetud, int annee) throws DAOException Collection<Note> notesetudiant(int numetud, Strin matiere) throws DAOException void updatenote(note n) void deletenote(note n) throws DAOException throws DAOException Pour les classes d'implémentation assurant la persistence avec une BD toutes ces méthodes ont besoin d'une connexion JDBC à la base de donnée Où est quand créer (ouvrir) la connexion? Quand la libérer (fermer)? Dans le code qui utilise les objets DAO? moyen ce code doit être le plus indépendant possible des DAO Dans le code des DAO? au début et à la fin de chaque méthode? connexion attribut de l'objet DAO? Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 6
try { stmt = conn.createstatement() ; DAO : connexion à la BD Connexion / déconnexion à chaque requête (début et à la fin de chaque méthode) public get(int annee, String matiere) throws DAOException { Connection conn = null; result = stmt.executequery("select note, count(*) from notes" + " where annee = " + annee + " and matiere='" + matiere + "' group by note") ; hist = new (); hist.setmatiere(matiere); hist.setannee(annee); // parcours du résultat de la requete et pour chaque note // on reporte son nombre d'occurences dans le tableau freq while (result.next()) { note = result.getint(); hist.setfreq(i,result.getint(2)); rs.close(); stmt.close(); return hist; catch (SQLException e) { throw new DAOException("pb histogramme ", e); conn = DriverManager.getConnection(url, user, passwd); Opération coûteuse Approche peu efficace finally { if (conn!= null) { try { conn.close(); catch (SQLException e) { throw new DAOException(e); Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 7 public class OracleNotesDAO implements INotesDAO DAO : connexion à la BD Connexion attribut du DAO partagé par toutes les requêtes (méthodes) du DAO Connection conn = null; public OracleNoteDAO() throws DAOException { connexion initialisée dans le constructeur Class.forName("oracle.jdbc.OracleDriver); conn = DriverManager.getConnection(url, user, passwd); public get(int annee, String matiere)throws DAOException { try { stmt = conn.createstatement() ; result = stmt.executequery("select note, count(*) from notes" + " where annee = " + annee + " and matiere='" + matiere + "' group by note") ; hist = new (); hist.setmatiere(matiere); hist.setannee(annee); // parcours du résultat de la requete et pour chaque note // on reporte son nombre d'occurences dans le tableau freq while (result.next()) { note = result.getint(); hist.setfreq(i,result.getint(2)); rs.close(); stmt.close(); return hist; catch (SQLException e) { throw new DAOException("pb histogramme ", e); connexion fermée lorsque l'objet DAO n'est plus utilisé public void finalize() { if (conn!= null) { try { conn.close(); catch (SQLException e) { // que faire???? Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 8
DAO : connexion à la BD Connexion attribut du DAO partagé par toutes les requêtes (méthodes) du DAO Nombreux problèmes possibles : Timeout : la connexion peut être fermée automatiquement (par exemple si l'utilisateur a cessé momentanément son activité sans quitter l'application). Passage à l'échelle si pour chaque utilisateur (session) un objet DAO avec une connexion est crée nombre d'utilisateur limité par le nombre de connexions pouvant être ouverte simultanément Risques d'incohérences si même objet DAO donc même objet connexion partagé par plusieurs utilisateurs ne pas oublier que les servlets sont multithreadés : la même instance sert toutes les requêtes Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 9 Rappel : Cycle de vie des Servlets Moteur de servlets Créer un pool de threads Thread Thread Thread Instancier la servlet Servlet Appeler init() initialisation Requête HTTP Affecter une requête à un thread Appeler service() Requête HTTP 2 Affecter une requête à un thread Appeler service() Exécuter le service Réponse HTTP Exécuter le service Réponse HTTP 2 Terminer le pool de threads Appeler destroy() Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 2
pb du partage d'une connexion Création d'un thread t pour utilisateur t configure la connexion pour effectuer une transaction t débute d la transaction création d'un thread t2 pour utilisateur 2 t poursuit la transaction t2 configure la connexion pour effectuer une transaction t valide la transaction (la mise à jour des tables est effective) t restitue le mode autocommit t2 débute d la transaction La connexion est en mode autocommit La mise à jour de la table est immédiatement effective! temps t try { con.setautocommit(false); // exécuter les instructions // qui constituent la transaction stmt.executeupdate(); stmt.executeupdate(); // valide la transaction con.commit(); con.setautocommit(true); catch (SQLException e) { con.rollback(); // annule // les opérations de la transaction Une partie du code exécut cuté par la méthode service d'un servlet t2 Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 2 Pool de connexions Utilisation d une liste (cache) de connexions disponibles : Pool de connexions Lors de l accès à la base de données : Demande d une connexion disponible au Pool de connexions évite la création de nouvelles connexions (ce qui est coûteux) Accès à la BD à travers cette connexion Restitution de la connexion au Pool de connexions Au niveau du Pool de connexions gestion de liste des connexions : Création de nouvelles connexions si nécessaire Fermeture des connexions inactives depuis trop longtemps javax.sql package d extension standard de JDBC Pour les applications JEE (Java Entreprise Edition) Inclus en standard dans JSE (Java Standard Edition) depuis version.4 interface javax.sql.datasource pour une source de données gérant un pool de connexions Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 22
javax.sql javax.sql package d extension standard de JDBC Pour les applications JEE (Java Entreprise Edition) Inclus en standard dans JSE (Java Standard Edition) depuis version.4 DataSource : Obtention du nom de la base à partir de serveurs de noms plutôt que d avoir le nom de la base de données codé «en dur» dans l application. Utilisation de JNDI (Java Naming and Directory Interface) pour connexion à une base de donnée PooledConnection : support pour gestion d un «pool» de connexion gestion d un cache des connexion ouvertes évite la création de nouvelles connexions (ce qui est coûteux) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 23 javax.sql.datasource L'objet DataSource avec pooling de connexions maintient un ensemble de connexions à la BD prêtes à l'emploi (pool ou cache de connexions). DataSource Connection En fait PooledConnection DataSource ds; Connection c = ds.getconnnection(); Quand un code a besoin d'accéder à la base de données, il obtient une connexion en s'adressant à la DataSource Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 24
javax.sql.datasource L'objet DataSource avec pooling de connexions maintient un ensemble de connexions à la BD prêtes à l'emploi (pool ou cache de connexions). DataSource Connection En fait PooledConnection DataSource ds; Connection c = ds.getconnnection(); c = c.close(); Quand un code a besoin d'accéder à la base de données, il obtient une connexion en s'adressant à la DataSource. Il conserve la connexion jusqu'à sa fermeture explicite. Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 25 javax.sql.datasource L'objet DataSource avec pooling de connexions maintient un ensemble de connexions à la BD prêtes à l'emploi (pool ou cache de connexions). DataSource Connection En fait PooledConnection DataSource ds; Connection c = ds.getconnnection(); c = c.close(); Quand un code a besoin d'accéder à la base de données, il obtient une connexion en s'adressant à la DataSource. Il conserve la connexion jusqu'à sa fermeture explicite. Lorsqu'une connexion est "fermée", elle est en fait relâchée et remise dans le pool. Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 26
public class OracleNotesDAO implements INotesDAO DAO : connexion à la BD DataSource partagée par toutes les requêtes DataSource ds = null; try { initialisée dans le constructeur du DAO ou par une méthode set public void setdatasource(datasource ds) { this.ds = ds; public get(int annee, String matiere)throws DAOException { Connection conn = null; stmt = conn.createstatement() ; result = stmt.executequery("select note, count(*) from notes" + " where annee = " + annee + " and matiere='" + matiere + "' group by note") ; hist = new (); hist.setmatiere(matiere); hist.setannee(annee); // parcours du résultat de la requete et pour chaque note // on reporte son nombre d'occurences dans le tableau freq while (result.next()) { note = result.getint(); hist.setfreq(i,result.getint(2)); rs.close(); stmt.close(); return hist; catch (SQLException e) { throw new DAOException("pb histogramme ", e); conn = ds.getconnection(); finally { if (conn!= null) { try { conn.close(); catch (SQLException e) { throw new DAOException(e); Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 27 Définir un pool de connexions avec TomCat javax.sql.datasource = pool de connexions Interface dans javax.sql.datasource Objet DataSource pris en charge par le serveur TomCat org.apache.commons.dbcp.basicdatasource Créé par le serveur Tomcat à partir de paramètres de configuration ressource globale à un contexte (ressource partagée par toutes les sessions d'une même application) description dans le fichier context.xml de l'application ressource globale partagée par tous les contextes description dans le fichier server.xml Mis à disposition des applications par un serveur d objets : on récupère l'objet DataSource créé et géré par le conteneur au travers d'un identificateur (API JNDI (Java Naming Directory Interface)) Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 28
Définir un pool de connexions avec Tomcat Définition d'une ressource de type DataSource pour le contexte de l'application dans le fichier de configuration context.xml de l'application Désignation cette ressource dans le fichier de déploiement de l'application dans le fichier web.xml de l'application Installer le pilote JDBC dans le répertoire lib de Tomcat Voir dans la documentation de Tomcat http://tomcat.apache.org/tomcat-x.x-doc/index.html jndi-datasource-examples-howto.html Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 29 Définir un pool de connexions avec Tomcat Définition d'une ressource de type DataSource pour le contexte de l'application dans le fichier de configuration context.xml de l'application Désignation cette ressource dans le fichier de déploiement de l'application dans le fichier web.xml de l'application Installer le pilote JDBC dans le répertoire lib de Tomcat context.xml <Context path="/histogrammvc"> <Context path="/histogrammvc"> <Resource name="jdbc/notes" <Resource name="jdbc/notes" auth="container" auth="container" type="javax.sql.datasource" type="javax.sql.datasource" maxactive="" Paramétrage du maxactive="" maxidle="3" pool de connexion maxidle="3" maxwait="" maxwait="" username="genoud" username="genoud" password="xxxxxxx" password="xxxxxxx" driverclassname="oracle.jdbc.oracledriver" driverclassname="oracle.jdbc.oracledriver" url="jdbc:oracle:thin:@hopper.e.ujf-grenoble.fr:52:ufrima"/> url="jdbc:oracle:thin:@hopper.e.ujf-grenoble.fr:52:ufrima"/> </Context> </Context> Paramétrage de la connexion JDBC Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 3
Définir un pool de connexions avec Tomcat 2 Définition d'une ressource de type DataSource pour le contexte de l'application dans le fichier de configuration context.xml de l'application Désignation cette ressource dans le fichier de déploiement de l'application dans le fichier web.xml de l'application Installer le pilote JDBC dans le répertoire lib de Tomcat web.xml <servlet-mapping> </servlet-mapping> <resource-ref> <res-ref-name>jdbc/notes</res-ref-name> <res-type>javax.sql.datasource</res-type> <res-auth>container</res-auth> </resource-ref> Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 3 Définir un pool de connexions avec Tomcat Définition d'une ressource de type DataSource pour le contexte de l'application dans le fichier de configuration context.xml de l'application Désignation cette ressource dans le fichier de déploiement de l'application dans le fichier web.xml de l'application 3 Installer le pilote JDBC dans le répertoire lib de Tomcat Si vous lancez Tomcat depuis Netbeans et que vous n'avez pas le droit d'écriture dans le répertoire d'installation de Tomcat vous pouvez configurer l'instance de Tomcat que vous utilisez à l'aide des fichiers contenus dans le fichier.netbeans qui se trouve sur votre compte Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 32
Définir un pool de connexions avec Tomcat Définition d'une ressource globale partagée par tous les contextes Description dans le fichier de configuration du serveur ($CATALINA_HOME/conf/server.xml) <GlobalNamingResources> <Environment name="simplevalue" type="java.lang.integer" value="3"/> <Resource auth="container" description="user database that can be updated and saved" name="userdatabase" type="org.apache.catalina.userdatabase" pathname="conf/tomcat-users.xml" factory="org.apache.catalina.users.memoryuserdatabasefactory"/> <Resource name="jdbc/test" type="javax.sql.datasource" driverclassname="com.mysql.jdbc.driver" password="xyyyyyyy" maxidle="2" maxwait="5" username="genoud" url="jdbc:mysql://localhost/essai" maxactive="4"/> </GlobalNamingResources> La définition de la DataSource peut se faire par l'intermédiaire de la console d'administration Tomcat Au /2/28 pas disponible avec Tomcat 6. Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 33 Définir un pool de connexions avec Tomcat Définition d'une ressource globale partagée par tous les contextes 2 Déclaration d'un lien pour chaque contexte qui peut y accéder (fichier context.xmldes applications concernées) context.xml de l'application web.xml de l'application 3 Désignation cette ressource dans le fichier de déploiement (web.xml)de l'application 4 Ne pas oublier d'installer le pilote jdbc dans le répertoire lib de tomcat Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 34
Définir un pool de connexions avec Tomcat L'objet DataSource est créé par conteneur (par ex. le serveur Tomcat) à partir des informations de configuration. Ce n'est pas le code applicatif qui instancie cet objet. context.xml <Context path="/histogrammvc"> <Resource name="jdbc/notes" auth="container" type="javax.sql.datasource" maxactive="" maxidle="3" maxwait="" username="genoud" password="xxxxxxx" driverclassname="oracle.jdbc.oracledriver" url="jdbc:oracle:thin:@hopper.e.ujf-grenoble.fr:52:ufrima"/> </Context> Serveur Tomcat Servlet org.apache.commons. dbcp.basicdatasource Servlet Comment le code applicatif accède-t-il à l'objet DataSource géré par le serveur? NotesDAO import javax.naming.* import javax.sql.datasource; DataSource datasource = null; try { Context initialctxt = new InitialContext(); datasource = (DataSource) initialctxt.lookup("java:comp/env/jdbc/notes"); catch (NamingException ne) { Utilisation de l'api JNDI (Java Naming Directory Interface) définie dans le package javax.naming Patrick REIGNIER - Philippe GENOUDUJF Janvier 29 35