James P.
James P.

Reputation: 19617

Using interfaces in combination with a generic asbtract DAO?

I have a BaseRepository generic abstract class with some interesting methods which can be used with JPA. More lately I've gotten into the habit of writing DAOs and Services against predefined interfaces.

Is there a way I could combine the two?

In other words, how could I go about having a concrete DAO class extend the abstract class while also implementing the interface?

Having experimented a bit I've come to the following conclusions:


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

}

Upvotes: 0

Views: 1435

Answers (1)

Alejandro Diaz
Alejandro Diaz

Reputation: 431

As explained there is no problem. Actually, this pattern is used in several frameworks (like Spring). You should change the name from BaseRepository to something like AbstractSingleIdRepository. The prefix "Abstract" denotes a template class.

Additionally it might be a good idea to expose the common methods on a common interface and make the abstract class implement that interface. Additionally, subclasses can implement their own custom interfaces.

Some additional comments:

  1. On findAll: "Select distinct" are you sure? will this apply for all the entities?
  2. remove: not sure if you want to provide a direct access to remove any entity. Probably adding a protected abstract "canBeDeleted" to check whenever an instance should be physically or logically deleted will do the trick (as general rule you should probably not delete data)

Upvotes: 1

Related Questions