Spring Framework: El patrón DAO

Spring Framework: El patrón DAO
Facebook Twitter Flipboard E-mail

Se que muchos estáis esperando la serie de Spring, aquí la tenéis. En esta segunda parte, voy a hablaros de el patrón de diseño DAO. DAO es un acrónimo de Data Access Object. No es algo que haya inventado Spring, pertenece al catálogo de Core J2EE Patterns.

En esta serie voy a ir construyendo una aplicación con Spring Framework. La arquitectura va a ser muy sencilla: capa de acceso a datos, capa de negocio y capa de presentación. Vamos a desarrollar la aplicación al igual que se construyen los edificios, desde los cimientos hasta el tejado. Asi que empecemos por la capa de acceso a los datos.

Software necesario

Para este artículo he usado el siguiente software:

  • Apache Maven 3.0.3

  • Eclipse Indigo (3.7)

  • m2eclipse (plugin de maven para Eclipse)

Contexto

En una aplicación J2EE necesitamos acceder a datos, ya sea por persistencia (hibernate, jdo, iBatis…), jdbc, ficheros de texto, LDAP, etc…. Esto puede suponer un problema, pues la forma de acceder a los datos depende del fabricante y del tipo de almacenamiento que estamos accediendo.

Los componentes de nuestra aplicación deben ser transparentes en la medida de lo posible al actual sistema de persistencia o fuente de datos para permitir migraciones entre distintos fabricantes , distintos tipos de almacenamiento y diferentes fuentes de datos. Supongamos que en un momento dado queremos cambiar el motor de persistencia. Siguiendo este módelo será mucho mas fácil.

Solución

El patrón DAO viene a resolver este problema usando un Objeto de Acceso a Datos para abstraer y encapsular el acceso a los datos. Un DAO maneja la conexión con la fuente de datos para obtener y guardar los datos.

Un DAO siempre realiza operaciones atómicas contra la base de datos, nunca son necesarias las transacciones. Claros ejemplos de esto serían busquedas por una clave, creación, actualización y borrado de un registro, obtener todos los registros y cualquier otra operación que vayamos a realizar muy a menudo.

Normalmente se crea un DAO por cada Objeto que usemos en nuestra aplicación. Objeto puede ser muchas cosas, según estemos usando puro jdbc o un framework de persistencia. En el primer caso podria ser una tabla y en el segundo un Entity de Hibernate.

Creación del proyecto

Vamos a crear un proyecto java simple, empaquetado como jar, por motivos de portabilidad. En este proyecto, tendremos todas las clases DAO. Para ello usaremos Maven:

mvn archetype:create -DgroupId=com.genbetadev.spring -DartifactId=spring-sample-dao

Despues, entramos en la carpeta del proyecto para convertirlo en proyecto de eclipse:

mvn eclipse:eclipse

De esta manera ya podemos importarlo en Eclipse. Este paso no sería necesario si tenemos instalado el plugin m2eclipse, pues se puede importar como proyecto de maven.

Una vez creado, lo importamos en Eclipse e introducimos las siguientes dependencias en el pom.xml:

<dependency>
    <groupid>org.springframework</groupid>
    <artifactid>spring-orm</artifactid>
    <version>3.0.5.RELEASE</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupid>org.hibernate</groupid>
    <artifactid>hibernate-core</artifactid>
    <version>3.6.5.Final</version>
</dependency>

El siguiente paso será crear la base de todos nuestros DAOs. He añadido la dependencia de spring-orm porque contine el api de spring necesario para manejar la persistencia. Crearemos una interfaz que todos nuestros DAOs implementarán y una implementación por defecto que todos extenderán.

El motivo por el cual añadir la dependencia con Hibernate, es porque es el framework de persistencia que he elegido para desarrollar nuestro ejemplo.

Creación de la interfaz: BaseDAO

Vamos a crear una interfaz muy típica, que luego se puede ampliar si es necesario:

public interface BaseDao<T extends Serializable, E> {
    public void deleteAll(Collection<T> instances) throws Exception;
    public int bulkUpdate(String query) throws Exception;
    public E save(T instance) throws Exception;
    public void saveOrUpdateAll(Collection<T> instances) throws Exception;
    public void saveOrUpdate(T instance) throws Exception;
    public void persist(T transientInstance) throws Exception;
    public void attachDirty(T instance) throws Exception;
    public void attachClean(T instance) throws Exception;
    public void delete(T persistentInstance) throws Exception;
    public List<T> findByExample(T instance) throws Exception;
    public List<T> findByQuery(String query) throws Exception;
    public List<Map<String, Object>> findMapByQuery(String queryString) throws Exception;
    public List<T> findByCriteria(DetachedCriteria criteria) throws Exception;
    public T merge(T detachedInstance) throws Exception;
    public List<T> findAll() throws Exception;
    public T findById(E id) throws Exception;
}

Hemos creado un interfaz con operaciones típicas de consulta, actualización y creación de datos.

¿Que es T y que es E? T, es el tipo del objeto mientras que E es el tipo del identificador. T es una clase java, un entity de Hibernate en nuestro caso.

Implementación con Hibernate

Vamos a crear una implementación por defecto para trabajar con Hibernate. Para ello creamos una implementación abstracta que todos nuestros DAOs extenderán:

public abstract class BaseDaoHibernate<T extends Serializable, E> extends HibernateDaoSupport implements BaseDao<T, E> {
    public void deleteAll(final Collection<T> instances) throws Exception {
        try {
            getHibernateTemplate().deleteAll(instances);
        } catch (final Exception e) {
            throw e;
        }
    }
    public int bulkUpdate(final String query) throws Exception {
        try {
            return getHibernateTemplate().bulkUpdate(query);
        } catch (final Exception e) {
            throw e;
        }
    }
    public E save(final T instance) throws Exception {
        try {
            return (E) getHibernateTemplate().save(instance);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void saveOrUpdateAll(final Collection<T> instances) throws Exception {
        try {
            getHibernateTemplate().saveOrUpdateAll(instances);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void saveOrUpdate(final T instance) throws Exception {
        try {
            getHibernateTemplate().saveOrUpdate(instance);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void persist(final T transientInstance) throws Exception {
        try {
            getHibernateTemplate().persist(transientInstance);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void attachDirty(final T instance) throws Exception {
        try {
            getHibernateTemplate().saveOrUpdate(instance);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void attachClean(final T instance) throws Exception {
        try {
            getHibernateTemplate().lock(instance, LockMode.NONE);
        } catch (final Exception e) {
            throw e;
        }
    }
    public void delete(final T persistentInstance) throws Exception {
        try {
            getHibernateTemplate().delete(persistentInstance);
        } catch (final Exception e) {
            throw e;
        }
    }
    public T merge(final T detachedInstance) throws Exception {
        try {
            final T result = getHibernateTemplate().merge(detachedInstance);
            return result;
        } catch (final Exception e) {
            throw e;
        }
    }
    public List<T> findByExample(final T instance) throws Exception {
        try {
            final List<T> results = getHibernateTemplate().findByExample(instance);
            return results;
        } catch (final Exception e) {
            throw e;
        }
    }
    public List<T> findByQuery(final String queryString) throws Exception {
        try {
            final List<T> results = getHibernateTemplate().find(queryString);
            return results;
        } catch (final Exception e) {
            throw e;
        }
    }
    public List<Map<String, Object>> findMapByQuery(final String queryString) throws Exception {
        try {
            final List<Map<String, Object>> results = getHibernateTemplate().find(queryString);
            return results;
        } catch (final Exception e) {
            throw e;
        }
    }
    public List<T> findByCriteria(final DetachedCriteria criteria) throws Exception {
        try {
            return getHibernateTemplate().findByCriteria(criteria);
        } catch (final Exception e) {
            throw e;
        }
    }
    public abstract List<T> findAll() throws Exception;
    public abstract T findById(E id) throws Exception;
}

¿Que es HibernateDaoSupport? es simplemente una plantilla para crear un DAO que use Hibernate. Nos permite inyectar en nuestros DAOs, o nuestro SessionFactory de Hibernate o una instancia de HibernateTemplate. Examinemos un fragmento del código de HibernateDaoSupport:

public abstract class HibernateDaoSupport extends DaoSupport {
    private HibernateTemplate hibernateTemplate;
    public final void setSessionFactory(SessionFactory sessionFactory) {
        if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) {
            this.hibernateTemplate = createHibernateTemplate(sessionFactory);
        }
    }
    protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) {
        return new HibernateTemplate(sessionFactory);
    }
    public final void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }
}

Como podéis observar esta clase solo tiene una propiedad, hibernateTemplate. Pero disponemos de dos setters, uno para el session factory y otro para HibernateTemplate. HibernateTemplate es otra plantilla, que utiliza el patrón facade, para hacer operaciones sobre hibernate como podéis ver en el javadoc de la clase. Básicamente, esta plantilla nos abstrae de pelearnos con Hibernate, de abrir la sesión, realizas ciertas operaciones y cerrarla que muchas veces induce a cometer errores. Así pues, podemos inyectar un SessionFactory o una instancia de HibernateTemplate. Yo suelo inyectar una instancia de HibernateTemplate, ya que en otro caso, tendría tantas instancias como DAOs tenga mi aplicación.

Ahora, echémosle un vistazo a un fragmento de código de nuestro BaseDaoHibernate:

    public List<Map<String, Object>> findMapByQuery(final String queryString) throws Exception {
        try {
            final List<Map<String, Object>> results = getHibernateTemplate().find(queryString);
            return results;
        } catch (final Exception e) {
            throw e;
        }
    }

Lo único que hacemos es obtener la plantilla y llamar a uno de sus métodos. Sencillo ¿no? Todos los métodos de nuestro DAO son igual, obtener la plantilla y llamar al método correspondiente.

Habréis observado que findAll y findById son abstract. Se implementarán en las clases hijas, ya veremos el motivo.

Conclusión

En este artículo hemos sentado las bases sobre las que construiremos nuestra capa de acceso a datos. En el siguiente, crearemos un esquema de datos, sus correspondientes DAO y configuraremos todas las dependencias con Spring.

Mas información: Introducción a Spring Framewok ¦ Spring reference manual
Código del ejemplo: spring-sample-dao

Comentarios cerrados
Inicio