Licence CC BY-NC-SA

Lier ses tables avec des objets Java : le pattern DAO

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Vous voulez utiliser vos données dans des objets, et c'est normal ! Vous avez sans doute essayé de faire en sorte que les données de votre base collent à vos objets, à l'aide des méthodes de récupération, de création, de mise à jour et (ou) de suppression, sans obtenir le résultat escompté.

Avec le pattern DAO (Data Access Object), vous allez voir comment procéder et surtout, comment rendre le tout stable !

Avant toute chose

Vous voulez que les données de la base puissent être utilisées via des objets Java ? En tout premier lieu, il faut créer une classe par entité (les tables, exceptées celles de jointure), ce qui nous donnerait les classes suivantes :

  • Eleve ;
  • Matiere ;
  • Professeur ;
  • Classe.

Et, si nous suivons la logique des relations entre nos tables, nos classes sont liées suivant le diagramme de classes correspondant à la figure suivante.

Diagramme de classe de notre BDD

Grâce à ce diagramme, nous voyons les liens entre les objets : une classe est composée de plusieurs élèves et de plusieurs professeurs, et un professeur peut exercer plusieurs matières. Les tables de jointures de la base sont symbolisées par la composition dans nos objets.

Une fois que cela est fait, nous devons coder ces objets avec les accesseurs et les mutateurs adéquats :

  • getters et setters pour tous les attributs de toutes les classes ;
  • méthodes d'ajout et de suppression pour les objets constitués de listes d'objets

On appelle ce genre d'objet des « POJO », pour Plain Old Java Object ! Ce qui nous donne ces codes source :

Classe Eleve.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.sdz.bean;

public class Eleve {
  //ID
  private int id = 0;
  //Nom de l'élève
  private String nom = "";
  //Prénom de l'élève
  private String prenom = "";

  public Eleve(int id, String nom, String prenom) {
    this.id = id;
    this.nom = nom;
    this.prenom = prenom;
  }
  public Eleve(){};

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }   
}

Classe Matiere.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.sdz.bean;

public class Matiere {
  //ID
  private int id = 0;
  //Nom du professeur
  private String nom = "";

  public Matiere(int id, String nom) {
    this.id = id;
    this.nom = nom;
  }

  public Matiere(){}

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }   
}

Classe Professeur.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.sdz.bean;

import java.util.HashSet;
import java.util.Set;

public class Professeur {
  //ID
  private int id = 0;
  //Nom du professeur
  private String nom = "";
  //Prénom du professeur
  private String prenom = "";
  //Liste des matières dispensées
  private Set<Matiere> listMatiere = new HashSet<Matiere>();

  public Professeur(int id, String nom, String prenom) {
    this.id = id;
    this.nom = nom;
    this.prenom = prenom;
  }

  public Professeur(){}

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  public Set<Matiere> getListMatiere() {
    return listMatiere;
  }

  public void setListMatiere(Set<Matiere> listMatiere) {
    this.listMatiere = listMatiere;
  }

  //Ajoute une matière à un professeur
  public void addMatiere(Matiere matiere){
    this.listMatiere.add(matiere);
  }

  //Retire une matière à un professeur
  public void removeMatiere(Matiere matiere){
    this.listMatiere.remove(matiere);
  }
}

Classe Classe.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.sdz.bean;

//CTRL + SHIFT + O pour générer les imports
public class Classe {
  //ID
  private int id = 0;
  //Nom du professeur
  private String nom = "";
  //Liste des professeurs
  private Set<Professeur> listProfesseur = new HashSet<Professeur>();
  //Liste des élèves
  private Set<Eleve> listEleve = new HashSet<Eleve>();

  public Classe(int id, String nom) {
    this.id = id;
    this.nom = nom;
  }
  public Classe(){}

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public Set<Professeur> getListProfesseur() {
    return listProfesseur;
  }

  public void setListProfesseur(Set<Professeur> listProfesseur) {
    this.listProfesseur = listProfesseur;
  }

  public void addProfesseur(Professeur prof) {
    if(!listProfesseur.contains(prof))
      listProfesseur.add(prof);
  }

  public void removeProfesseur(Professeur prof ) {
    this.listProfesseur.remove(prof);
  }

  public Set<Eleve> getListEleve() {
    return listEleve;
  }

  public void setListEleve(Set<Eleve> listEleve) {
    this.listEleve = listEleve;
  }

  //Ajoute un élève à la classe
  public void addEleve(Eleve eleve){
    if(!this.listEleve.contains(eleve))
      this.listEleve.add(eleve);
  }

  //Retire un élève de la classe
  public void removeEleve(Eleve eleve){
    this.listEleve.remove(eleve);
  }

  public boolean equals(Classe cls){
    return this.getId() == cls.getId();
  }   
}

Nous avons des objets prêts à l'emploi. Maintenant, comment faire en sorte que ces objets puissent recevoir les données de notre base ? Au lieu de faire des essais à tâtons, nous allons définir le pattern DAO et voir comment il fonctionne avant de l'implémenter.

Le pattern DAO

Contexte

Vous disposez de données sérialisées dans une base de données et vous souhaitez les manipuler avec des objets Java. Cependant, votre entreprise est en pleine restructuration et vous ne savez pas si vos données vont :

  • rester où elles sont ;
  • migrer sur une autre base de données ;
  • être stockées dans des fichiers XML ;

Comment faire en sorte de ne pas avoir à modifier toutes les utilisations de nos objets ? Comment réaliser un système qui pourrait s'adapter aux futures modifications de supports de données ? Comment procéder afin que les objets que nous allons utiliser restent tels qu'ils sont ?

Le pattern DAO

Ce pattern permet de faire le lien entre la couche d'accès aux données et la couche métier d'une application (vos classes). Il permet de mieux maîtriser les changements susceptibles d'être opérés sur le système de stockage des données ; donc, par extension, de préparer une migration d'un système à un autre (BDD vers fichiers XML, par exemple…). Ceci se fait en séparant accès aux données (BDD) et objets métiers (POJO).

Je me doute que tout ceci doit vous sembler très flou. C'est normal, mais ne vous en faites pas, je vais tout vous expliquer… Déjà, il y a cette histoire de séparation des couches métier et des couches d'accès aux données. Il s'agit ni plus ni moins de faire en sorte qu'un type d'objet se charge de récupérer les données dans la base et qu'un autre type d'objet (souvent des POJO) soit utilisé pour manipuler ces données. Schématiquement, ça nous donne la figure suivante.

Fonctionnement du pattern DAO

Les objets que nous avons créés plus haut sont nos POJO, les objets utilisés par le programme pour manipuler les données de la base. Les objets qui iront chercher les données en base devront être capables d'effectuer des recherches, des insertions, des mises à jour et des suppressions ! Par conséquent, nous pouvons définir un super type d'objet afin d'utiliser au mieux le polymorphisme… Nous allons devoir créer une classe abstraite (ou une interface) mettant en oeuvre toutes les méthodes sus-mentionnées.

Comment faire pour demander à nos objets DAO de récupérer tel type d'objet ou de sérialiser tel autre ? Avec des cast ?

Soit avec des cast, soit en créant une classe générique (figure suivante) !

Classe DAO

Afin de ne pas surcharger les codes d'exemples, j'ai volontairement utilisé des objets Statement mais il aurait mieux valu utiliser des requêtes préparées (PreparedStatement) pour des questions de performances et de sécurité.

Classe DAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.sdz.dao;

import java.sql.Connection;
import com.sdz.connection.SdzConnection;

public abstract class DAO<T> {
  protected Connection connect = null;

  public DAO(Connection conn){
    this.connect = conn;
  }

  /**
  * Méthode de création
  * @param obj
  * @return boolean 
  */
  public abstract boolean create(T obj);

  /**
  * Méthode pour effacer
  * @param obj
  * @return boolean 
  */
  public abstract boolean delete(T obj);

  /**
  * Méthode de mise à jour
  * @param obj
  * @return boolean
  */
  public abstract boolean update(T obj);

  /**
  * Méthode de recherche des informations
  * @param id
  * @return T
  */
  public abstract T find(int id);
}

Classe EleveDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.sdz.dao.implement;
//CTRL + SHIFT + O pour générer les imports
public class EleveDAO extends DAO<Eleve> {
  public EleveDAO(Connection conn) {
    super(conn);
  }

  public boolean create(Eleve obj) {
    return false;
  }

  public boolean delete(Eleve obj) {
    return false;
  }

  public boolean update(Eleve obj) {
    return false;
  }

  public Eleve find(int id) {
    Eleve eleve = new Eleve();      

    try {
      ResultSet result = this.connect.createStatement(
        ResultSet.TYPE_SCROLL_INSENSITIVE,
        ResultSet.CONCUR_READ_ONLY).executeQuery("SELECT * FROM eleve WHERE elv_id = " + id);
      if(result.first())
        eleve = new Eleve(
          id,
          result.getString("elv_nom"),
          result.getString("elv_prenom"
        ));         
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return eleve;
  }
}

Classe MatiereDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.sdz.dao.implement;
//CTRL + SHIFT + O pour générer les imports
public class MatiereDAO extends DAO<Matiere> {
  public MatiereDAO(Connection conn) {
    super(conn);
  }

  public boolean create(Matiere obj) {
    return false;
  }

  public boolean delete(Matiere obj) {
    return false;
  }

  public boolean update(Matiere obj) {
    return false;
  }

  public Matiere find(int id) {
    Matiere matiere = new Matiere();  

    try {
      ResultSet result = this.connect.createStatement(
        ResultSet.TYPE_SCROLL_INSENSITIVE, 
        ResultSet.CONCUR_READ_ONLY
      ).executeQuery("SELECT * FROM matiere WHERE mat_id = " + id);
        if(result.first())
          matiere = new Matiere(id, result.getString("mat_nom"));         
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return matiere;
  }
}

Classe ProfesseurDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.sdz.dao.implement;
//CTRL + SHIFT + O pour générer les imports
public class ProfesseurDAO extends DAO<Professeur> {
  public ProfesseurDAO(Connection conn) {
    super(conn);
  }

  public boolean create(Professeur obj) {
    return false;
  }

  public boolean delete(Professeur obj) {
    return false;
  }

  public boolean update(Professeur obj) {
    return false;
  }

  public Professeur find(int id) {
    Professeur professeur = new Professeur();            
    try {
      ResultSet result = this.connect.createStatement(
        ResultSet.TYPE_SCROLL_INSENSITIVE, 
        ResultSet.CONCUR_READ_ONLY
      ).executeQuery(
        "SELECT * FROM professeur "+
        "LEFT JOIN j_mat_prof ON jmp_prof_k = prof_id " + 
        "AND prof_id = "+ id +
        " INNER JOIN matiere ON jmp_mat_k = mat_id"
      );
      if(result.first()){
        professeur = new Professeur(id, result.getString("prof_nom"), result.getString("prof_prenom"));
        result.beforeFirst();
        MatiereDAO matDao = new MatiereDAO(this.connect);

        while(result.next())
          professeur.addMatiere(matDao.find(result.getInt("mat_id")));
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return professeur;
  }

  public boolean update(Professeur obj) {
    return false;
  }
}

Classe ClasseDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.sdz.dao.implement;
//CTRL + SHIFT + O pour générer les imports
public class ClasseDAO extends DAO<Classe> {
  public ClasseDAO(Connection conn) {
    super(conn);
  }

  public boolean create(Classe obj) {
    return false;
  }

  public boolean delete(Classe obj) {
    return false;
  }

  public boolean update(Classe obj) {
    return false;
  }

  public Classe find(int id) {
    Classe classe = new Classe();            
    try {
      ResultSet result = this.connect.createStatement(
        ResultSet.TYPE_SCROLL_INSENSITIVE, 
        ResultSet.CONCUR_READ_ONLY
      ).executeQuery("SELECT * FROM classe WHERE cls_id = " + id); 

      if(result.first()){
        classe = new Classe(id, result.getString("cls_nom"));

        result = this.connect.createStatement().executeQuery(
          "SELECT prof_id, prof_nom, prof_prenom from professeur " +
          "INNER JOIN j_mat_prof ON prof_id = jmp_prof_k " +
          "INNER JOIN j_cls_jmp ON jmp_id = jcm_jmp_k AND jcm_cls_k = " + id
        );

        ProfesseurDAO profDao = new ProfesseurDAO(this.connect);

        while(result.next())             
          classe.addProfesseur(profDao.find(result.getInt("prof_id")));

        EleveDAO eleveDao = new EleveDAO(this.connect);
        result = this.connect.createStatement().executeQuery(
          "SELECT elv_id, elv_nom, elv_prenom FROM eleve " +
          "INNER JOIN classe ON elv_cls_k = cls_id AND cls_id = " + id
        );

        while(result.next())
          classe.addEleve(eleveDao.find(result.getInt("elv_id")));
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return classe;
  }
}

Pour ne pas compliquer la tâche, je n'ai détaillé que la méthode de recherche des données, les autres sont des coquilles vides. Mais vous devriez être capables de faire ça tout seuls.

Premier test

Nous avons réalisé une bonne partie de ce pattern, nous allons pouvoir faire notre premier test.

Je tiens à préciser que j'utilise toujours le singleton créé il y a quelques chapitres !

Voici un code de test :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import com.sdz.bean.Classe;
//CTRL + SHIFT + O pour générer les imports
public class FirstTest { 
  public static void main(String[] args) {
    //Testons des élèves
    DAO<Eleve> eleveDao = new EleveDAO(SdzConnection.getInstance());
    for(int i = 1; i < 5; i++){
      Eleve eleve = eleveDao.find(i);
      System.out.println("Elève N°" + eleve.getId() + "  - " + eleve.getNom() + " " + eleve.getPrenom());
    }

    System.out.println("\n********************************\n");

    //Voyons voir les professeurs
    DAO<Professeur> profDao = new ProfesseurDAO(SdzConnection.getInstance());
    for(int i = 4; i < 8; i++){
      Professeur prof = profDao.find(i);
      System.out.println(prof.getNom() + " " + prof.getPrenom() + " enseigne : ");
      for(Matiere mat : prof.getListMatiere())
        System.out.println("\t * " + mat.getNom());
    }

    System.out.println("\n********************************\n");

    //Et là, c'est la classe
    DAO<Classe> classeDao = new ClasseDAO(SdzConnection.getInstance());
    Classe classe = classeDao.find(11);

    System.out.println("Classe de " + classe.getNom());
    System.out.println("\nListe des élèves :");
    for(Eleve eleve : classe.getListEleve())
      System.out.println("  - " + eleve.getNom() + " " + eleve.getPrenom());

    System.out.println("\nListe des professeurs :");
    for(Professeur prof : classe.getListProfesseur())
      System.out.println("  - " + prof.getNom() + " " + prof.getPrenom());      
  }
}

Ce qui me donne la figure suivante.

Test de notre DAO

Vous avez compris comment tout ça fonctionnait ? Je vous laisse quelques instants pour lire, tester, relire, tester à nouveau… Nous utilisons des objets spécifiques afin de rechercher dans la base des données. Nous nous en servons pour instancier des objets Java habituels.

Le pattern factory

Nous allons aborder ici une notion importante : la fabrication d'objets ! En effet, le pattern DAO implémente aussi ce qu'on appelle le pattern factory. Celui-ci consiste à déléguer l'instanciation d'objets à une classe.

En fait, une fabrique ne fait que ça. En général, lorsque vous voyez ce genre de code dans une classe :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class A{
  public Object getData(int type){
    Object obj;     
    //----------------------
    if(type == 0)
      obj = new B();
    else if(type == 1)
      obj = new C();
    else
      obj = new D();
    //----------------------
    obj.doSomething();
    obj.doSomethingElse();
  }
}

… vous constatez que la création d'objets est conditionnée par une variable et que, selon cette dernière, l'objet instancié n'est pas le même. Nous allons donc extraire ce code pour le mettre dans une classe à part :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.sdz.transact;

public class Factory {
  public static Object getData(int type){
    if(type == 0)
      return new B();
    else if(type == 1)
      return new C();
    else
      return new D();
  }
}

Du coup, lorsque nous voudrons instancier les objets de la fabrique, nous l'utiliserons à présent comme ceci :

1
2
3
B b = Factory.getData(0);
C c = Factory.getData(1);
//…

Pourquoi faire tout ça ? En temps normal, nous travaillons avec des objets concrets, non soumis au changement. Cependant, dans le cas qui nous intéresse, nos objets peuvent être amenés à changer. Et j'irai même plus loin : le type d'objet utilisé peut changer !

L'avantage d'utiliser une fabrique, c'est que les instances concrètes (utilisation du mot clé new) se font à un seul endroit ! Donc, si nous devons faire des changements, il ne se feront qu'à un seul endroit. Si nous ajoutons un paramètre dans le constructeur, par exemple…

Je vous propose maintenant de voir comment ce pattern est implémenté dans le pattern DAO.

Fabriquer vos DAO

En fait, la factory dans le pattern DAO sert à construire nos instances d'objets d'accès aux données. Du coup, vu que nous disposons d'un super type d'objet, nous savons ce que va retourner notre fabrique (figure suivante).

Diagramme de classe de notre factory

Voici le code de notre fabrique :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.sdz.dao;
//CTRL + SHIFT + O pour générer les imports
public class DAOFactory {
  protected static final Connection conn = SdzConnection.getInstance();   

  /**
  * Retourne un objet Classe interagissant avec la BDD
  * @return DAO
  */
  public static DAO getClasseDAO(){
    return new ClasseDAO(conn);
  }

  /**
  * Retourne un objet Professeur interagissant avec la BDD
  * @return DAO
  */
  public static DAO getProfesseurDAO(){
    return new ProfesseurDAO(conn);
  }

  /**
  * Retourne un objet Eleve interagissant avec la BDD
  * @return DAO
  */
  public static DAO getEleveDAO(){
    return new EleveDAO(conn);
  }

  /**
  * Retourne un objet Matiere interagissant avec la BDD
  * @return DAO
  */
  public static DAO getMatiereDAO(){
    return new MatiereDAO(conn);
  }   
}

Et voici un code qui devrait vous plaire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//CTRL + SHIFT + O pour générer les imports
public class TestDAO {
  public static void main(String[] args) {
    System.out.println("");
    //On récupère un objet faisant le lien entre la base et nos objets 
    DAO<Eleve> eleveDao = DAOFactory.getEleveDAO();

    for(int i = 1; i < 5; i++){
      //On fait notre recherche
      Eleve eleve = eleveDao.find(i);
      System.out.println("\tELEVE N°" + eleve.getId() + " - NOM : " + eleve.getNom() + " - PRENOM : " + eleve.getPrenom());
    }      

    System.out.println("\n\t****************************************");

    //On agit de même pour une classe
    DAO<Classe> classeDao = DAOFactory.getClasseDAO();
    //On cherche la classe ayant pour ID 10
    Classe classe = classeDao.find(10);

    System.out.println("\tCLASSE DE " + classe.getNom());

    //On récupère la liste des élèves
    System.out.println("\n\tCelle-ci contient " + classe.getListEleve().size() + " élève(s)");
    for(Eleve eleve : classe.getListEleve())
      System.out.println("\t\t - " + eleve.getNom() + " " + eleve.getPrenom());

    //De même pour la liste des professeurs
    System.out.println("\n\tCelle-ci a " + classe.getListProfesseur().size() + " professeur(s)");      
    for(Professeur prof : classe.getListProfesseur()){
      System.out.println("\t\t - Mr " + prof.getNom() + " " + prof.getPrenom() + " professeur de :");

      //Tant qu'à faire, on prend aussi les matières
      for(Matiere mat : prof.getListMatiere())
        System.out.println("\t\t\t * " + mat.getNom());         
    }

    System.out.println("\n\t****************************************");

    //Un petit essai sur les matières
    DAO<Matiere> matiereDao = DAOFactory.getMatiereDAO();
    Matiere mat = matiereDao.find(2);
    System.out.println("\tMATIERE " + mat.getId() + " : " + mat.getNom());      
  }
}

Le résultat que nous donne ce code se trouve à la figure suivante.

Test du pattern DAO avec une factory

Vous pouvez être fiers de vous ! Vous venez d'implémenter le pattern DAO utilisant une fabrique. C'était un peu effrayant, mais, au final, ce n'est rien du tout.

On a bien compris le principe du pattern DAO, ainsi que la combinaison DAO - factory. Cependant, on ne voit pas comment gérer plusieurs systèmes de sauvegarde de données. Faut-il modifier les DAO à chaque fois ?

Non, bien sûr… Chaque type de gestion de données (PostgreSQL, XML, MySQL…) peut disposer de son propre type de DAO. Le vrai problème, c'est de savoir comment récupérer les DAO, puisque nous avons délégué leurs instanciations à une fabrique. Vous allez voir : les choses les plus compliquées peuvent être aussi les plus simples.

De l'usine à la multinationale

Résumons de quoi nous disposons :

  • des objets métiers (nos classes de base) ;
  • une implémentation d'accès aux données (classes DAO) ;
  • une classe permettant d'instancier les objets d'accès aux données (la Factory).

Le fait est que notre structure actuelle fonctionne pour notre système actuel. Ah ! Mais ! Qu'entends-je, qu'ouïs-je ? Votre patron vient de trancher ! Vous allez utiliser PostgreSQL et du XML !

C'est bien ce qu'on disait plus haut… Comment gérer ça ? On ne va pas mettre des if(){…}else{…} dans la fabrique, tout de même ?

Vous voulez insérer des conditions afin de savoir quel type d'instance retourner : ça ressemble grandement à une portion de code pouvant être déclinée en fabrique !

Tu veux créer des fabriques de fabriques ?

Oui ! Notre fabrique actuelle nous permet de construire des objets accédant à des données se trouvant dans une base de données PostgreSQL. Mais maintenant, le défi consiste à utiliser aussi des données provenant de fichiers XML.

Voici, en figure suivante, un petit schéma représentant la situation actuelle.

Notre situation actuelle

Et la figure suivante correspond à ce qu'on cherche à obtenir.

Nos deux factory

Je pense que vous êtes tous d'accord pour dire que ces deux usines ont un processus de fabrication très similaire. Par là, j'entends que nous allons utiliser les mêmes méthodes sur les objets sortant de ces deux usines. Voyez ça un peu comme une grande marque de pain qui aurait beaucoup de boulangeries dans tous les pays du monde ! Cette firme a un savoir-faire évident, mais aussi des particularités : le pain ne se fait pas à l'identique dans tous les endroits du globe.

Pour vous, c'est comme si vous passiez commande directement au siège social, qui va charger l'usine spécialisée de produire ce qui répondra à vos attentes. Schématiquement, ça donne la figure suivante.

Une factory de factory

Lorsque je vous dis ça, vous devez avoir une réaction quasi immédiate : « héritage - polymorphisme » ! Ce qui va changer le plus, par rapport à notre ancienne fabrique, c'est que nous n'utiliserons plus de méthodes statiques, mais des méthodes d'une instance concrète, et pour cause : il est impossible de créer une classe abstraite ou une interface avec des méthodes statiques destinée à la redéfinition !

Nous allons donc créer une classe abstraite pour nos futures fabriques. Elle devra avoir les méthodes permettant de récupérer les différents DAO et une méthode permettant d'instancier la bonne fabrique ! Je vous ai préparé un diagramme de classe à la figure suivante, vous comprendrez mieux.

Diagramme de classe de nos factory

Je vous ai même préparé les codes source :

Classe AbstractDAOFactory.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.sdz.dao;

public abstract class AbstractDAOFactory {
  public static final int DAO_FACTORY = 0;
  public static final int XML_DAO_FACTORY = 1;

  //Retourne un objet Classe interagissant avec la BDD
  public abstract DAO getClasseDAO();

  //Retourne un objet Professeur interagissant avec la BDD
  public abstract DAO getProfesseurDAO();

  //Retourne un objet Eleve interagissant avec la BDD
  public abstract DAO getEleveDAO();

  //Retourne un objet Matiere interagissant avec la BDD
  public abstract DAO getMatiereDAO();

  //Méthode permettant de récupérer les Factory
  public static AbstractDAOFactory getFactory(int type){
    switch(type){
      case DAO_FACTORY:
        return new DAOFactory();
      case XML_DAO_FACTORY: 
        return new XMLDAOFactory();
      default:
        return null;
    }
  }
}

Classe DAOFactory.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.sdz.dao;
//CTRL + SHIFT + O pour générer les imports
public class DAOFactory extends AbstractDAOFactory{
  protected static final Connection conn = SdzConnection.getInstance();   

  public DAO getClasseDAO(){
    return new ClasseDAO(conn);
  }

  public DAO getProfesseurDAO(){
    return new ProfesseurDAO(conn);
  }

  public DAO getEleveDAO(){
    return new EleveDAO(conn);
  }

  public DAO getMatiereDAO(){
    return new MatiereDAO(conn);
  }   
}

Classe XMLDAOFactory.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.sdz.dao;

public class XMLDAOFactory extends AbstractDAOFactory {
  public DAO getClasseDAO() {      
    return null;
  }

  public DAO getEleveDAO() {
    return null;
  }

  public DAO getMatiereDAO() {
    return null;
  }

  public DAO getProfesseurDAO() {
    return null;
  }
}

Vous devez y voir plus clair : même si la classe XMLDAOFactory ne fait rien du tout, vous voyez le principe de base et c'est l'important ! Nous avons maintenant une hiérarchie de classes capables de travailler ensemble.

Je reprends le dernier exemple que nous avions réalisé, avec quelques modifications…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//CTRL + SHIFT + O pour générer les imports
public class TestDAO {

  public static void main(String[] args) {
    System.out.println("");
    AbstractDAOFactory adf = AbstractDAOFactory.getFactory(AbstractDAOFactory.DAO_FACTORY);
    //On récupère un objet faisant le lien entre la base et nos objets 
    DAO<Eleve> eleveDao = adf.getEleveDAO();

    for(int i = 1; i < 5; i++){
      //On fait notre recherche
      Eleve eleve = eleveDao.find(i);
      System.out.println("\tELEVE N°" + eleve.getId() + " - NOM : " + eleve.getNom() + " - PRENOM : " + eleve.getPrenom());
    }      

    System.out.println("\n\t************************************");

    //On fait de même pour une classe
    DAO<Classe> classeDao = adf.getClasseDAO();
    //On cherche la classe ayant pour ID 10
    Classe classe = classeDao.find(10);

    System.out.println("\tCLASSE DE " + classe.getNom());

    //On récupère la liste des élèves
    System.out.println("\n\tCelle-ci contient " + classe.getListEleve().size() + " élève(s)");
    for(Eleve eleve : classe.getListEleve())
      System.out.println("\t\t - " + eleve.getNom() + " " + eleve.getPrenom());

    //De même pour la liste des professeurs
    System.out.println("\n\tCelle-ci a " + classe.getListProfesseur().size() + " professeur(s)");      
    for(Professeur prof : classe.getListProfesseur()){
      System.out.println("\t\t - Mr " + prof.getNom() + " " + prof.getPrenom() + " professeur de :");

      //Tant qu'à faire, on prend aussi les matières
      for(Matiere mat : prof.getListMatiere())
        System.out.println("\t\t\t * " + mat.getNom());
    }

    System.out.println("\n\t***********************************");

    //Un petit essai sur les matières
    DAO<Matiere> matiereDao = adf.getMatiereDAO();
    Matiere mat = matiereDao.find(2);
    System.out.println("\tMATIERE " + mat.getId() + " : " + mat.getNom());      
  }
}

Et le résultat est le même qu'avant ! Tout fonctionne à merveille ! Si vous utilisez un jour l'usine de fabrication XML, vous n'aurez qu'une seule ligne de code à changer :

1
AbstractDAOFactory adf = AbstractDAOFactory.getFactory(AbstractDAOFactory.XML_DAO_FACTORY);

Voilà : vous en savez plus sur ce pattern de conception et vous devriez être à même de coder le reste des méthodes (insertion, mise à jour et suppression). Il n'y a rien de compliqué, ce sont juste des requêtes SQL. ;-)


  • Le pattern DAO vous permet de lier vos tables avec des objets Java.
  • Interagir avec des bases de données en encapsulant l'accès à celles-ci permet de faciliter la migration vers une autre base en cas de besoin.
  • Afin d'être vraiment le plus souple possible, on peut laisser la création de nos DAO à une factory codée par nos soins.
  • Pour gérer différents types de DAO (BDD, XML, fichiers…), on peut utiliser une factory qui se chargera de créer nos factory de DAO.