2011-08-28 7 views
0

J'ai une classe abstraite générique BaseRepository avec quelques méthodes intéressantes qui peuvent être utilisées avec JPA. Plus récemment, j'ai pris l'habitude d'écrire des DAO et des services par rapport à des interfaces prédéfinies.Utilisation d'interfaces en combinaison avec un DAO asbtract générique?

Y a-t-il un moyen de combiner les deux? En d'autres termes, comment pourrais-je faire en sorte qu'une classe DAO concrète étende la classe abstraite tout en implémentant l'interface?

Après avoir expérimenté un peu, je suis venu aux conclusions suivantes:

  • Avoir la classe abstraite mettre en œuvre une interface ne pas appliquer les méthodes (il y a probablement une bonne raison pour cela).
  • L'extension d'une classe abstraite à l'aide de Generics nécessite de définir des arguments de type même si le type a été spécifié dans une interface.

import java.io.Serializable; 
import java.lang.reflect.ParameterizedType; 
import java.lang.reflect.Type; 
import java.util.List; 

import javax.persistence.EntityManager; 
import javax.persistence.NoResultException; 
import javax.persistence.PersistenceContext; 

import org.hibernate.mapping.PersistentClass; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Transactional; 

@Transactional 
@Service 
@SuppressWarnings("unchecked") 
public abstract class BaseRepository<E extends Identifiable<PK>, PK extends Serializable> { 

    /** 
    * Class corresponding to E. For example, if you define class BankRepository 
    * extends BaseRepository<Bank, Long>, then after BankRepository bankRep = 
    * new BankRepository(); bankRep.entityClass will be Bank.class. Assigned in 
    * the constructor. 
    */ 
    private Class<E> entityClass; 

    @PersistenceContext 
    protected EntityManager em; 

    // => 6.1.5.3. Introspection With Spring 

    // This constructor will probably be executed TWICE by Spring (for each 
    // repository): 
    // once for the real Repository, and once for instantiating a proxy. 
    public BaseRepository() { 
     // // We try to know, at runtime, the class associated to E, and put it 
     // in this.entityClass. 
     // 1. find someEntityRepositoryClass (== BankRepository.class) 
     Class someEntityRepositoryClass; // Your repository class, i.e. 
              // BankRepository (extending 
              // BaseRepository<Bank,Long>) 
     if (this.getClass().getSuperclass() == BaseRepository.class) { // We are 
                     // instantiated 
                     // without 
                     // CGLIB: 
                     // new 
                     // BankRepository() 
      someEntityRepositoryClass = this.getClass(); 
     } else { // Spring instantiates as CGLIB class 
        // BankRepository$$EnhancedByCGLIB$$de100650 extends 
        // BankRepository: 
      // new BankRepository$$EnhancedByCGLIB$$de100650() 
      Class cglibRepositoryClass = this.getClass(); 
      someEntityRepositoryClass = cglibRepositoryClass.getSuperclass(); 
     } 

     // 2. find the ancestor of BankRepository.class, which is 
     // BaseRepository<E, PK>.class 
     ParameterizedType baseRepositoryType = (ParameterizedType) someEntityRepositoryClass 
       .getGenericSuperclass(); 

     // 3. Extract the type of E (from BaseRepository<E, PK>.class) 
     Type entityTypeOne = (baseRepositoryType).getActualTypeArguments()[0]; 
     entityClass = (Class<E>) entityTypeOne; 
    } 

    public E find(final PK id) { 
     // TODO Validate.notNull(id, "The id cannot be null"); 
     return em.find(entityClass, id); 
    } 

    public E persist(final E entity) { 
     // TODO Validate.notNull(entity, "The entity cannot be null"); 
     em.persist(entity); 
     return entity; 
    } 

    public E merge(final E object) { 
     // TODO Validate.notNull(object, "The object cannot be null"); 
     return em.merge(object); 
    } 

    public List<E> findAll() { 
     return em.createQuery(
       "Select distinct e from " + getEntityName() + " e") 
       .getResultList(); 
    } 

    public List<E> findAll(final int first, final int max) { 
     return em.createQuery("Select e from " + getEntityName() + " e") 
       .setFirstResult(first).setMaxResults(max).getResultList(); 
    } 

    public List<E> findAll(final int max) { 
     return em.createQuery("Select e from " + getEntityName() + " e") 
       .setMaxResults(max).getResultList(); 
    } 

    public void remove(final PK id) { 
     em.remove(this.find(id)); 
    } 

    public void remove(final E entity) { 
     em.remove(entity); 
    } 

    public Class<E> getEntityClass() { 
     return entityClass; 
    } 

    /** 
    * Returns the name of the entity which is probably the same as the class 
    * name. But if on, the entity we used @Entity(name="..."), then the name of 
    * the entity is different. Useful for building query strings: 
    * "select w from Worker" where "Worker" is the entity name. CURRENT 
    * IMPLEMENTATION JUST RETURNS THE CLASSNAME!!! 
    */ 
    public String getEntityName() { 
     // how to get Configuration configuration? I'm afraid we'll have to know 
     // how JPA is initialized, 
     // and this class (BaseRepositoty) does not want to know that. 
     // for (PersistentClass pc : configuration.getClassMappings()) { 
     // if (pc.getMappedClass().equals(entityClass)) { 
     // return pc.getEntityName(); 
     // } 
     // } 
     // throw new 
     // IllegalStateException("entityClass not found in Hibernate configuration. EntityClas=["+entityClass+"]"); 

     // Simplistic result in the meanwhile ;-) 
     return entityClass.getSimpleName(); 
    } 

    protected EntityManager getEntityManager() { 
     return em; 
    } 

    public void setEntityManager(final EntityManager entityManager) { 
     this.em = entityManager; 
    } 
} 

public interface Identifiable<K> { 
     K getId(); 

} 
+0

Je ne suis pas sûr d'avoir identifié la question est, mais une classe abstraite n'est pas forcée d'implémenter les méthodes de l'interface qu'elle implémente précisément parce qu'elle est abstraite, et peut donc avoir des méthodes non implémentées que des sous-classes concrètes devront implémenter. –

+0

Merci pour l'explication. Je pensais que c'était quelque chose dans ce sens. En ce qui concerne la question, j'aimerais continuer à utiliser des interfaces pour que plusieurs implémentations DAO soient possibles (Hibernate/JPA, iBatis, XML ...) mais je voudrais aussi utiliser le GenericDAO ci-dessus pour JPA. Cela arrive-t-il aussi clairement? –

+3

Pas de problème. Définissez une interface pour votre DAO spécifique et implémentez une classe concrète qui implémente cette interface et étend la classe BaseRepository abstraite. Cela ne causera aucun problème, sauf si la classe abstraite et l'interface ont des méthodes conflictuelles (par exemple: des méthodes avec la même signature mais des types de retour différents, ou la même méthode mais avec une exception différente levée) –

Répondre

1

Comme expliqué il n'y a pas de problème. En fait, ce modèle est utilisé dans plusieurs frameworks (comme Spring). Vous devriez changer le nom de BaseRepository en quelque chose comme AbstractSingleIdRepository. Le préfixe "Abstract" indique une classe de modèle.

De plus, il peut être judicieux d'exposer les méthodes courantes sur une interface commune et de faire en sorte que la classe abstraite implémente cette interface. De plus, les sous-classes peuvent implémenter leurs propres interfaces personnalisées.

Quelques commentaires supplémentaires:

  1. Sur findAll: "Choisissez distinct" êtes-vous sûr? Est-ce que cela s'appliquera à toutes les entités? Remove: vous ne savez pas si vous voulez fournir un accès direct pour supprimer une entité. Probablement ajouter un résumé protégé "canBeDeleted" pour vérifier chaque fois qu'une instance devrait être supprimée physiquement ou logiquement fera l'affaire (en règle générale vous ne devriez probablement pas supprimer les données)
Questions connexes