Herramientas de usuario

Herramientas del sitio


unidades:07_arquitectura:03_dao

Diferencias

Muestra las diferencias entre dos versiones de la página.


unidades:07_arquitectura:03_dao [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== DAO ======
 +Pasemos ahora a ver cómo implementar el patrón [[patrones:dao]] usando Hibernate y de forma que sea fácilmente reutilizable. 
  
 +El mayor problema del patrón [[patrones: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''.
 +
 +<uml>
 +note "Clase Generica con Tipos T e ID" as N1
 +class GenericDAO <<interface>>
 +GenericDAO : T create()
 +GenericDAO : void saveOrUpdate(T entity)
 +GenericDAO : T get(ID id)
 +GenericDAO : void delete(ID id)
 +GenericDAO : List<T> findAll()
 +
 +N1 .. GenericDAO 
 +
 +class ProfesorDAO <<interface>>
 +class UsuarioDAO <<interface>>
 +
 +GenericDAO <|.. ProfesorDAO
 +GenericDAO <|.. UsuarioDAO
 +
 +</uml>
 +
 +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:
 +<code java 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;
 +}
 +</code>
 +
 +<code java 1 | ProfesorDAO.java >
 +public interface ProfesorDAO extends GenericDAO<Profesor,Integer> {
 +    
 +}
 +</code>
 +
 +<code java 1 | UsuarioDAO.java >
 +public interface UsuarioDAO extends GenericDAO<Usuario,Integer>  {
 +    
 +}
 +</code>
 +
 +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.
 +
 +<note important>
 +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 [[http://www.genbetadev.com/java-j2ee/spring-framework-el-patrn-dao|Spring Framework: El patrón DAO]]
 +</note>
 +
 +<note important>
 +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()''
 +</note>
 +===== La implementación =====
 +Como ya hemos comentado la ventaja de usar el patrón [[patrones: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:
 +
 +<uml>
 +class GenericDAOImplHibernate
 +class ProfesorDAOImplHibernate
 +class UsuarioDAOImplHibernate
 +
 +GenericDAOImplHibernate <|-- ProfesorDAOImplHibernate
 +GenericDAOImplHibernate <|-- UsuarioDAOImplHibernate
 +
 +</uml>
 +
 +
 +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.
 +
 +<uml>
 +
 +class GenericDAO <<interface>>
 +
 +class ProfesorDAO <<interface>>
 +class UsuarioDAO <<interface>>
 +
 +class GenericDAOImplHibernate
 +class ProfesorDAOImplHibernate
 +class UsuarioDAOImplHibernate
 +
 +GenericDAO <.. GenericDAOImplHibernate 
 +ProfesorDAO <.. ProfesorDAOImplHibernate
 +UsuarioDAO  <.. UsuarioDAOImplHibernate
 +</uml>
 +
 +El código Java correspondiente a las 3 clases es el siguiente:
 +<code java 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];
 +    }
 +}
 +</code>
 +
 +<code java 1 | ProfesorDAOImplHibernate.java>
 +public class ProfesorDAOImplHibernate extends GenericDAOImplHibernate<Profesor,Integer> implements  ProfesorDAO {
 +
 +}
 +</code>
 +
 +<code java 1 | UsuarioDAOImplHibernate .java>
 +public class UsuarioDAOImplHibernate extends GenericDAOImplHibernate<Usuario,Integer> implements  UsuarioDAO {
 +    
 +}
 +</code>
 +
 +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.
 +
 +<note important>
 +La clase ''GenericDAOImplHibernate'' hace uso de los [[wp>Generics_in_Java|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.
 +</note>
 +
 +Ahora en un único diagrama UML se ven todas las relaciones entre los interfaces y las clases. 
 +
 +<uml>
 +note "Clase Generica con Tipos T e ID" as N1
 +class GenericDAO <<interface>>
 +GenericDAO : T create()
 +GenericDAO : void saveOrUpdate(T entity)
 +GenericDAO : T get(ID id)
 +GenericDAO : void delete(ID id)
 +GenericDAO : List<T> findAll()
 +
 +N1 .. GenericDAO 
 +
 +class ProfesorDAO <<interface>>
 +class UsuarioDAO <<interface>>
 +
 +GenericDAO <|.. ProfesorDAO
 +GenericDAO <|.. UsuarioDAO
 +
 +class GenericDAOImplHibernate
 +class ProfesorDAOImplHibernate
 +class UsuarioDAOImplHibernate
 +
 +GenericDAOImplHibernate <|-- ProfesorDAOImplHibernate
 +GenericDAOImplHibernate <|-- UsuarioDAOImplHibernate
 +
 +GenericDAO <.. GenericDAOImplHibernate 
 +ProfesorDAO <.. ProfesorDAOImplHibernate
 +UsuarioDAO  <.. UsuarioDAOImplHibernate
 +</uml>
 +
 +Este diagrama UML contiene todos los intefaces y clases que se necesitan para implementar el patrón [[patrones: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:
 +<code java >
 +public interface MunicipioDAO extends GenericDAO<Municipio,Integer> {
 +    
 +}
 +</code>
 +  * Crear una nueva implementación llamada ''MunicipioDAOImplHibernate '' para Hibernate que herede de ''GenericDAOImplHibernate'' e implemente ''MunicipioDAO''
 +<code java>
 +public class MunicipioDAOImplHibernate extends GenericDAOImplHibernate<Municipio,Integer> implements  MunicipioDAO {
 +
 +}
 +</code>
 +
 +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:
 +<code java>
 +UsuarioDAO usuarioDAO = new UsuarioDAOImplHibernate();
 +ProfesorDAO profesorDAO = new ProfesorDAOImplHibernate();
 +</code>
 +
 +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:
 +<code java>
 +UsuarioDAO usuarioDAO = new UsuarioDAOImplJDBC();
 +ProfesorDAO profesorDAO = new ProfesorDAOImplJDBC();
 +</code>
 +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.