Использование интерфейсов в сочетании с общим DAO asbtract? - PullRequest
0 голосов
/ 28 августа 2011

У меня есть базовый абстрактный класс BaseRepository с некоторыми интересными методами, которые можно использовать с JPA. Позже у меня появилась привычка писать DAO и службы для предопределенных интерфейсов.

Есть ли способ, которым я мог бы объединить два?

Другими словами, как я могу добиться того, чтобы конкретный класс 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 Ответ

1 голос
/ 04 декабря 2011

Как объяснено, проблем нет. На самом деле, этот шаблон используется в нескольких средах (например, Spring). Вы должны изменить имя BaseRepository на что-то вроде AbstractSingleIdRepository. Приставка «Абстрактный» обозначает шаблон класса.

Кроме того, было бы неплохо представить общие методы в общем интерфейсе и заставить абстрактный класс реализовать этот интерфейс. Кроме того, подклассы могут реализовывать свои собственные пользовательские интерфейсы.

Некоторые дополнительные комментарии:

  1. На findAll: "Выбрать отличное" вы уверены? будет ли это применяться ко всем организациям?
  2. удалить: не уверен, хотите ли вы предоставить прямой доступ для удаления любого объекта. Вероятно, добавление защищенного абстрактного «canBeDeleted» для проверки всякий раз, когда экземпляр должен быть физически или логически удален, поможет (как правило, вы не должны удалять данные)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...