Thématiques principales

Affichage des articles dont le libellé est JDBC. Afficher tous les articles
Affichage des articles dont le libellé est JDBC. Afficher tous les articles

mercredi 7 mars 2018

Design Pattern : DAO


Aujourd’hui nous revenons à java et plus particulièrement à sa version entreprise JEE. Le but de cet article n’est pas de présenter JEE dans son ensemble ( cela sera le propos d’un article plus générale sur JEE) mais de regarder un pattern important du développement des applications d’entreprises. Le pattern que j'évoque n’est pas le pattern MVC auquel nous allons dédié un article plus tard également mais le pattern DAO (Data Access Object) [1] qui nous permet de gérer la couche d'accès aux bases de données. Bien sur aujourd’hui JPA (TopLink [2], Hibernate [3]) a largement pris sa place mais à l'époque où ce dernier n’avait pas la maturité qu’on lui connaît aujourd’hui, ce pattern a rendu bien des services techniques mais aussi théorique en apportant un concept clef de la programmation, le découplage.

Je vous propose donc de traiter un peu ce sujet afin et de le poursuivre par JPA (ba oui forcément…)

Bien sur j’aurai du commencer par une présentation générale de JEE avant d’aborder des sujet plus précis. Ainsi afin que l’on puisse s’y retrouver, il faut juste imaginer une architecture applicative JEE comme une architecture en 3 couches, une couche présentation, une couche métier et une couche données


Dans cette architecture, on trouvera un certains nombres de patterns permettant de faciliter le développement mais aussi permettant de rendre standard toutes applications JEE. Ainsi un développeur sur un projet JEE trouvera les mêmes types de structures dans un autre projet quelques soit le métier. Ainsi par exemple, on utilise classiquement le modèle MVC pour implémenter la couche présentation et gérer une partie de la couche métier. De la même manière, une application ayant des besoins de stockage de données et utilisant un SGBD devra implémenter une couche d'accès aux données. C’est à ce niveau que le pattern DAO va intervenir.

Le découplage de la couche métier

Dans l’absolu, lorsque l’on conçoit une application logiciel, la première composante à laquelle on pense et la composante métier . Effectivement, celle-ci comporte la plus grande partie de la valeur du logiciel en fournissant les briques fonctionnelles de base qui répondront aux besoins du client.

Cependant si le besoin fonctionnel nécessite la consolidation des données dans l’objectif d'être exploité ultérieurement, une première approche est mapper les données métiers directement à un système de stockage.

Par exemple si nous considérons un logiciel de gestion de tâches, associé à des utilisateurs qui vont les réaliser. nous pourrions avoir une structure métier tel que (je vous passe les accesseurs par soucis de concision mais ils doivent être présent afin de respecter la convention bean [11]):


public class Task {
 private String title;
 private String description;
 public enum State {READY, STARTED, DONE} ;
 public State state=State.READY;
 private List<User> users=new ArrayList<>();
 public Task(String title,String description){
  this.title=title;
  this.description=description;
 }
}
public class User {
 private String name;
 private List<Task> tasks=new ArrayList<>();
 public User(String name){
  this.name=name;
 }
 public void work(Task t){
  t.state=State.STARTED;
 }
 public void done(Task t){
  t.state=State.DONE;
 }
}


Ainsi une première approche peut nous décider que sérialiser nos objets en leur ajoutant une fonction toXml() sera suffisant. On imagine rapidement les limitations de cette approche en terme de maintenance car cela implique de produire du code métier dépendant d’une solution technique de sauvegarde. Alors que se passera t il si l’on souhaite passer à l'échelle et utiliser une techno comme JDBC ? On va encore modifier le code métier afin d’y ajouter les spécificités techniques de cette techno et de la base de données qui sera choisie? Non bien sur et je ne vous parle pas des tests unitaires qu’il faudra réadapter à chaque fois…. et la complexité de les produire….

Il nous faut donc une approche rationnelle et propre permettant de développer séparément le code métier et la ou les solutions de stockage. Alors bien sur il existe des patterns génériques permettant le découplage de composantes logicielles comme les patterns visiteur[4] , proxy[5] , observateur[6] ou médiateur [7]. En fait le pattern DAO est plutôt une association de plusieurs patterns dont le Pont [8] et le stratégie [9].

Ainsi le principe est simple, le pattern DAO implique la définition d’un certain nombre d’interfaces fournissant les services pour la manipulation du modèle de données au travers de quatre principales procédures formant le CRUD [10] : Create, Read, Update, Delete.

Il nous faut donc une interface dédiée par élément métier:


public interface IUserDAO {
 boolean create(User user);
 User read(String name);
 boolean update(User user);
 boolean delete(String name);
}
public interface ITaskDAO {
 boolean create(Task task);
 Task read(int identifiant);
 boolean update(Task task);
 boolean delete(int identifiant);
}


L’utilisation des objets DAO au travers des interfaces va alors nous garantir l'indépendance au niveau implémentation. Il nous reste par contre à fournir un moyen d’obtenir ces objets. Pour cela, on va s’appuyer sur le pattern factory qui va au passage nous servir d’environnement pour toute la configuration des drivers de la base de données cible.

On aura donc une factory par famille d’objet implémentant les interfaces DAO. Par exemple:


public class FactoryDAO {
 private String url;
 private String userDB;
 private String pwdDB;
 private FactoryDAO( String url, String userDB, String pwdDB ) {
  this.url = url;
  this.userDB = userDB;
  this.pwdDB = pwdDB;
 }
 public static FactoryDAO getInstance() throws ClassNotFoundException {
  String url="UrlBD";
  String driver="Driver";
  String userDB="UserBD";
  String pwdDB="mdpBD";
  Class.forName( driver );
  return new FactoryDAO(url, userDB, pwdDB);
 }
 public Connection getConnection() throws SQLException {
  return DriverManager.getConnection(url, userDB, pwdDB);
 }
 public IUserDAO getUserDao() {
  return new UserDAOImpl(this);
 }
 public ITaskDAO getTaskDao() {
  return new TaskDAOImpl(this);
 }
}


Si on voulait faire ça bien, on réalisera également une interface pour la factory de façon à la découpler totalement de son utilisateur final (qui sera probablement une servlet … mais c’est une autre histoire)

Bien notre factory va nous produire les Objets UserDAOImpl, TaskDAOImpl dont l'implémentation est spécifique à la base de données gérée par la factory. On trouvera donc dans ces deux classes les composantes CRUD telles que:


public class UserDAOImpl implements IUserDAO {
 private FactoryDAO factory;
 public UserDAOImpl(FactoryDAO factory ) {
  this.factory=factory;
 }
 @Override
 public boolean create(User user) {return false;}
 @Override
 public User read(String name) {return null;}
 @Override
 public boolean update(User user) { return false;}
 @Override
 public boolean delete(String name) { return false;}
}
public class TaskDAOImpl implements ITaskDAO {
 private FactoryDAO factory;
 public TaskDAOImpl(FactoryDAO factoryDAO) {
  this.factory=factoryDAO;
 }
 @Override
 public boolean create(Task task) { return false; }
 @Override
 public Task read(int identifiant) { return null; }
 @Override
 public boolean update(Task task) { return false;}
 @Override
 public boolean delete(int identifiant) { return false; }
}


Bien sur les composantes create, read, update et delete ne sont pas encore ici implémenté. On va s’en charger en utilisant la factory passé en paramètre de l’objet lors de sa construction. Par contre, et parce que c’est à la factory que nous avons donné le rôle de gérer les accès à la BD, nous allons ajouter des fonctions a cette première pour que les implémentations de DAO n’en soit que plus simple.

Pour implémenter ces méthodes, nous allons utiliser utiliser la méthode getConnection de la factory. Celle-ci va nous fournir un objet Connexion nous permettant de construire un Statement ou un PreparedStatement [12].

La différence entre les deux ces deux types d’objet est simple, les Statements permettant l'exécution d’une requête construite à la main alors que le PrepareStatement fournir une structure a trou a la place.

Dans tous les cas, leur exécution nous retournera un ResultSet contenant l’ensemble des données résultant de la requête.

Ainsi par exemple pour implémenter le read de la classe UserDAOImpl permettant de récupérer un utilisateur, on procédera tout d’abord en définissant la requête Sql a base select qui correspond (très simple dans cet exemple). Ensuite on demandera à la factory un PreparedStatement (obtenu à partir de la Connexion) que l’on exécutera. Son résultat sera alors traité afin d'être mapper avec un vrai objet métier User.


private static String SELECT="select name from User";
@Override
public User read(String name) throws SQLException {
 PreparedStatement statement=this.factory.preparedStatement(SELECT);
 ResultSet result=statement.executeQuery();
 return this.mapResultSetOnUser(result);
}
private User mapResultSetOnUser(ResultSet set) throws SQLException {
 ser user=new User(set.getString("name"));
 return user;
}


Bien sur ce cas présent est très simple et ne présente vraiment aucun difficulté particulière. Par contre, les méthodes traitant de la création, de la mise à jour et de la suppression vont être pour leur part un peu plus complexe à mener à cause de la nécessité de gérer la cohérence de la base de données.

En effet, la modification d’une association entre deux objets métiers va forcément impacter leur relation images dans le monde relationnel de la base de données. Et même avec tous les effort du monde, si l’on souhaite faire les choses de façon cohérente et ne pas risquer de tomber dans des états intermédiaire et passablement dégradé, il sera important d’employer un modèle transactionnel de plus haut niveau tel que JTA [13] mais c’est une autre histoire.

De plus rappelons nous, si a l’origine nous voulions découpler les couche métier et base de données, nous nous retrouvons malgré tout maintenant avec une bonne dose de code à maintenir (c’est toujours ça que de l’avoir dans le code métier). Dans un prochain article nous nous intéresserons a comment les frameworks de mapping objet relationnel ont résolu ce problème

Voilà, sans forcément être entré dans beaucoup de détails, nous avons fait un tour rapide du pattern DAO, de ses avantages et de ses faiblesses. Bien sur aujourd’hui d’autres approches sont à envisager pour concevoir la couche d'accès à la base de données mais il ne faut pas oublier qu’il existe encore des logiciels à maintenir utilisant encore cette technologie et qu’il reste bon de la connaître (du moins savoir de quoi nous sommes parti avant JPA, toplink, hibernate, etc....)

Références:

[1] https://fr.wikipedia.org/wiki/Objet_d%27acc%C3%A8s_aux_donn%C3%A9es
[2] http://www.oracle.com/technetwork/middleware/toplink/overview/index.html
[3] http://hibernate.org/
[4] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/design-pattern-visiteur.html
[5] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-proxy.html
[6] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-observateur.html
[7] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/ready-design-pattern-mediateur.html
[8] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-le-pont.html
[9] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-factory.html
[10] https://www.supinfo.com/articles/single/3637-operations-crud
[11] https://www.jmdoudoux.fr/java/dej/chap-javabean.htm
[12] https://java.developpez.com/faq/jdbc?page=Les-instructions-parametrees-moins-PreparedStatement#Qu-est-ce-qu-un-PreparedStatement
[13] http://blog.xebia.fr/2010/01/07/transactions-les-concepts-et-modeles/

dimanche 4 février 2018

SGBD-R : Introduction

Introduction

Le développement de systèmes logiciels fait intervenir de nombreux composants et concepts.  Ces éléments sont organisés généralement a l’aide d’architectures conçues afin de répondre à différents types besoins comme capturer les concepts métier au travers d’algorithmes, ou définir des IHM afin de rendre convivial l'utilisation de l’application. A l‘opposé des IHM, dans ces architectures (que nous détaillerons dans un autre article), nous trouverons de façon quasi systématique un service de persistance des données ou base de données (SGBD).

En effet, l’utilisation de SGBD dans les systèmes logiciel actuel est devenu quasi incontournable. Leur rôle qui dépasse aujourd’hui le simple rôle de sauvegardes s'étend sur des fonctions telles que la redondance ou la gestion décentralisé des données afin de fournir des services à haute disponibilité . A la base très structuré pour des besoins de cohérence dans les systèmes de gestion de base de données relationnelles,  l'émergence du nosql a fait apparaître des type de base plus rapide et capable d'absorber et restituer de très grande quantité de données.

Les approche NoSQL sont tres interessante mais seront le propos la aussi d’un autre article, ainsi, dans celui ci je vous propose de revenir sur les SGBD-R et de parcourir leur utilisations,  leur avantage et leur inconvénients et ce qui justifie encore largement leur emplois [1].

Les SGBD-R ou systeme de gestion de bases de données relationnelles sont des outils de stockages de l’information contenant deux composants

  • un outil de gestion
  • un base de données relationnelle

Logiciel de gestion

L’outils de gestion est un outil offrant un service transactionnel d'accès aux données via un langage de requetes (SQL étant le plus commun et connu) des services de gestion des utilisateurs, des droits (en lecture et/ou écriture) , de la configuration (adresse d’acces, port de connexion, config de cache, etc.). Bien sur l’architecture du logiciel de gestion de la base de données comporte divers composants comme un catalogue système ou un gestionnaire de reprise pour la gérer les pannes et l'intégrité des données mais ces éléments peuvent variés d’une SGBDR a un autre (pour entrer dans le détails il est préférable de consulter les documentation officielle comme celle de postgres [2], Oracle [3], MySQL [4] ou SqlServer [5])

La partie logiciel de gestion n’est pas la partie la plus intéressante (enfin sauf pour les spécialistes a spécifique à ces bases), ce qu’il faut par contre en retenir est sa composante transactionnelle dont l’objectif est de garantir l'intégrité des données (en terme de concurrence d'accès, etc.) En ces termes, les transactions sont alors dites ACID :

  • Atomique : une transaction a la fois
  • Coherent: une transaction ne doit permettre la corruption de la base de donnée ou violer des contraintes d'intégrités
  • Isolation : les modifications apportées par une transaction ne sont accessible qu'après validation de la transaction
  • Durabilité : les modifications apportées aux données doivent, après validation, etre recuperable meme apres une panne.

La base de données

Caché derrière le logiciel de gestion, la partie base de données est le conteneur qui va structurer l’ensemble des informations. Pour cela, la base de donnée va s’appuyer sur le modèle relationnel qui consiste en la définition et la manipulation de Relations constituées d’Attributs. Associé aux Relations seront construits des tuples (ou enregistrement) assimilables a des instances des Relations pour lesquelles pour chaques Attributs on disposera d’une valeur. Classiquement on représente les relations, attributs et tuples par un tableau.

Ainsi par exemple on peut définir la Relation Personne constitué des Attributs Nom et Prenom pour lesquelles on va definir les tuples {Einstein, Albert} et {Turing, Alan} représenté comme suit:

Personne
Nom
Prenom

Einstein
Albert

Turing
Alan

Bien sur on aura un probleme si deux personnes ont les meme Nom et Prenom, c’est a dire les memes attributs, on va donc utiliser une clef primaire afin de permettre la différenciation. Cette clef primaire nommé id par convention, est définie comme unique (souvent auto-incrémenté), ceci est une contrainte d'intégrité

Personne
Id
Nom
Prenom

1
Einstein
Albert

2
Turing
Alan

Les contraintes d’intégrités

Des contraintes d'intégrations [6] il en existe 4 types. Nous venons d’en voir une, la contrainte structurelle, permettant un fonctionnement nominal de la base. Ensuite il est possible de définir des contraintes fonctionnelles comme une limite sur une valeur (comme un âge) permettant une capture plus fine du domaine métier que représente la base de données (nous reviendrons sur ce point plus précisément par la suite) Ensuite, il y aura des contraintes intra-relation, comme d’imposer la non nullité de l'attribut Nom dans la relation Personne. Ceci est une contrainte qui pourrait être vu comme fonctionnelle effectivement elle permet à l’enregistrement d’avoir du sens pour le domaine métier mais elle permet aussi de garantir un besoin technique (comme le calcule d’un jointure que nous verrons plus tard)

Enfin dernier type de contrainte, la contrainte inter-relation, qui va garantir une certaine coherence des données entre des relations différentes. Par exemple imaginons qu’il existe en plus de notre relation Personne, une relation adresse contenant les attributs Rue, CodePostal et Ville. Une contrainte pourrait définir que pour tout enregistrement Personne, il existe au moins une adresse connue.

Pour ce type de contrainte, il suffira par exemple de définir une clef étrangère dans la relation personne qui sera non null et dont la valeur sera la clef primaire de la relation Adresse:

Personne
IdPersonne
Nom
Prenom
IdAdresse

1
Einstein
Albert
1

2
Turing
Alan
2

Adresse
IdAdresse
Rue
CodePostal
Ville

1
xxx
59000
Lille

2
yyy
59500
Douai

Bien sur si cette façon de faire permet de répondre a la contrainte inter-relation, elle ne permet de resoudre le probleme du au moins une adresse, signifiant que potentiellement, une Personne peut avoir plusieur adresse. Ici c’est impossible.

Pour resoudre ce probleme, il va falloir inverser la logique, c’est a dire de référencer la Personne aupres des adresses ainsi on va pouvoir avoir plusieurs adresse référençant la même personne:

Personne
IdPersonne
Nom
Prenom

1
Einstein
Albert

2
Turing
Alan

Adresse
IdPersonne
IdAdresse
Rue
CodePostal
Ville

1
1
xxx
59000
Lille

2
2
yyy
59500
Douai

2
3
yyy
80000
Amiens

Bien sûr ceci ne résout pas un dernier potentiel problème qu’est : et si une adresse correspond a plusieurs personnes? (dans le cas d’une famille par exemple). Alors il est vrai que le plus évident est alors de dupliquer les adresses tout simplement comme elles auront des Id differentes mais si ils habitent au même endroit, a priori, cela ne sera pas compliqué de retrouver les enregistrements commun… Ceci est probablement l’approche la plus simple dans un cas comme celui la mais, on duplique des données et si les enregistrements ne sont pas renseigné exactement correctement, alors il y aura peut être des problèmes.

Pour resoudre ce probleme, on va créer une nouvelle relation dont la vocation sera de réifier l’association n-n que l’on cherche a creer (jusque la en fait sans le dire on examinait comment créer des associations 1-n et n-1 entre les relations Personne et Adresse) Ainsi on va créer une troisième relation que l’on nommera AssPersonneAdresse contenant juste les Id des différentes :

Personne
IdPersonne
Nom
Prenom

1
Einstein
Albert

2
Turing
Alan

Adresse
IdAdresse
Rue
CodePostal
Ville

1
xxx
59000
Lille

2
yyy
59500
Douai

3
yyy
80000
Amiens

AssPersonneAdresse
IdPersonne
IdAdresse

1
1

2
2

2
3

Par les questions que nous venons de traiter, on se rend facilement compte que créer une base de données nécessite une certaines expertise et de la méthode. Pour cela, on abordera la notion de normalisation de base de données dans un article prochain

Références

[1] https://dzone.com/articles/selecting-an-application-database?edition=305102&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=dd%202017-06-16
[2] https://www.postgresql.org/
[3] https://docs.oracle.com/en/database/oracle/oracle-database/index.html
[4] https://www.mysql.com/fr/
[5] https://www.microsoft.com/fr-fr/sql-server/sql-server-2017
[6] http://www-inf.int-evry.fr/cours/BD/MSCurriculum/FRENCH/PDF/CI.pdf