将接口与通用抽象DAO结合使用?

时间:2011-08-28 15:26:38

标签: java interface abstract-class

我有一个BaseRepository泛型抽象类,其中包含一些可以与JPA一起使用的有趣方法。最近,我已经养成了针对预定义接口编写DAO和服务的习惯。

有没有办法将这两者结合起来?

换句话说,我怎样才能在实现接口的同时扩展抽象类?

经过实验,我得出以下结论:

  • 让抽象类实现接口不会强制执行方法(可能有很好的理由)。
  • 使用Generics扩展抽象类需要您定义类型参数,即使已在接口中指定了类型。

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();

}

1 个答案:

答案 0 :(得分:1)

如上所述,没有问题。实际上,这种模式在几个框架中使用(如Spring)。您应该将名称从BaseRepository更改为类似AbstractSingleIdRepository的名称。前缀“Abstract”表示模板类。

此外,在公共接口上公开常用方法并使抽象类实现该接口可能是个好主意。另外,子类可以实现自己的自定义接口。

其他一些评论:

  1. 在findAll上:“选择不同”你确定吗?这适用于所有实体吗?
  2. 删除:不确定是否要提供删除任何实体的直接访问权限。可能添加一个受保护的抽象“canBeDeleted”来检查实例应该在物理上或逻辑上删除的时候都可以解决问题(一般来说你应该不删除数据)