Simplifiez le DAO avec Spring et Java Generics

1. Vue d'ensemble

Cet article se concentrera sur la simplification de la couche DAO en utilisant un seul objet d'accès aux données généré pour toutes les entités du système, ce qui entraînera un accès aux données élégant, sans encombrement ni verbosité inutiles.

Nous allons construire sur la classe Abstract DAO que nous avons vue dans notre article précédent sur Spring et Hibernate, et ajouter le support des génériques.

2. Les DAO Hibernate et JPA

La plupart des bases de code de production ont une sorte de couche DAO. Habituellement, l'implémentation va de plusieurs classes sans classe de base abstraite à une sorte de classe générée. Cependant, une chose est cohérente - il y en a toujours plus d'une . Très probablement, il existe une relation un à un entre les DAO et les entités du système.

En outre, selon le niveau de génériques impliqués, les implémentations réelles peuvent varier d'un code fortement dupliqué à presque vide, la majeure partie de la logique étant regroupée dans une classe abstraite de base.

Ces multiples implémentations peuvent généralement être remplacées par un seul DAO paramétré. Nous pouvons implémenter cela de telle sorte qu'aucune fonctionnalité ne soit perdue en tirant pleinement parti de la sécurité de type fournie par Java Generics.

Nous montrerons ensuite deux implémentations de ce concept, l'une pour une couche de persistance centrée Hibernate et l'autre axée sur JPA. Ces implémentations ne sont en aucun cas complètes, mais nous pouvons facilement ajouter d'autres méthodes d'accès aux données supplémentaires.

2.1. Le DAO abstrait Hibernate

Jetons un coup d'œil à la classe AbstractHibernateDao :

public abstract class AbstractHibernateDao { private Class clazz; @Autowired SessionFactory sessionFactory; public void setClazz(Class clazzToSet){ this.clazz = clazzToSet; } public T findOne(long id){ return (T) getCurrentSession().get(clazz, id); } public List findAll() { return getCurrentSession().createQuery("from " + clazz.getName()).list(); } public T create(T entity) { getCurrentSession().saveOrUpdate(entity); return entity; } public T update(T entity) { return (T) getCurrentSession().merge(entity); } public void delete(T entity) { getCurrentSession().delete(entity); } public void deleteById(long entityId) { T entity = findOne(entityId); delete(entity); } protected Session getCurrentSession() { return sessionFactory.getCurrentSession(); } }

Il s'agit d'une classe abstraite avec plusieurs méthodes d'accès aux données, qui utilise la SessionFactory pour manipuler les entités.

2.2. Le DAO Hibernate générique

Maintenant que nous avons la classe DAO abstraite, nous ne pouvons l'étendre qu'une seule fois. L'implémentation générique DAO deviendra la seule implémentation dont nous aurons besoin:

@Repository @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class GenericHibernateDao extends AbstractHibernateDao implements IGenericDao{ // }

Tout d'abord, notez que l'implémentation générique est elle-même paramétrée , ce qui permet au client de choisir le bon paramètre au cas par cas. Cela signifie que les clients bénéficient de tous les avantages de la sécurité des types sans avoir à créer plusieurs artefacts pour chaque entité.

Deuxièmement, notez la portée du prototype de cette implémentation générique de DAO . L'utilisation de cette étendue signifie que le conteneur Spring créera une nouvelle instance du DAO à chaque fois qu'il sera demandé (y compris lors du câblage automatique). Cela permettra à un service d'utiliser plusieurs DAO avec des paramètres différents pour différentes entités, selon les besoins.

La raison pour laquelle cette étendue est si importante est due à la façon dont Spring initialise les beans dans le conteneur. Laisser le DAO générique sans portée signifierait utiliser la portée singleton par défaut, ce qui conduirait à une seule instance du DAO vivant dans le conteneur . Ce serait évidemment très restrictif pour tout type de scénario plus complexe.

Le IGenericDao est simplement une interface pour toutes les méthodes de DAO afin que nous puissions Injecter la mise en œuvre dont nous avons besoin:

public interface IGenericDao { T findOne(final long id); List findAll(); void create(final T entity); T update(final T entity); void delete(final T entity); void deleteById(final long entityId); }

2.3. Le résumé JPA DAO

Le AbstractJpaDao est très similaire au AbstractHibernateDao:

public abstract class AbstractJpaDao { private Class clazz; @PersistenceContext EntityManager entityManager; public void setClazz( Class clazzToSet ) { this.clazz = clazzToSet; } public T findOne( Long id ){ return entityManager.find( clazz, id ); } public List findAll(){ return entityManager.createQuery( "from " + clazz.getName() ) .getResultList(); } public void save( T entity ){ entityManager.persist( entity ); } public void update( T entity ){ entityManager.merge( entity ); } public void delete( T entity ){ entityManager.remove( entity ); } public void deleteById( Long entityId ){ T entity = getById( entityId ); delete( entity ); } }

Semblable à l'implémentation Hibernate DAO, nous utilisons directement l'API Java Persistence, sans compter sur le Spring JpaTemplate désormais obsolète .

2.4. Le générique JPA DAO

Semblable à l'implémentation Hibernate, l'objet d'accès aux données JPA est également simple:

@Repository @Scope( BeanDefinition.SCOPE_PROTOTYPE ) public class GenericJpaDao extends AbstractJpaDao implements IGenericDao{ // }

3. Injection de ce DAO

Nous avons maintenant une seule interface DAO que nous pouvons injecter. Nous devons également spécifier la classe:

@Service class FooService implements IFooService{ IGenericDao dao; @Autowired public void setDao(IGenericDao daoToSet) { dao = daoToSet; dao.setClazz(Foo.class); } // ... }

Spring transfère automatiquement la nouvelle instance DAO à l'aide de l'injection setter afin que l'implémentation puisse être personnalisée avec l' objet Class . Après ce point, le DAO est entièrement paramétré et prêt à être utilisé par le service.

Il existe bien sûr d'autres manières de spécifier la classe pour le DAO - via la réflexion, ou même en XML. Ma préférence va à cette solution plus simple en raison de la lisibilité et de la transparence améliorées par rapport à l'utilisation de la réflexion.

4. Conclusion

Cet article traite de la simplification de la couche d'accès aux données en fournissant une implémentation unique et réutilisable d'un DAO générique. Nous avons montré l'implémentation dans un environnement Hibernate et JPA. Le résultat est une couche de persistance simplifiée, sans encombrement inutile.

Pour une introduction étape par étape sur la configuration du contexte Spring à l'aide de la configuration basée sur Java et du pom Maven de base pour le projet, consultez cet article.

Enfin, le code de cet article se trouve dans le projet GitHub.