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 :
- le pattern inversion de contrôle (ou IOC)
- la programmation orientée aspect (ou AOP)
- 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 :
public interface GenericDao<T, PK extends Serializable> {
PK create(T newInstance);
T read(PK id);
List<T> readAll();
void update(T transientObject);
void delete(T persistentObject);
}
- 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 :
public class GenericDaoImpl<T, PK extends Serializable> implements GenericDao<T, PK> {
private SessionFactory sessionFactory;
private Class<T> type;
public GenericDaoImpl(Class<T> type) {
this.type = type;
}
public PK create(T o) {
return (PK) getSession().save(o);
}
public T read(PK id) {
return (T) getSession().get(type, id);
}
public List<T> readAll() {
Criteria crit = getSession().createCriteria(type);
return crit.list();
}
public void update(T o) {
getSession().update(o);
}
public void delete(T o) {
getSession().delete(o);
}
public Session getSession() {
boolean allowCreate = true;
return SessionFactoryUtils.getSession(sessionFactory, allowCreate);
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
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 :
public interface CardDao extends GenericDao<Card, Integer> {
/**
* Récupération de tous les artistes différents
* @return
* @throws DataAccessException
*/
List<String> selectAllArtists() throws DataAccessException;
}
- Son implémentation, désormais les méthodes create, read, update, delete ainsi que selectAllArtists sont accessibles :
public class CardDaoImpl extends GenericDaoImpl<Card, Integer> implements CardDao {
/**
* @param type
*/
public CardDaoImpl(Class<Card> type) {
super(type);
}
/* (non-Javadoc)
* @see fr.geeks.magic.domain.dao.CardDao#selectAllArtists()
*/
public List<String> selectAllArtists() throws DataAccessException {
Query query = getSession().createQuery("select distinct(artist) from Card where artist not like '(none)' order by artist");
List<String> artists = query.list();
return artists;
}
}
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
<!-- ========================= Start of PERSISTENCE DEFINITIONS ========================= --> <!-- DataSource Definition via JNDI --> <bean id="dbMagicWeb" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName" value="java:comp/env/jdbc/dbMagicWeb" /> </bean> <!-- Hibernate SessionFactory Definition --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="mappingResources"> <list> <value>fr/geeks/magic/domain/model/Card.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.bytecode.use_reflection_optimizer">true</prop> <prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop> </props> </property> <property name="dataSource" ref="dbMagicWeb" /> </bean> <!-- Hibernate Transaction Manager Definition --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
- 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
<!-- ========================= Start of DAO DEFINITIONS ========================= --> <!-- proxy for DAO using generic DAO --> <bean id="proxyDAO" abstract="true"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- Card Dao definition --> <bean id="cardDao" class="fr.geeks.magic.domain.dao.hibernate.CardDaoImpl" parent="proxyDAO"> <constructor-arg value="fr.geeks.magic.domain.model.Card" /> </bean>
- Partie Service : proxy pour les transactions automatiques sur nos services métier, injection des DAO au service
<!-- ========================= Start of SERVICE DEFINITIONS ========================= --> <!-- Transactional proxy for Services --> <bean id="proxyService" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="find*">PROPAGATION_REQUIRED, readOnly</prop> <prop key="get*">PROPAGATION_REQUIRED, readOnly</prop> <prop key="*">PROPAGATION_REQUIRED, -java.lang.Exception</prop> </props> </property> </bean> <!-- Card service target --> <bean id="cardServiceTarget" class="fr.geeks.magic.business.service.impl.CardServiceImpl" init-method="init"> <property name="cardDao" ref="cardDao" /> </bean> <!-- Card service --> <bean id="cardService" parent="proxyService"> <property name="target" ref="cardServiceTarget" /> </bean>
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.




Affichez votre portrait
{ 30 commentaires… à vous de vous exprimer ! }
Hi all ! Toujours pas de réponse à la question posée ? :’
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
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.
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
Voilà les sources sont disponibles dans la section « Sources » de l’article, j’espère que ça aidera
Merci beaucoup Greg pour les sources. Cela m’aidera beaucoup
Oups, ‘tite erreur Maribo, tout le mérite revient à Mimie c’est lui le programmeur du blog
Merci à lui !
Oups, oui désolé. j’ai dû écrire un peu trop vite. Je voulais dire Merci 1000 fois à Mimie. Bonne soirée
De rien, à ta prochaine visite
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 !
@Stephane : tu as tout à fait raison, je me suis mélangé les pinceaux, moi et les termes anglais ça fait deux
, merci bien ^^
Article corrigé.
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+
@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
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 !
De rien
ravi de t’avoir débloqué ^^
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’
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.
@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
@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.
@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.
@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
@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 ?
la persistance=DAO+les classes model.
service métier=les classes de service
@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.
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
@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 »).
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
Il faut que tu utilises les balises
(sans espace dans les crochets) ou autre langage lorsque tu souhaites insérer du code, espérons que ça marche.
rebonjour, bien dormi tout le monde
?
voici le code exact :