Spring + Hibernate : Mise en place d’un DAO générique

 Article modifié dernièrement le 17 Juil 2011 @ 2 h 45 min

Introduction

Les opérations de base pour la persistance des données sont toutes identiques quelques soit l’objet à sauvegarder, les méthodes en question sont celles d’un CRUD : enregistrement (Create), lecture (Read), mise à jour (Update) et suppression (Delete).

La plupart du temps ces méthodes sont répétées et redéfinies dans chacun des DAO de notre application, la seule différence notable est le type des objets que ces méthodes prennent en paramètre, cependant avec Java 5 et les generics pourquoi ne pas les écrire une fois pour toutes dans un DAO générique ? cela est possible et est considéré comme une bonne pratique, la redondance des données étant à éviter à tout prix.

L’exemple ci-dessous utilise Hibernate pour la persistance des données et Spring pour la création des objets et leurs dépendances.

Définition

  • Spring framework open souce J2EE qui prend en charge la création d’objets et la mise en relation d’objets par l’intermédiaire d’un fichier de configuration XML qui décrit les objets à fabriquer et les relations de dépendances entre ces objets.3 concepts font la force de Spring :
  1. le pattern inversion de contrôle (ou IOC)
  2. la programmation orientée aspect (ou AOP)
  3. une couche d’abstraction permettant d’intégrer d’autres frameworks existant
  • Hibernate framework open source Java qui simplifie la persistance des objets en base de données relationnelle. Son utilisation remplace la mise en place de JDBC et propose entre autres le cache des objets, les sessions et les transactions.
  • DAO : de son vrai nom Data Access Object, ce patron de conception (ou pattern) permet de séparer la couche d’accès aux données de la couche logique applicative.  Son utilisation permet de s’abstraire de la façon dont les données sont stockées au niveau des objets métier.

DAO Générique

  • Voici l’interface du DAO générique, les méthodes disponibles sont celles d’un CRUD, les interfaces des autres DAO devront étendre celle-ci :
  • Son implémentation est tout simple, cependant pour en bénéficier les implémentations des autres DAO devront étendre celui-ci. A noter que c’est le constructeur de cette classe qui permet de typer les objets concernés par le DAO :

DAO spécifique

  • Exemple d’interface d’un DAO propre à votre application qui doit pouvoir appeler les méthodes du DAO générique plus d’autres qui lui sont propre :
  • Son implémentation, désormais les méthodes create, read, update, delete ainsi que selectAllArtists sont accessibles :

Configuration XML

Nous venons de voir que simplement deux classes suffisent à rendre générique les principales actions d’un CRUD, pour ce qui est de la configuration Spring c’est un peu plus compliqué tout en restant accessible avec un peu de concentration 🙂

Voici un exemple de fichier de configuration Spring pour l’utilisation du DAO générique, la partie sur les transactions n’est pas utile mais je la laisse pour exemple.

  • Partie Persistance : source de données utilisant JNDI, propriétés Hibernate (sessionFactory), et gestionnaire de transaction Hibernate
  • Partie DAO : proxy DAO pour l’injection du sessionFactory d’Hibernate qui permet d’exécuter les requêtes, injection de l’objet concerné par les méthodes génériques
  • Partie Service : proxy pour les transactions automatiques sur nos services métier, injection des DAO au service

Conclusion

Finalement ce n’est pas bien compliqué d’utiliser un DAO générique pour Hibernate, deux classes à ajouter à votre projet ainsi qu’une modification dans votre fichier de configuration Spring.

Je me demande si cela est envisageable avec l’utilisation de JDBC uniquement, je vois mal comment … si quelqu’un a déjà vu ça, je suis preneur ^^

Sources

Voici les sources d’un exemple d’utilisation du DAO générique : sources disponibles.

L’exemple n’est pas une WebApp mais plutôt un Batch, les paramètres de connexion à votre base de données sont à modifier dans le fichier JndiDatasourceCreator.java.

Trois flèches vers le bas

1- Logiciel de brouillage d’adresse IP :

Contourner la censure en surfant anonyme

2- L’article explicatif :

La différence entre un proxy et un VPN

3- Comment espionner un smartphone (app) :

L’application de référence

Commentez ici

  • Greg 16 mars 2010, 12 12

    Hi all ! Toujours pas de réponse à la question posée ? :’

  • maribo 30 mars 2010, 21 09

    Salut,
    Je trouve super le Tuto. Mais il y a un truc que je ne comprends pas. comment on on charge les fichiers de configuration spring. Serait il possible d’avoir le code source s’il te plaît.

    Merci d’avance

  • Mimie 30 mars 2010, 23 11

    Salut maribo, la configuration Spring se charge dans le web.xml :
    [xml]

    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:TON_FICHIER_SPRING.xml</param-value>
    </context-param>

    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    [/xml]
    Je vais néanmoins mettre à disposition les sources dès que possible. A bientôt.

  • maribo 30 mars 2010, 23 11

    Je te remercie Mimie pour ta réponse et de la réactivité. Je vais essayer cette config dans mon web.xml. Mais c’est vrai que si tu as les sources ça serait sympa.
    Merci d’avance

  • Mimie 31 mars 2010, 21 09

    Voilà les sources sont disponibles dans la section « Sources » de l’article, j’espère que ça aidera 🙂

  • maribo 31 mars 2010, 21 09

    Merci beaucoup Greg pour les sources. Cela m’aidera beaucoup

  • Greg 31 mars 2010, 21 09

    Oups, ‘tite erreur Maribo, tout le mérite revient à Mimie c’est lui le programmeur du blog 😀 Merci à lui !

  • maribo 31 mars 2010, 21 09

    Oups, oui désolé. j’ai dû écrire un peu trop vite. Je voulais dire Merci 1000 fois à Mimie. Bonne soirée

  • Mimie 31 mars 2010, 21 09

    De rien, à ta prochaine visite 🙂

  • Stephane 17 juillet 2010, 14 02

    Une petite erreur ? Vous ecrivez : « injection de contrôle »

    On parle de « inversion de contrôle » et de « injection de dependance »

    🙂

    Sinon, c’est un blog sympa, l’article est clair !

  • Mimie 18 juillet 2010, 16 04

    @Stephane : tu as tout à fait raison, je me suis mélangé les pinceaux, moi et les termes anglais ça fait deux :D, merci bien ^^
    Article corrigé.

  • Waddle 18 août 2010, 18 06

    Salut,

    J’ai déjà fais la même chose de mon côté et je voulais te demander ton avis puisqu’on a eu la même idée au final. Dans le cas d’une DAO « vide », cad qui se contente de la genericDAO, on serait tenté de l’instanciée en la paramétrant par le bean manipulé. Quitte à instancier la genericDAO pour chaque bean dont on ne veut qu’un simple CRUD. Le problème c’est qu’instancier cette classe nous empêche de la gérer dans Spring.

    Donc comment ferais-tu ? Aujourd’hui, quand ce cas se présente je fais une DAO vide que je déclare dans Spring pour pouvoir l’injecter dans la couche service. J’aimerais donc me passer de cette classe vide. En résumé : instancier « n » fois la classe GenericDao dans Spring, mais avec un type différent.

    a+

  • Mimie 18 août 2010, 21 09

    @Waddle : si je comprends bien tu souhaites te passer d’avoir à créer les classes Dao qui utilisent uniquement les méthodes du GenericDao, voici la solution que je te propose et qui fonctionne chez moi :
    1. tu définis ton Dao dans Spring sans créer aucun fichier java
    [xml]
    <bean id="setDao" class="fr.geeks.magic.domain.dao.hibernate.GenericDaoImpl" parent="proxyDAO">
    <constructor-arg value="fr.geeks.magic.domain.model.Set" />
    </bean>
    [/xml]
    2. tu l’injectes dans ton service comme le CardDao de l’article
    [xml]
    <bean id="cardServiceTarget" class="fr.geeks.magic.business.service.impl.CardServiceImpl">
    <property name="cardDao" ref="cardDao" />
    <property name="setDao" ref="setDao" />
    </bean>
    [/xml]
    3. dans ton service CardServiceImpl tu récupères le SetDao de cette façon
    [java]
    GenericDao<Set, Integer> setDao;

    // Spring injection
    public void setSetDao(GenericDao<Set, Integer> setDao) {
    this.setDao = setDao;
    }
    [/java]
    4. tu te fais une nouvelle méthode publique dans ce service pour tester le SetDao
    [java]
    public Set findSetById(Integer setId) {
    return setDao.read(setId);
    }
    [/java]

    C’est tout. Je suis peut-être à côté de la plaque sur tes attentes, à toi de me le dire 🙂

  • waddle 18 août 2010, 22 10

    En fait, t’avais déjà tout dit mais j’avais pas fais attention. Désolé. La grosse différence entre mon implémentation et la tienne, c’est le constructeur ! Et ça fait toute la différence. En fait, je paramètre ma classe par ma sous-classe. Ce qui implique que je doive en avoir une. Pas dans ton implémentation puisque tu passe par un argument de constructeur que Spring peut fournir ! C’est nickel ! Le détail qui tue 😉 Surtout que Spring charge tout seul la classe d’après le nom qualifié de la config. C’est énorme. Merci bcp !

  • Mimie 18 août 2010, 22 10

    De rien 🙂 ravi de t’avoir débloqué ^^

  • chamyouti 11 octobre 2010, 3 03

    J’ai essayé de suivre ton tutorial mais j’ai une exception qui me bloque, pourrais-tu stp m’aider à trouver la cause ?
    Ce que j’ai rajouté à ton applicationContext est l’appel à l’action struts

    Il m’affiche l’erreur suivante :
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘/login’ defined in ServletContext resource [/WEB-INF/classes/SpringBeans.xml]: Cannot resolve reference to bean ‘clientService’ while setting bean property ‘clientService’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘clientService’ defined in ServletContext resource [/WEB-INF/classes/SpringBeans.xml]: Cannot resolve reference to bean ‘clientServiceTarget’ while setting bean property ‘target’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘clientServiceTarget’ defined in ServletContext resource [/WEB-INF/classes/SpringBeans.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMethodException: Couldn’t find an init method named ‘init’ on bean with name ‘clientServiceTarget’

  • Stephane 11 octobre 2010, 8 08

    Quand à moi, j’ai une question qui me trotte dans la tête, que j’ai posé sur le forum de Spring, mais qui ne semble pas avoir intéressé grand monde là-bas.

    En voila l’url, peut-être que certains ici la trouveront plus intéressante.

    http://forum.springsource.org/showthread.php?t=95674

    C’est à cause d’un test unitaire que j’en suis arrivé là.

    http://forum.springsource.org/showthread.php?t=95675

    En effet, j’aimerais bien avoir un test unitaire contre une constraint violation.

  • Mimie 11 octobre 2010, 10 10

    @chamyouti : dans mon exemple j’appelle une méthode init de mon service au démarrage du contexte Spring avec la ligne suivante :

    [xml]
    <bean id="cardServiceTarget" class="fr.geeks.magic.business.service.impl.CardServiceImpl"
    init-method="init">
    [/xml]

    Je pense que dans ton cas ce n’est pas le cas, ton service n’a pas de méthode init, donc il faut retirer la déclaration du init-method dans ta config Spring.
    Tiens moi au courant 🙂

  • Mimie 11 octobre 2010, 11 11

    @stephane : peut-être devrais-tu tester unitairement tes services qui engloberaient les accès en base avec tes DAO, car la transaction doit se trouver au niveau des services et non des DAO.

  • Stephane 11 octobre 2010, 11 11

    @Mimie : Cela veut il dire que lorsque c’est Spring qui est censé gérer les transactions, il ne peut le faire dans un Dao, mais a besoin d’un service ? Merci.

  • chamyouti 12 octobre 2010, 2 02

    @Mimie, je te remercie pour ta réponse.

    Pourrais-tu me passer ton adr mail, je t’envoie le projet pour que ça plus claire pour toi ?
    Merci

  • Mimie 12 octobre 2010, 9 09

    @chamyouti : j’ai supprimé ton message car il est incompréhensible et la moitié des fichiers que tu as copié-collé ne sont pas passés, utilise les options [plain] [xml] … [/xml] ou [java] … [/java] [/plain] pour que ce soit plus compréhensible.
    De plus j’ai l’impression que ton problème ne correspond pas du tout avec l’article, je me trompe ?

  • intelo 4 janvier 2011, 21 09

    la persistance=DAO+les classes model.
    service métier=les classes de service

  • Mimie 4 janvier 2011, 21 09

    @intelo : dans l’article je ne définissais pas ce qu’était la persistance ou les services métiers mais uniquement les beans spring qui sont crées dans ces parties au niveau des fichiers de configuration xml.

  • pvz 22 février 2011, 22 10

    Salut,

    j’ai testé ta dao générique, ca doit très bien marcher avec une base de données oracle ou mySql, mais moi j’essaie de mettre ca en place avec une bdd java emportée HSQLDB, j’ai configuré ma datasource à la place de la tienne en changeant driver et url, et j’arrive à lire ce qu’il y a dans la bdd, mais pas à persister.
    La datasource est configurée dans le context spring xml, la je vois plus trop comment m’en sortir.
    Est ce que quelqu’un a déjà fait ca et à une idée ?

    merci,
    Franck

  • Mimie 22 février 2011, 22 10

    @pvz : peux-tu nous donner tes paramètres de connexion à ta base ? car il semblerai que selon l’url les données ne sont plus persistées mais uniquement stockées en mémoire vive (ex: « jdbc:hsqldb:mem:aname »).

  • pvz 22 février 2011, 22 10

    ouais j’ai lu ca aussi, je poste ca demain je l’ai pas sous les yeux
    mais il me semble de memoire que c’est un truc du genre :

    jdbc:hsqldb:hsql://localhost

  • Mimie 23 février 2011, 10 10

    Il faut que tu utilises les balises

    <pre class="brush: xml"> ... </pre>

    (sans espace dans les crochets) ou autre langage lorsque tu souhaites insérer du code, espérons que ça marche.

  • franck 23 février 2011, 10 10
    <bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver" />		
    <property name="url" value="jdbc:hsqldb:hsql://localhost" />
    <property name="username" value="sa" />
    <property name="password" value="" />
    </bean>
    
  • pvz 23 février 2011, 9 09

    rebonjour, bien dormi tout le monde 🙂 ?

    voici le code exact :

  • yvan 8 mars 2012, 16 04

    Bonjour
    j’utilise votre dao generique et j’aimerai savoir si il faut rajouter les methodes des tous les champs et les collections ?
    cordialement
    Yvan

  • ameniiii 30 mars 2012, 12 12

    SVP comment je peux tester le dao générique avec Junit???? Réponse SVP

  • jcy29 25 mars 2013, 9 09

    Bonjour,
    j’ai voulu mettre en place un DAO généric sur la même base. J’ai deux objets que je manipule Region et Pays et donc logiquement j’ai crée les classes correspondantes public class RegionDAOImpl extends GenericDAOImpl implements IRegionDAO et public class PaysDAOImpl extends GenericDAOImpl implements IPaysDAO, mais j’ai une erreur de ce type :
    The interface IGenericDAO cannot be implemented more than once with different arguments: IGenericDAO and
    IGenericDAO
    Avez vous une idée?
    Merci d’avance

  • Mimie 25 mars 2013, 10 10

    Tu es sûr d’utiliser « extends » et pas « implements » ?

  • jcy29 25 mars 2013, 11 11

    dsl j’avais une erreur de classe J’ai resolu ce problème, mais je fais fasse à un autre.
    Je m’explique:
    J’ai deux enités Region et Pays, deux DAO qui héritent du DAO générique, et donc aussi deux services pour faire le lien avec la couche présentation.
    Mais à chaque fois que je vais sauvegarder une entité, spring va ouvrir une session hibernate vers la base de donnée. Est ce qu’il y a un moyen de créer une seule session durant l’application?
    Typiquement j’ai ceci :
    ApplicationContext appContext = new ClassPathXmlApplicationContext(« applicationContext.xml »);
    IRegionService regionBO=(IRegionService)appContext.getBean(« myRegionService »);
    IPaysService paysBO=(IPaysService)appContext.getBean(« myPaysService »);
    Region r=new Region();
    r.setNom(« EUROPE »);
    Pays p=new Pays();
    p.setNom(« Belgique »);
    p.setNomRegion(r);
    r.getLesPays().add(p);
    regionBO.saveRegion(r);
    paysBO.savePays(p);

    et dans mes logs je vois bien qu’il crée une première session pour sauvegarder la région, puis une deuxième pour le pays.

  • Mimie 25 mars 2013, 23 11

    Je ne vois aucun problème à ce qu’Hibernate crée une session par requête en bdd, j’ai l’impression que tu souhaites plutôt parler de transaction non ?

  • jcy29 26 mars 2013, 10 10

    Oui c’est de transaction dont je voulais parler, la parade que j’ai trouvée est d’utiliser une methode qui me fait appel à mes deux méthodes du dao, ce qui fait que cela se fera dans une même transaction.

  • Mimie 26 mars 2013, 11 11

    Oui tu te crées un service qui porte la transaction et qui fait les appels à ta bdd, comme ça le commit ou le rollback se feront sur l’ensemble des actions en base qui auront été faites par ce service.

  • Julien 16 avril 2013, 14 02

    C’est bien, sauf qu’on ne peut pas utiliser GenericDaoImpl dans une couche service ou un controlleur java en utilisant l’annotation @autowired : il n’y a pas de constructeur par défaut à GenericDaoImpl.

  • Youness 3 novembre 2016, 20 08

    Bonjour,
    Merci pour cette article il ma aider enormement lors de la creation de mom projet
    Ma question c’est par exmple j’ai un dao poir salarie qui contient les requete propre a salarie
    Si par exemple j’ai une autre entite telephone. La relation entre salarie et telehpone ont to many dans mon cas pour avoir la liste du telephone je peut faire salarie.getlisttelephone
    Dans le controlleur je v loader le salarie apartir de service salarie et lui meme va aller chercher l’info a partir de dao get salarie by login
    Le code :
    Salarie objetsalarie = salarieservicr.getsalariebylogin(login)
    Donc la lorsque je v executer salarie.getlistphone
    J’ai une exception lazyinitexeption c’est normal car la transaction et ferme
    A votre avis comment je peut regler ce probleme ?
    Est ce que dans salarie dao je doit crer une methode qui retoune la list de telephone en pass dans le parametre le salarie
    Ou je doit appeler le service de la class telehpone et crée une methode getlisttelephonebyid salarie ?

Article suivant:

Article précédent:

Share This