Persistance des objets
Dans l’architecture en couches des applications distribuées, la couche de persistance permet de faire le lien avec la sauvegarde physique des données. Nous avons des objets qui représentent des entités avec des états : ● une personne avec son nom, son âge, sa date de naissance… ; ● un compte bancaire avec son numéro, son solde… ; ● une adresse ; ● etc. Ces objets ont des états en mémoire et si l’application est arrêtée, cet état n’est pas sauvegardé. Le développeur doit donc se soucier du maintien de l’état dans une base de données : la persistance. Le nec plus ultra pour le développeur serait qu’il n’ait pas à se soucier des mécanismes sousjacents qui permettent aux objets d’être « persistés ». Ceci arrivera peutêtre un jour… Cette sauvegarde est principalement effectuée dans une base de données, en général, relationnelle : Oracle, MySql, PosgreSQL, HSQLDB. Elle peut aussi être effectuée par sérialisation utilisation de fichiers, de XML, etc. Cette couche va encapsuler les mécanismes qui auront la responsabilité de sauvegarder, modifier ou restaurer l’état de certains des objets métiers de l’application. Il ne doit pas y avoir de requêtes SQL, ou autres, qui parsèment l’ensemble du code des autres couches applicatives. Idéalement, le développeur ne devrait pas avoir à se soucier des mécanismes sousjacents utilisés pour permettre la persistance de ses objets. Cette couche doit aussi permettre de garder une certaine indépendance visàvis de l’architecture de la base de données utilisée. Les couches de persistance s’appuient sur l’API JDBC. De très nombreuses implémentations de cette couche peuvent être envisagées : ● codage de la couche par le développeur en utilisant l’API JDBC ; ● utilisation du framework Hibernate qui est inclus dans la distribution JBoss ; ● utilisation des EJB entités ; ● utilisation d’autres frameworks…
Codage du pattern DAO
Si le codage de la couche est effectué par le développeur, celuici mettra certainement en pratique le design pattern DAO (Data Acces Object). Ce design pattern de conception montre comment encapsuler tous les accès aux sources de données. En général, à chaque objet métier est associé un objet DAO qui contient le code SQL nécessaire à la sauvegarde, modification, suppression en base de données. Les sources présentés ici, sont issues du projet « Chap4 JDBC ». Par exemple, une classe métier Ville est associée à une classe VilleDAO qui contiendra les méthodes permettant l’ajout, les recherches en base de données, etc. Les méthodes de cette classe DAO renvoient un objet, ou des collections d’objets, de type Ville. Les classes de type Ville sont des classes en général, très simples, constituées par des méthodes de type setter/getter, appelées aussi POJO (Plain Old Java Object). Le codage de ces classes DAO n’est pas complexe, mais s’avère très vite fastidieux, voire ennuyeux car répétitif. En général, des méthodes nommées getQuelqueChoseByAutreChose(…) encapsulent des requêtes SQL du type « SELECT quelqueChose FROM table WHERE colonne=autreChose; ». La classe Ville : public class Ville implements Serializable { private String codePostal; private String nom; © ENI Editions – All rigths reserved – Kaiss Tag – 1 – enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA5MzczMzc5IC0gS2Fpc3MgVGFnIC0gZWNhNTA5YTUtMDdkMC00NDU1LTgzYjItZGFmOTViNjc0ZWMzEcY1wqORzIgLAA==-enidentnumber public Ville() {} public Ville(String nom, String cp) { this.nom = nom; this.codePostal = cp; } public String getCodePostal() { return codePostal; } public void setCodePostal(String codePostal) { this.codePostal = codePostal; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String toString() { return this.nom + » – » + this.codePostal; } } Voici quelques extraits de la classe VilleDAO : public class VilleDAO { private DAO dao = null; public VilleDAO(DAO dao) { this.dao = dao; } public Collection getVillesByCodePostal(String cp) throws ApplicationDAOException { Collection villes = new ArrayList(); String sql = « SELECT * FROM codes_postaux WHERE cp=? »; Connection con = null; try { con = dao.getConnection(); PreparedStatement st = con.prepareStatement(sql); st.setString(1, cp); ResultSet rs = st.executeQuery(); while (rs.next()) { villes.add(this.construireVille(rs)); } } catch (SQLException e) { throw new ApplicationDAOException(e); } – 2 – © ENI Editions – All rigths reserved – Kaiss Tag enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA5MzczMzc5IC0gS2Fpc3MgVGFnIC0gZWNhNTA5YTUtMDdkMC00NDU1LTgzYjItZGFmOTViNjc0ZWMzEcY1wqORzIgLAA==-enidentnumber finally { try { dao.releaseConnection(con); } catch (SQLException e) { } } return villes; } … } Vous remarquerez que cette classe est construite sur une classe DAO, que nous présenterons dans la section suivante. Remarquez aussi que les exceptions levées sont d’un type propre à l’application ce qui permet de ne pas « polluer » les autres couches de l’application par des Exceptions trop spécifiques qui exposent les APIs utilisées. En effet, la couche DAO gère la persistance, si une exception d’une API de bas niveau remonte vers la couche métier ou la couche de présentation, on supprime le couplage faible qui doit exister entre les couches de l’application. La classe ApplicationDAOException vient encapsuler le type réel de l’exception. Cette classe est très simple et permet de changer la couche DAO, qui peut générer d’autres types d’exceptions, sans impacter les couches utilisatrices. public class ApplicationDAOException extends Exception { private static final long serialVersionUID = 1L; public ApplicationDAOException() {} public ApplicationDAOException(String cause) { super(cause); } public ApplicationDAOException(Exception e) { super(e); } public ApplicationDAOException(String cause,Exception e) { super(cause,e); } } Le modèle général de codage utilisant JDBC est le suivant : ● obtention d’une connexion au serveur de base de données ; ● création d’une requête SQL ; ● exécution de la requête SQL ; ● traitement du jeu de résultat ; ● fermeture de la connexion. Le codage des classes DAO est toujours basé sur le même schéma, symbolisé par l’acronyme CRUD (Create, Read, Update and Delete) pour la création, la lecture, la mise à jour et la suppression des enregistrements en base.
Source de données et pool de connexion
Si le codage luimême n’est pas complexe, il se pose malgré tout, très vite, dans les environnements distribués, un problème important : comment gérer les connexions et déconnexions à la base de données. De manière classique, la connexion et la déconnexion sont effectuées via la classe java.sql.DriverManager de JDBC. connexion = DriverManager.getConnection(url,user,pswd) C’est donc le développeur qui gère les connexions. Mais, par exemple pour un site web, les cycles de connexion/déconnexion peuvent être très fréquents et le nombre de connexions nécessaires à un instant donné, très important. Ce mode de fonctionnement est donc inadapté. Pour pallier à ce problème, les serveurs d’applications fournissent un service de source de données qui utilise un mécanisme de pool de connexion. Le développeur n’utilise plus le DriverManager, mais une classe javax.sql.DataSource qui est fournie par le serveur. Attention, cette classe n’est utilisable que dans un environnement qui peut la fournir, elle ne l’est pas dans une application autonome. Ce n’est plus l’application qui gère les connexions, c’est le serveur. Le serveur maintient un certain nombre de connexions qui sont physiquement connectées à la base de données, et qui sont distribuées au fur et à mesure des besoins de l’application. L’appel de la méthode getConnection() permet de récupérer une connexion dans le pool. Une connexion est remise dans le pool après un appel à la méthode close(), ou après un timeout. Les sources de données sont montées dans le nommage JNDI par le serveur. Pour utiliser une source de données, il faut donc : ● effectuer une recherche JNDI ; ● demander une connexion. Une classe spécifique peut gérer les connexions et déconnexions à la base. C’est ici, la classe appelée DAO qui permet d’encapsuler le type réel utilisé pour l’accès aux objets java.sql.Connection. En effet, une application non distribuée utilisera le DriverManager, tandis qu’une application exécutée au sein d’un conteneur utilisera de préférence une DataSource. public class DAO { String driver; String url; String user; String pswd; DataSource dataSource=null; public DAO(String driver, String url, String user, String pswd) throws ClassNotFoundException { this.driver = driver; this.url = url; this.user = user; this.pswd = pswd; Class.forName(driver); } public DAO(DataSource dataSource) { this.dataSource = dataSource; } public Connection getConnection() throws SQLException { Connection connexion = null; if(this.dataSource!=null) { connexion = this.dataSource.getConnection(); } else { connexion = DriverManager.getConnection(url,user,pswd); } – 4 – © ENI Editions – All rigths reserved – Kaiss Tag enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA5MzczMzc5IC0gS2Fpc3MgVGFnIC0gZWNhNTA5YTUtMDdkMC00NDU1LTgzYjItZGFmOTViNjc0ZWMzEcY1wqORzIgLAA==-enidentnumber return connexion; } public void releaseConnection(Connection con) throws SQLException { con.close(); } } Les classes du modèle de VilleDAO vont demander à la classe DAO une connexion à la base. La classe DAO, en fonction de son mode de construction, par une DataSource, ou par les paramètres de connexion, renverra la connexion adéquate.