====== 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''. note "Clase Generica con Tipos T e ID" as N1 class GenericDAO <> GenericDAO : T create() GenericDAO : void saveOrUpdate(T entity) GenericDAO : T get(ID id) GenericDAO : void delete(ID id) GenericDAO : List findAll() N1 .. GenericDAO class ProfesorDAO <> class UsuarioDAO <> GenericDAO <|.. ProfesorDAO GenericDAO <|.. 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: public interface GenericDAO { T create() throws BussinessException; void saveOrUpdate(T entity) throws BussinessException; T get(ID id) throws BussinessException; void delete(ID id) throws BussinessException; List findAll() throws BussinessException; } public interface ProfesorDAO extends GenericDAO { } public interface UsuarioDAO extends GenericDAO { } 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 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 [[http://www.genbetadev.com/java-j2ee/spring-framework-el-patrn-dao|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 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 findAll()'' ===== 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: class GenericDAOImplHibernate class ProfesorDAOImplHibernate class UsuarioDAOImplHibernate GenericDAOImplHibernate <|-- ProfesorDAOImplHibernate GenericDAOImplHibernate <|-- UsuarioDAOImplHibernate 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. class GenericDAO <> class ProfesorDAO <> class UsuarioDAO <> class GenericDAOImplHibernate class ProfesorDAOImplHibernate class UsuarioDAOImplHibernate GenericDAO <.. GenericDAOImplHibernate ProfesorDAO <.. ProfesorDAOImplHibernate UsuarioDAO <.. UsuarioDAOImplHibernate El código Java correspondiente a las 3 clases es el siguiente: public class GenericDAOImplHibernate implements GenericDAO { 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 findAll() throws BussinessException { Session session = sessionFactory.getCurrentSession(); try { Query query = session.createQuery("SELECT e FROM " + getEntityClass().getName() + " e"); List 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 getEntityClass() { return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } } public class ProfesorDAOImplHibernate extends GenericDAOImplHibernate implements ProfesorDAO { } public class UsuarioDAOImplHibernate extends GenericDAOImplHibernate 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 [[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. Ahora en un único diagrama UML se ven todas las relaciones entre los interfaces y las clases. note "Clase Generica con Tipos T e ID" as N1 class GenericDAO <> GenericDAO : T create() GenericDAO : void saveOrUpdate(T entity) GenericDAO : T get(ID id) GenericDAO : void delete(ID id) GenericDAO : List findAll() N1 .. GenericDAO class ProfesorDAO <> class UsuarioDAO <> GenericDAO <|.. ProfesorDAO GenericDAO <|.. UsuarioDAO class GenericDAOImplHibernate class ProfesorDAOImplHibernate class UsuarioDAOImplHibernate GenericDAOImplHibernate <|-- ProfesorDAOImplHibernate GenericDAOImplHibernate <|-- UsuarioDAOImplHibernate GenericDAO <.. GenericDAOImplHibernate ProfesorDAO <.. ProfesorDAOImplHibernate UsuarioDAO <.. UsuarioDAOImplHibernate 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: public interface MunicipioDAO extends GenericDAO { } * Crear una nueva implementación llamada ''MunicipioDAOImplHibernate '' para Hibernate que herede de ''GenericDAOImplHibernate'' e implemente ''MunicipioDAO'' public class MunicipioDAOImplHibernate extends GenericDAOImplHibernate 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.