Herramientas de usuario

Herramientas del sitio


unidades:07_arquitectura:03_dao

DAO

Pasemos ahora a ver cómo implementar el patrón DAO usando Hibernate y de forma que sea fácilmente reutilizable.

El mayor problema del patrón DAO es la repetición de código para cada una de las entidades. Para evitar dicha repetición y obtener una gran flexibilidad en la implementación del objeto DAO se va a usar un modelo de clases e interfaces un poco complejo, pero una vez entendido cómo funciona será muy sencillo crear nuevos objetos DAO o cambiar su implementación.

Los interfaces

El siguiente diagrama UML explica los interfaces ProfesorDAO y UsuarioDAO.

Clase Generica con Tipos T e ID«interface»GenericDAOT create()void saveOrUpdate(T entity)T get(ID id)void delete(ID id)List<T> findAll()«interface»ProfesorDAO«interface»UsuarioDAO

Como podemos ver, los objetos DAO de Profesor y Usuario son realmente los interfaces ProfesorDAO y UsuarioDAO ya que de esa forma podremos fácilmente tener varias implementaciones para cada interfaz DAO. Para evitar definir los mismos métodos en cada interfaz DAO se ha creado un interfaz padre llamado GenericDAO del que heredan ProfesorDAO y UsuarioDAO.

El código Java relativo a los 3 interfaces es el siguiente:

1 | GenericDAO.java
public interface GenericDAO<T,ID extends Serializable> {
    T create() throws BussinessException;
    void saveOrUpdate(T entity) throws BussinessException;
    T get(ID id) throws BussinessException;
    void delete(ID id) throws BussinessException;
    List<T> findAll() throws BussinessException;
}
1 | ProfesorDAO.java
public interface ProfesorDAO extends GenericDAO<Profesor,Integer> {
 
}
1 | UsuarioDAO.java
public interface UsuarioDAO extends GenericDAO<Usuario,Integer>  {
 
}

Pasemos ahora a explicar cada uno de los métodos de GenericDAO:

  • T create(): Crea un nuevo objeto de la entidad.
  • void saveOrUpdate(T entity) : Inserta o actualiza un objeto de una entidad en la base de datos.
  • T get(ID id) : Obtiene un objeto de una entidad desde la base de datos en función de la clave primaria.
  • void delete(ID id) : Borra un objeto de una entidad de la base de datos en función de la clave primaria.
  • List<T> findAll() : Obtiene una lista con todos los objetos de una entidad de la base de datos.
Los métodos que tiene el interfaz GenericDAO son un posible ejemplo de métodos para un objeto DAO. El programador puede diseñar el objeto DAO de la forma que más le interese,como por ejemplo:
  • Separando el método void saveOrUpdate(T entity) en dos distintos, uno para la inserción y otro para la actualización.
  • Haciendo que el método T get(ID id) permita bloquear el objeto para que nadie lo pueda modificar.
  • Etc.

Un GenericDAO con muchos mas métodos lo podemos encontrar en Spring Framework: El patrón DAO

En los interfaces ProfesorDAO y UsuarioDAO será normal añadir, entre otros, más métodos find para personalizar los métodos de búsqueda de entidades.

Por ejemplo en UsuarioDAO sería útil añadir el método Usuario findByLogin(String login) que nos permitiría obtener un usuario en función de su login en vez de en función de su idUsuario tal y como hace T get(ID id).

Tambien en ProfesorDAO sería útil añadir el método List<Profesor> findByNombreAndApe1AndApe2(String nombre,String ape1,String ape2) que nos permitiría obtener los usuarios que tengan el nombre y el 1º apellido y el 2º apellido en vez de obtener todos los profesores como hace List<T> findAll()

La implementación

Como ya hemos comentado la ventaja de usar el patrón DAO es poder tener varias implementaciones del mismo interfaz DAO de forma que podamos cambiar la funcionalidad de una forma sencilla pero también es necesario que no se repita el código entre las distintas implementaciones.

Para reutilizar el código se ha creado la clase GenericDAOImplHibernate que implementa de forma genérica el acceso a la base de datos mediante Hibernate. De esta clase heredarán las implementaciones de cada interfaz DAO.

Veamos un diagrama UML con las relaciones:

GenericDAOImplHibernateProfesorDAOImplHibernateUsuarioDAOImplHibernate

Ahora tenemos las 2 clases ProfesorDAOImplHibernate y UsuarioDAOImplHibernate que reutilizan todo el código de GenericDAOImplHibernate ya que heredan de GenericDAOImplHibernate y de esa forma evitaremos duplicar el código de Hibernate en cada entidad.

En el siguiente diagrama UML puede verse los interfaces que implementa cada clase.

«interface»GenericDAO«interface»ProfesorDAO«interface»UsuarioDAOGenericDAOImplHibernateProfesorDAOImplHibernateUsuarioDAOImplHibernate

El código Java correspondiente a las 3 clases es el siguiente:

1 | GenericDAOImplHibernate.java
public class GenericDAOImplHibernate<T, ID extends Serializable> implements GenericDAO<T, ID> {
 
 
    SessionFactory sessionFactory;
 
    private final static Logger LOGGER = Logger.getLogger(GenericDAOImplHibernate.class.getName());
 
    public GenericDAOImplHibernate() {
        sessionFactory=HibernateUtil.getSessionFactory();
    }
 
 
 
    @Override
    public T create() throws BussinessException {
        try {
            return getEntityClass().newInstance();
        } catch (InstantiationException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
 
    @Override
    public void saveOrUpdate(T entity) throws BussinessException {
        Session session = sessionFactory.getCurrentSession();
        try {
            session.beginTransaction();
            session.saveOrUpdate(entity);
            session.getTransaction().commit();
        } catch (javax.validation.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (org.hibernate.exception.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (RuntimeException ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw ex;
        } catch (Exception ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new RuntimeException(ex);
        }
    }
 
    @Override
    public T get(ID id) throws BussinessException {
        Session session = sessionFactory.getCurrentSession();
        try {
            session.beginTransaction();
            T entity = (T) session.get(getEntityClass(), id);
            session.getTransaction().commit();
 
            return entity;
        } catch (javax.validation.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (org.hibernate.exception.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (RuntimeException ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw ex;
        } catch (Exception ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new RuntimeException(ex);
        }
    }
 
    @Override
    public void delete(ID id) throws BussinessException {
        Session session = sessionFactory.getCurrentSession();
        try {
            session.beginTransaction();
            T entity = (T) session.get(getEntityClass(), id);
            if (entity == null) {
                throw new BussinessException(new BussinessMessage(null, "Los datos a borrar ya no existen"));
            }
            session.delete(entity);
            session.getTransaction().commit();
        } catch (javax.validation.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (org.hibernate.exception.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (BussinessException ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw ex;
        } catch (RuntimeException ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw ex;
        } catch (Exception ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new RuntimeException(ex);
        }
    }
 
    @Override
    public List<T> findAll() throws BussinessException {
        Session session = sessionFactory.getCurrentSession();
        try {
 
            Query query = session.createQuery("SELECT e FROM " + getEntityClass().getName() + " e");
            List<T> entities = query.list();
 
            return entities;
        } catch (javax.validation.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (org.hibernate.exception.ConstraintViolationException cve) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new BussinessException(cve);
        } catch (RuntimeException ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw ex;
        } catch (Exception ex) {
            try {
                if (session.getTransaction().isActive()) {
                    session.getTransaction().rollback();
                }
            } catch (Exception exc) {
                LOGGER.log(Level.WARNING,"Falló al hacer un rollback", exc);
            }
            throw new RuntimeException(ex);
        }
    }
 
    private Class<T> getEntityClass() {
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
}
1 | ProfesorDAOImplHibernate.java
public class ProfesorDAOImplHibernate extends GenericDAOImplHibernate<Profesor,Integer> implements  ProfesorDAO {
 
}
1 | UsuarioDAOImplHibernate .java
public class UsuarioDAOImplHibernate extends GenericDAOImplHibernate<Usuario,Integer> implements  UsuarioDAO {
 
}

El código de GenericDAOImplHibernate ha quedado un poco largo ya que se ha incluido la gestión de excepciones que hemos visto en el tema anterior.

Destacar la línea 9 de GenericDAOImplHibernate en la que se ve cómo usamos la clase HibernateUtil para acceder al objeto SessionFactory. Ésto es importante ya que en la siguiente lección explicaremos cómo hacer que no sea necesaria esa línea y evitar la dependencia de GenericDAOImplHibernate con HibernateUtil.

También destacar la simplicidad de las implementaciones para los interfaces ProfesorDAO y UsuarioDAO. Sólo es necesario crear las clases ProfesorDAOImplHibernate y UsuarioDAOImplHibernate y que hereden de GenericDAOImplHibernate no habiendo ni una sola línea de código.

La clase GenericDAOImplHibernate hace uso de los Generics de Java. Está mas allá del alcance de este curso explicar cómo funcionan los Generics de Java, pero en Internet existen gran cantidad de tutoriales explicando su funcionamiento.

Ahora en un único diagrama UML se ven todas las relaciones entre los interfaces y las clases.

Clase Generica con Tipos T e ID«interface»GenericDAOT create()void saveOrUpdate(T entity)T get(ID id)void delete(ID id)List<T> findAll()«interface»ProfesorDAO«interface»UsuarioDAOGenericDAOImplHibernateProfesorDAOImplHibernateUsuarioDAOImplHibernate

Este diagrama UML contiene todos los intefaces y clases que se necesitan para implementar el patrón DAO en hibernate de una forma correcta.

Creando un nuevo objeto DAO

Para finalizar explicar los pasos necesarios para crear un objeto DAO de una nueva entidad. Para ello, lo haremos con el ejemplo de la entidad Municipio.

  • Crear un nuevo interfaz llamado MunicipioDAO que herede de GenericDAO. El código Java es el siguiente:
public interface MunicipioDAO extends GenericDAO<Municipio,Integer> {
 
}
  • Crear una nueva implementación llamada MunicipioDAOImplHibernate para Hibernate que herede de GenericDAOImplHibernate e implemente MunicipioDAO
public class MunicipioDAOImplHibernate extends GenericDAOImplHibernate<Municipio,Integer> implements  MunicipioDAO {
 
}

Como vemos es sencillísimo crear un nuevo objeto DAO para una nueva entidad y sin nada de código repetido.

Creando objetos DAO

Ya sabemos cómo programar nuestros objetos DAO, pero ahora hay que añadirle el problema de crearlos desde nuestra aplicación. Aparentemente es tan sencillo como el siguiente código:

UsuarioDAO usuarioDAO = new UsuarioDAOImplHibernate();
ProfesorDAO profesorDAO = new ProfesorDAOImplHibernate();

Con esas dos simples líneas hemos creado 2 objetos DAO para las entidades Profesor y Usuario. Estas líneas se repetirán a lo largo del código cada vez que quedamos acceder a las entidades Profesor y Usuario.

¿Cuál es el problema?

Que si queremos cambiar la implementación de UsuarioDAO o de ProfesorDAO deberemos cambiar el código en todos los sitios en los que se use. Supongamos que ahora queremos usar implementaciones basadas en JDBC, se debería cambiar el código a:

UsuarioDAO usuarioDAO = new UsuarioDAOImplJDBC();
ProfesorDAO profesorDAO = new ProfesorDAOImplJDBC();

Podríamos pensar que hacer un simple buscar/reemplazar en nuestro IDE sería suficiente, pero podríamos tener código ya compilado en JARs con lo que no se cambiaría a no ser que buscáramos el fuente de los JARs y además habría que volver a recompilarlos.Una aplicación pueden ser miles y miles de líneas de código y decenas de JARs, así que el trabajo puede ser un poco largo y tedioso.

Hay varias soluciones a ésto pero hablaremos de la solución elegida en el siguiente tema sobre Spring.

unidades/07_arquitectura/03_dao.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1