UNIVERSIDAD PONTIFICIA COMILLAS ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA (ICAI) INGENIERO TÉCNICO EN INFORMÁTICA DE GESTIÓN INGENIERO TÉCNICO EN INFORMÁTICA DE SISTEMAS PROYECTO FIN DE CARRERA ANÁLISIS Y USO DE FRAMEWORKS DE PERSISTENCIA EN JAVA Autor: Alfredo Payá Martín Director: José Miguel Ordax Cassá Análisis y Uso de Frameworks de Persistencia en Java Agradecimientos Gracias a todos. A mi familia que me ha apoyado durante toda la carrera. Pero en especial a mis padres, por esta gran oportunidad. A Esther, que me ha ayudado a sentar la cabeza, y ha sufrido todas las horas de trabajo dedicadas al proyecto sacrificando preciosos días de vacaciones. A todos mis amigos y compañeros de clase, por haberme animado y apoyado durante todos los años de carrera. I Análisis y Uso de Frameworks de Persistencia en Java Resumen II Análisis y Uso de Frameworks de Persistencia en Java RESUMEN Este proyecto pretende realizar un análisis completo de las diferentes alternativas que hay para conseguir la persistencia de datos en Java. A continuación, se construirá una aplicación Web para la explotación de datos empleando una de las alternativas analizadas en un principio. Como consecuencia, el proyecto va a estar formado por dos partes bien diferenciadas, una primera parte teórica y otra práctica. La motivación que inspira este proyecto es fácil de explicar. Lo más importante para una empresa son los datos que maneja. Todos los elementos que conforman una empresa están reflejados en datos, desde los empleados hasta los clientes, desde los recursos que posee hasta los productos que vende, etc. La información y características asociadas de estos elementos siempre pueden ser útiles y, en otros casos, indispensables para el funcionamiento de la empresa. ¿Qué sería de un banco si tiene problemas a la hora de manejar los datos de sus clientes? ¿Y los datos de sus transacciones? Es evidente que los datos conforman el corazón de las empresas y, por esta razón, es crucial la forma de manejarlos. Toda la información que posee cualquier empresa, generalmente, está almacenada en forma de tablas que comparten datos y conforman bases de datos relacionales. Si los accesos a la información toman un papel importante en las operaciones rutinarias empresariales, es normal que la forma de explotar esos datos III Análisis y Uso de Frameworks de Persistencia en Java tenga una repercusión directa en la eficiencia de la empresa. Dada la trascendencia que toman los accesos a dicha información, se puede afirmar que la forma de realizar consultas a las bases de datos es tan importante como el diseño de sus tablas. Hoy en día Java sigue siendo un lenguaje en pleno desarrollo. Su falta de sencillez a la hora de trabajar con bases de datos, ha provocado la aparición de diferentes frameworks que facilitan estas tareas. Existen diversas alternativas que aportan métodos diferentes: Hibernate, EJB’s, JDO, o, sencillamente, JDBC. La eficiencia de dichas alternativas será definitiva a la hora de manejar bases de datos con Java. Ahora, si nos paramos a pensar en las aplicaciones prácticas de esta tecnología veremos que una gran forma de aprovechar estas tecnologías es la explotación de tablas en un entorno Web. Por ejemplo, en una empresa sería de gran utilidad que, a través de un portal común, los empleados de los diferentes departamentos puedan hacer consultas directamente sobre las bases de datos de una forma sencilla e intuitiva. Gracias a los perfiles de usuario, un mismo portal serviría a todos los componentes de la empresa. Todo el mundo tendría un acceso cómodo, rápido y sin intermediarios a la información que necesite en cada momento, dentro de una selección de los datos más usados por los departamentos. A su vez, el departamento de informática sería el encargado del mantenimiento del portal. Esto se puede realizar con tecnología J2EE y una de las alternativas de persistencia de datos. IV Análisis y Uso de Frameworks de Persistencia en Java ABSTRACT This proyect tries to do a complete analysis of the most important alternatives that can be found in order to persist data with Java. Next, a web application will be designed and built using one of the alternatives studied in the first place. This way, the proyect will be divided into two different parts, theoretical and practical. The motivation that inspires this proyect is easy to explain. The most important thing about any company is the data management. Every component of the system is going to be represented by data: employees and clients, products and resources... The information associated to these elements is always useful, and many times is essential. What would happen to a bank if it finds difficulties when managing the data about its clients? And what would happen with the transactions? It is obvious that the data is the heart of the companies and, because of this, its management is critical. All the information about a company is usually stored with the help of relational data bases. If the access to the information is important when doing daily operations in a company, it is normal that the way used to get the data is linked to the company’s efficiency. This means that the way used to get the information is as important as the data base design V Análisis y Uso de Frameworks de Persistencia en Java Today, Java is still a growing technology. The complicated process involved while working with data bases has caused the release of frameworks in order to make the task a little easier. There are different alternatives such as Hibernate, JDO, EJB or just JDBC. The efficiency of this layer will be critical while working with data bases. Now, one of the possible ways to take advantage with these technologies is to manage data bases through a web application. For example, it would be useful for a company that the employees of the different departments could consult some data bases trough a web site. The same site would provide services to everyone using different user profiles. Every employee would have a fast and easy access to the information required related to his department. This could be build with J2EE (Java 2 Enterprise Edition) and one of the alternatives to persist data. VI Análisis y Uso de Frameworks de Persistencia en Java Índice VII Análisis y Uso de Frameworks de Persistencia en Java ÍNDICE 1. Introducción……………………………………………………………………...1 2. Definición del Problema…………………………………………………………3 3. Análisis de las Tecnologías……………………………………………………...6 3.1. Objetivos…………………………………………………………………7 3.2. Alternativas de Persistencia……………………………………………...9 3.2.1. Introducción…….…………………………………………………9 El Mapeo Objeto-Relacional (ORM)…………………………….9 JDBC: Componente indispensable para los ORM……………...12 3.2.2. Tecnologías basadas en el estándar………………………………15 Java Data Objects (JDO) – Definición………………………….15 Enterprise Java Beans (EJB) de Entidad – Definición………….18 3.2.3. Tecnologías Open Source: Hibernate – Definición……………...20 3.3. Funcionamiento de las Alternativas…………………………………….21 3.3.1. Tecnologías basadas en el estándar………………………………21 Java Data Objects (JDO) – Funcionamiento……………………21 Crear un Objeto…………………………………………21 Crear un Gestor de Persistencia…………………………23 Crear un Descriptor JDO para el JDOEnhancer………...30 Construir y Ejecutar la Aplicación……………………...31 El Lenguaje de Consulta JDOQL……………………….35 Enterprise Java Beans (EJB) de Entidad – Funcionamiento……37 Persistencia Manejada por el Contenedor (CMP)………37 VIII Análisis y Uso de Frameworks de Persistencia en Java La Clase Bean…………………………………..38 Interface Home………………………………….43 Interface Remoto………………………………..46 Métodos de Retrollamada……………………….48 Persistencia Manejada por el Bean (BMP)……………..53 3.3.2. Tecnologías Open Source: Hibernate – Funcionamiento………..62 Creación del Objeto……………………………………..63 Persistencia……………………………………………...65 El Fichero de Mapeo……………………………………67 Configuración de la Base de Datos……………………..71 Construcción y Ejecución……………………………….73 3.4. Evaluación……………………………………………………………...76 JDO vs. EJB…………………………….....................................76 JDO vs. JDBC/SQL…………………………………………….77 Hibernate vs. JDO………………………………………………79 3.5. Conclusiones……………………………………………………………80 4. Diseño y Desarrollo de la Aplicación Web…………………………………….84 4.1. Objetivos………………………………………………………………..85 4.2. Descripción de la Aplicación…………………………………………...86 4.3. Requisitos funcionales………………………………………………….87 4.3.1. Actores Participantes……………………………………………87 4.3.2. Casos de uso…………………………………………………….88 Descripción detallada de los Casos de Uso……………..90 Diagramas de Casos de Uso…………………………...100 4.4. Diseño Externo………………………………………………………..103 IX Análisis y Uso de Frameworks de Persistencia en Java 4.4.1. Diseño del Interfaz del Usuario……………………………….103 Inicio…………………………………………………...103 Navegación Básica…………………………………….105 Gestión de Pacientes…………………………………...108 Gestión de Alergias……………………………………111 Gestión del Portal (Noticias y Cuentas de Usuario)…...114 Cierre de Sesión………………………………………..120 4.5. Diseño Interno…………………………………………………………121 4.5.1. JSP y Servlets………………………………………………….121 4.5.2. Diagrama de navegación………………………………………126 4.5.3. Diagramas de clases…………………………………………...127 Clases Objeto…………………………………………..128 Clases de Acceso a la Base de Datos………………….131 4.5.4. Modelo de datos……………………………………………….133 5. La alternativa a JDBC: Hibernate……………………………………………..135 5.1. Las clases a persistir…………………………………………………..136 5.2. Los ficheros de mapeo………………………………………………...147 5.3. Las clases gestoras de objetos…………………………………………152 5.4. Implantación…………………………………………………………..172 5.4.1. Instalación y Configuración…………………………………...172 5.4.2. Requisitos del Sistema………………………………………...174 6. Presupuesto……………………………………………………………………175 7. Conclusiones.………….………………………………………………………177 8. Bibliografía……………………………………………………………………180 X Análisis y Uso de Frameworks de Persistencia en Java 1- Introducción 1 Análisis y Uso de Frameworks de Persistencia en Java 1- INTRODUCCIÓN El fin de este proyecto es dar a conocer ciertas alternativas a JDBC a la hora de persistir objetos Java en bases de datos relacionales, así como ver su funcionamiento en una aplicación real. Una forma de abordar el problema es realizar un análisis de las alternativas más importantes que están a nuestra disposición. Conocer el funcionamiento de las diferentes alternativas da pie a enfrentarlas y a poder comparar su servicio al problema de la persistencia. Sin embargo, cada una de las tecnologías tiene su propio origen y filosofía para alcanzar el mismo fin y, por esta razón, no será fácil analizar sus ventajas y desventajas frente a las demás alternativas. Una vez terminado el análisis de las tecnologías, se procederá a diseñar una aplicación Web que se caracterice por su necesidad de persistir objetos. Dicha aplicación contará con diversas funciones relacionadas con los accesos a bases de datos, especialmente la persistencia de objetos Java. La fase final del proyecto consistirá en introducir en la aplicación una de las tecnologías analizadas. Dicha tecnología será seleccionada por ser la más adecuada, y se encargará de realizar la tarea de persistir objetos, sirviendo de enlace entre la lógica y la interacción con una base de datos. La aplicación será construida en un principio con JDBC y sentencias SQL de forma que, al introducir la tecnología, se podrá ver claramente la gran función que es capaz de desempeñar un framework de persistencia. 2 Análisis y Uso de Frameworks de Persistencia en Java 2- Definición del Problema 3 Análisis y Uso de Frameworks de Persistencia en Java 2- DEFINICIÓN DEL PROBLEMA La persistencia de datos en Java viene facilitada por mapeadores Objeto/Relacionales (O/R). Estas tecnologías conforman técnicas de programación para enlazar un lenguaje de programación orientado a objetos con una base de datos relacional. Los mecanismos de mapeo O/R permiten al programador mantener una perspectiva orientada a objetos y cuidar que se consiga aportar una solución a la lógica de negocio, eliminando el obstáculo que supone compatibilizar objetos con modelos relacionales. El framework para la persistencia de datos se ocupa de todos los detalles que conllevaría desarrollar un mecanismo de mapeo personalizado. Hoy en día, uno de los asuntos más debatidos y discutidos más apasionadamente en la industria del software es: ¿cuál de las tecnologías de persistencia (o frameworks para el mapeo Objeto/Relacional) merece ser la alternativa dominante? Esta discusión ha creado una división en la comunidad Java. Mientras esto siga así, dicho debate seguirá en pie y los desarrolladores de software deberán elegir la tecnología que mejor se adapte a sus necesidades, y esto se debe extender a cualquier componente que conforme la arquitectura del sistema completo, incluyendo el driver JDBC. 4 Análisis y Uso de Frameworks de Persistencia en Java En la figura podemos observar la estructura básica de una aplicación Web desarrollada con Java empleando Servlets y JSP. La lógica de la aplicación localizada en el servidor contiene, entre otros, un módulo (representado por una estrella roja) que será el encargado de interactuar con las bases de datos. Todas y cada una de las consultas y modificaciones que deba realizar la aplicación sobre las bases de datos dependerán de la implementación del módulo en cuestión. Es en esa parte de la lógica del programa donde entran en escena los frameworks de persistencia. 5 Análisis y Uso de Frameworks de Persistencia en Java 3- Análisis de las Tecnologías 6 Análisis y Uso de Frameworks de Persistencia en Java 3- ANÁLISIS DE LAS TECNOLOGÍAS 3.1.- OBJETIVOS • Definición de las alternativas Es necesario conocer qué alternativas de persistencia son las más importantes actualmente. Se pretende definir cuáles son las se van a barajar y cuáles son sus orígenes. Se especificará cómo cada una de las alternativas está asociada a una organización o comunidad de desarrollo, así como su filosofía. • Descripción de su funcionamiento Se pretende conocer el funcionamiento de cada una de las alternativas analizadas de una forma sencilla. Para ello es necesario explicar paso a paso la metodología a seguir para conseguir la persistencia de los objetos, mostrando ejemplos y código fuente. • Evaluación: Características más relevantes 7 Análisis y Uso de Frameworks de Persistencia en Java Antes de finalizar el análisis, se pretende identificar las características más relevantes de cada una de las alternativas estudiadas. De esta forma, enfrentando unas con otras se dará pie a obtener conclusiones. • Conclusiones A continuación, se obtendrán diversas conclusiones acerca de la información reunida en los puntos anteriores. Por último, para dar sentido al estudio, se elegirá la alternativa de persistencia más apropiada para suplir la codificación JDBC empleada en la aplicación que se desarrolle a continuación, incorporándola al servidor. 8 Análisis y Uso de Frameworks de Persistencia en Java 3.2- ALTERNATIVAS DE PERSISTENCIA 3.2.1. Introducción El Mapeo Objeto-Relacional (ORM) Si se está trabajando con programación orientada a objetos y bases de datos relacionales, es fácil observar que estos son dos paradigmas diferentes. El modelo relacional trata con relaciones y conjuntos, y es muy matemático por naturaleza. Sin embargo, el paradigma orientado a objetos trata con objetos, sus atributos y asociaciones de unos con otros. Tan pronto como se quieran persistir los objetos utilizando una base de datos relacional se puede observar que hay un problema de compatibilidad entre estos dos paradigmas, la también llamada diferencia objeto-relacional. Un mapeador objeto-relacional (ORM) es de gran ayuda para evitar esta diferencia. Esta diferencia se manifiesta cuando se tienen diversos objetos en una aplicación y se alcanza el punto donde es oportuno que sean persistentes. En este caso se debe abrir una conexión JDBC, crear una sentencia SQL y copiar todos los valores de las propiedades de los objetos sobre la selección. Esto 9 Análisis y Uso de Frameworks de Persistencia en Java podría ser fácil para un objeto pequeño, pero si se considera para un objeto con muchas propiedades, la labor se convierte en un proceso incómodo. Este no es el único problema. Las asociaciones entre objetos también hay que tenerlas en cuenta, así como las restricciones de claves. Lo mismo se puede aplicar para la carga. Si se asume que se carga un objeto desde la base de datos y que tiene una colección asociada. A la hora de carga el objeto ¿se cargará también la colección? Si se cargan también los elementos de la colección, se considerará que cada objeto elemento tiene una asociación con todavía más objetos. En este caso, sería mejor cargar todo el árbol de objetos. Por tanto, la diferencia objeto-relacional se amplia muy rápidamente cuando se está trabajando con grandes modelos de objetos. Y hay muchas más cosas a considerar como la carga lenta, las referencias circulares, el caché, etc. Como detalle, se han realizado estudios que demuestran que el 35% del código de una aplicación se produce para mapear datos entre la aplicación y la base de datos. Para la mayoría de las aplicaciones, almacenar y recuperar información implica alguna forma de interacción con una base de datos relacional. Esto ha representado un problema fundamental para los desarrolladores porque algunas veces el diseño de datos relacionales y los ejemplares orientados a objetos 10 Análisis y Uso de Frameworks de Persistencia en Java comparten estructuras de relaciones muy diferentes dentro de sus respectivos entornos. Las bases de datos relacionales están estructuradas en una configuración tabular y los ejemplares orientados a objetos normalmente están relacionados en forma de árbol. Esta 'diferencia de impedancia' ha llevado a los desarrolladores de varias tecnologías de persistencia de objetos a intentar construir un puente entre el mundo relacional y el mundo orientado a objetos. Persistir objetos Java en una base de datos relacional es un reto único que implica serializar un árbol de objetos Java en una base de datos de estructura tabular y viceversa. Este reto actualmente está siendo corregido por varias herramientas diferentes. La mayoría de estas herramientas permite a los desarrolladores instruir a los motores de persistencia de cómo convertir objetos Java a columnas/registros de la base de datos y al revés. Esencial para este esfuerzo es la necesidad de mapear objetos Java a columnas y registros de la base de datos de una forma optimizada en velocidad y eficiencia. 11 Análisis y Uso de Frameworks de Persistencia en Java JDBC: Componente indispensable para los ORM La mayoría de las aplicaciones que son importantes en una empresa están respaldadas por una arquitectura normalizada y optimizada de bases de datos relacionales. Tradicionalmente, dichas aplicaciones están basadas en sentencias SQL con las cuales se gestionan todos los datos que manejan. Este modelo continúa teniendo una gran importancia estratégica y es la base para el continuo crecimiento del mapeo Objeto-Relacional (O/R) y está asociado a los mecanismos de persistencia. El mapeo O/R es una herramienta muy útil para la programación en Java puesto que el desarrollador se puede concentrar en la construcción de la aplicación mientras que delega los detalles de la persistencia de datos al framework de persistencia. Hay múltiples alternativas para los programadores de Java cuando se pretende trabajar con mapeadores O/R. Hay tres organizaciones o comunidades que están implicadas en el mundo de la persistencia O/R de Java de forma activa: organizaciones basadas en el estándar, comunidades open source y grupos comerciales. Las comunidades open source incluyen importantes tecnologías, entre ellas Hibernate y el framework Spring. Las alternativas más importantes basadas en el estándar, son EJB 3.0 y JDO. Entre las implementaciones comerciales se pueden 12 Análisis y Uso de Frameworks de Persistencia en Java resaltar Oracle’s TopLink. Para este proyecto se analizarán las tecnologías open souce y las basadas en el estándar. Sin tener en cuenta el mecanismo de mapeo O/R que se vaya a utilizar para comunicarse con la base de datos relacional, todos ellos dependen de JDBC. Teniendo en cuenta que la mayor parte de las aplicaciones se comunican con bases de datos relacionales, es fundamental considerar cada uno de los niveles del software (desde el código del programa hasta la fuente de datos) para asegurar que el diseño de persistencia O/R sea óptimo. Tal y como se verá más adelante, cada uno de los mecanismos de mapeo O/R tiene una dependencia particular en el driver JDBC para poder comunicarse con la base de datos de una forma eficiente. Si el driver JDBC que va a participar en la comunicación no es óptimo, la posible gran eficiencia de cualquier framework quedará debilitada. Por tanto, elegir el driver JDBC que mejor se adapte a la aplicación es esencial a la hora de construir un sistema eficiente en el que participe un mecanismo de mapeo O/R. 13 Análisis y Uso de Frameworks de Persistencia en Java La figura muestra una representación de los diferentes mecanismos de mapeo O/R y cómo se relacionan con el código de la aplicación y con los recursos de datos relacionados. Esto muestra claramente la función crítica que desempeña el driver JDBC puesto que está situado en la base de cada uno de los frameworks. La eficiencia del driver JDBC tiene importantes consecuencias en el comportamiento de las aplicaciones. Cada mecanismo de mapeo O/R es completamente dependiente del driver, sin tener en cuenta el diseño de la API del framework que esté expuesta al código fuente de la aplicación. Como los mecanismos de mapeo O/R generan llamadas eficientes para acceder a la base de datos, mucha gente defiende que la importancia del driver JDBC se ha visto reducida. Sin embargo, como en cualquier arquitectura, la 14 Análisis y Uso de Frameworks de Persistencia en Java totalidad de eficiencia en una aplicación siempre estará afectada por el nivel más débil del sistema. Independientemente del código JDBC generado, los mecanismos de mapeo O/R son incapaces de controlar cómo los drivers interactúan con la base de datos. Al fin y al cabo la eficiencia de la aplicación depende en gran parte de la habilidad que tenga el driver del nivel JDBC para mover todos los datos manejados entre la aplicación y la base de datos. Aunque hay múltiples factores que considerar a la hora de elegir un driver JDBC, seleccionar el mejor driver JDBC posible basándose en comportamiento, escalabilidad y fiabilidad es la clave para obtener el máximo beneficio de cualquier aplicación basada en un framework O/R. 3.2.2. Definición de las tecnologías basadas en el estándar • Java Data Objects (JDO) – Definición Java Data Objects (JDO) es una especificación desarrollada para conseguir una persistencia de objetos Java de forma transparente. Es una API de alto nivel que permite a las aplicaciones almacenar objetos Java en un almacén 15 Análisis y Uso de Frameworks de Persistencia en Java de datos transaccional (ficheros y bases de datos) siguiendo unos estándares predefinidos. Esta API puede ser utilizada para acceder a datos en servidores o sistemas embebidos. Además, las implementaciones de JDO pueden llegar a la telefonía móvil, PDA y demás tecnologías wireless. Sin embargo, su función más destacada es su capacidad para manejar como objetos cierta información perteneciente a una base de datos relacional. JDO ha sido creado bajo la dirección de la comunidad de Java y documentado como una Especificación Java (JSAR 12). Craig Russell de Sun Microsystems lideró a un grupo de expertos de varias compañías para desarrollar la documentación. Como con otras muchas tecnologías de Java, Sun Microsystems ha lanzado una especificación y una referencia para la implementación. Además, los vendedores están desarrollando productos compatibles con JDO. La siguiente tabla muestra una lista de algunos vendedores: 16 Análisis y Uso de Frameworks de Persistencia en Java Vendedores de JDO Compañía Producto SolarMetric URL Asociada Kodo JDO Enterprise Edition, Kodo www.solarmetric.com JDO Standard Edition Poet Software FastObjects www.fastobjects.com LiBeLIS LiDO www.libelis.com Hemisphere JDO Genie www.hemtech.co.za/jdo ObjectFrontier Inc. FrontierSuite for JDO www.objectfrontier.com Versant Versant enJin www.versant.com Sun Microsystems, Reference Implementation www.java.sun.com JRelay www.objectindustries.com Corporation Technologies Inc. Object Industries GMBH JDO ha sido diseñado para poderse incorporar fácilmente a servidores de aplicación. Esto permite que la tecnología pueda asumir muchas funciones ofrecidas también por las Enterprise Java Beans (EJB) de Entidad, CMP. Dicha funcionalidad consiste en separar los objetos Java del código JDBC con sus correspondientes sentencias SQL necesarios para acceder a las bases de datos. 17 Análisis y Uso de Frameworks de Persistencia en Java JDO define un lenguaje de consulta conocido como JDOQL. Este lenguaje está embebido dentro del interface javax.jdo.Query. • Enterprise Java Beans (EJB) de Entidad – Definición El bean de entidad es uno de los dos tipos de beans primarios: entidad y sesión. El bean de entidad es usado para representar datos en una base de datos. Proporciona un interface orientado a objetos a los datos que normalmente serían accedidos mediante el JDBC u otro API. Más que eso, los beans de entidad proporcionan un modelo de componentes que permite a los desarrolladores de beans enfocar su atención en la lógica de negocio del bean, mientras el contenedor tiene cuidado de manejar la persistencia, las transacciones, y el control de accesos. El objetivo de los EJB de entidad es encapsular los objetos de lado del servidor que almacena los datos. Los EJBs de entidad presentan la característica fundamental de la persistencia: 18 Análisis y Uso de Frameworks de Persistencia en Java * Persistencia gestionada por el contenedor (CMP): el contenedor se encarga de almacenar y recuperar los datos del objeto de entidad mediante un mapeado en una tabla de una base de datos. * Persistencia gestionada por el bean (BMP): el propio objeto entidad se encarga, mediante una base de datos u otro mecanismo, de almacenar y recuperar los datos a los que se refiere, por lo cual, la responsabilidad de implementar los mecanismos de persistencia es del programador. Con CMP, el contenedor maneja la persistencia del bean de entidad. Las herramientas de los vendedores se usan para mapear los campos de entidad a la base de datos y no se escribe ningún código de acceso a las bases de datos en la clase del bean. Con BMP, el bean de entidad contiene código de acceso a la base de datos (normalmente JDBC) y es responsable de leer y escribir en la base de datos su propio estado. Las entidades CMP tienen mucha ayuda ya que el contenedor alertará al bean siempre que sea necesario hacer una actualización o leer su estado desde la base de datos. El contenedor también puede manejar cualquier bloqueo o transacción, para que la base de datos se mantenga íntegra. 19 Análisis y Uso de Frameworks de Persistencia en Java 3.2.3. Tecnologías Open Source: Hibernate - Definición Hibernate es un marco de trabajo de mapeo O/R Open Source que evita la necesidad de utilizar el API JDBC. Hibernate soporta la mayoría de los sistemas de bases de datos SQL. El Hibernate Query Language, diseñado como una extensión mínima, orientada a objetos, de SQL, proporciona un puente elegante entre los mundos objeto y relacional. Además, ofrece facilidades para recuperación y actualización de datos, control de transacciones, repositorios de conexiones a bases de datos, consultas programáticas y declarativas, y un control de relaciones de entidades declarativas. Hibernate es un servicio de alto rendimiento para la persistencia O/R. Permite desarrollar clases que se pueden persistir y que son acordes con la programación orientada a objetos, incluyendo asociación, herencia, polimorfismo, composición y colecciones. Hibernate dispone de si propio lenguaje llamado HQL, que ofrece portabilidad a SQL. Hibernate es un proyecto profesional Open Source y un componente crítico del catálogo de productos de JBoss Enterprise Middleware System (JEMS). JBoss es una división de Red Hat, y ofrece servicios de mantenimiento, consulta y entrenamiento para asistir a sus usuarios. 20 Análisis y Uso de Frameworks de Persistencia en Java 3.3- FUNCIONAMIENTO 3.3.1. Tecnologías basadas en el estándar • Java Data Objects (JDO) - Funcionamiento Para comprender el funcionamiento de JDO se pretende describir los diversos pasos a seguir para construir una aplicación que permita realizar el proceso de persistencia. Primero, se creará un objeto Persona (Person) para persistirlo en la base de datos. A continuación, se creará el objeto PersistePersona (PersistPerson) que será el encargado de gestionar el objeto persona en la base de datos. Después se construirá un descriptor JDO para decirle al JDOEnhancer qué es lo que deseamos persistir. Por último se verá qué pasos se deben seguir para construir y ejecutar el sistema. OBJETO: Creación del objeto Persona (Person.java) El objeto ‘Person’ incluye los métodos “getters” y “setters” para sus atributos, tal y como es conveniente para cualquier clase de Java. Los únicos requisitos para que una clase se pueda persistir son: 21 Análisis y Uso de Frameworks de Persistencia en Java 1. Sus atributos deben ser accesibles para las clases JDO (“public” o métodos “setters”). 2. Los tipos de datos de los atributos deben estar permitidos por la JDO specification. 3. Ciertos a tributos no son admitidos (por ejemplo, Thread, File o Socket, que no son “serializable”. Teniendo en cuenta los requisitos, la clase ‘Person.java’ podría tener este aspecto: public class Person { // -- ATRIBUTOS private String private String private String private String private String private String name; address; ssn; email; homePhone; workPhone; // -- CONSTRUCTOR public Person(String name, String address, String ssn, String email, String homePhone, String workPhone) { this.name = name; this.address = address; this.ssn = ssn; this.email = email; this.homePhone = homePhone; this.workPhone = workPhone; } // -- MÉTODOS public String getName() { return name; } public String getAddress() { return address; } public String getSsn() { return ssn; } public String getEmail() { return email; } public String getHomePhone() { return homePhone; } public String getWorkPhone() { return workPhone; } 22 Análisis y Uso de Frameworks de Persistencia en Java // -- mutators public void setName(String name) { this.name = name; } public void setAddress(String address) { this.address = address; } public void setSsn(String ssn) { this.ssn = ssn; } public void setEmail(String email) { this.email = email; } public void setHomePhone(String homePhone) { this.homePhone = homePhone; } public void setWorkPhone(String workPhone) { this.workPhone = workPhone; } } PERSISTENCIA: Creación del objeto gestor de la persistencia (PersonPersist.java) Una vez que se tiene un objeto ‘Person’ con el cuál podemos trabajar, necesitamos crear un objeto que se encargue de gestionar su persistencia. Este objeto será el ‘PersonPersist’. Seguidamente, por pasos, se realizarán diversas operaciones con el objeto ‘Person’: 1. Inicializar el JDO Persistente Manager. 2. Persistir tres personas en la base de datos. 3. Mostrar el contenido de la base de datos. 4. Cambiar el nombre de una persona. 5. Borrar una de las personas. 6. Realizar las operaciones desde el método main ( ). 23 Análisis y Uso de Frameworks de Persistencia en Java Paso 1º: Inicializar el JDO Persistence Manager Aquí figura el principio de la codificación del objeto ‘PersistPerson’. Es necesario importar las clases estándar de JDO y la ‘ManagedConnectionFactory’ desde la implementación de OpenFusion. El constructor asigna la “Connection Factory”, empleando la propiedad ‘javax.jdo.PersistenceManagerFactoryClass’. Esto es como asignar el driver de la base de datos si se trabaja con JDBC. package addressbook; import java.util.*; import javax.jdo.*; import com.prismt.j2ee.connector.jdbc.ManagedConnectionFactoryImpl; public class PersonPersist { private final static int SIZE = 3; private PersistenceManagerFactory pmf = null; private PersistenceManager pm = null; private Transaction transaction = null; // Array de personas a persistir private Person[] people; // Vector de identificadores de objetos private Vector id = new Vector(SIZE); public PersonPersist() { try { Properties props = new Properties(); props.setProperty("javax.jdo.PersistenceManagerFactoryClass", "com.prismt.j2ee.jdo.PersistenceManagerFactoryImpl"); pmf = JDOHelper.getPersistenceManagerFactory(props); pmf.setConnectionFactory( createConnectionFactory() ); } catch(Exception ex) { ex.printStackTrace(); System.exit(1); } } 24 Análisis y Uso de Frameworks de Persistencia en Java La “Connection Factory” se crea en un método estático llamado ‘createConnectionFactory()’. Esta factoría necesita la URL de JDBC, el driver JDBC, un nombre de usuario y la contraseña. OpenFusion JDO también viene con una ‘DBHelper’ que busca un fichero de propiedades que contiene las propiedades a usar de la base de datos. public static Object createConnectionFactory() { ManagedConnectionFactoryImpl mcfi = new ManagedConnectionFactoryImpl(); Object connectionFactory = null; try { mcfi.setUserName("scott"); mcfi.setPassword("tiger"); mcfi.setConnectionURL( "jdbc:oracle:thin:@localhost:1521:thedb"); mcfi.setDBDriver("oracle.jdbc.driver.OracleDriver"); connectionFactory = mcfi.createConnectionFactory(); } catch(Exception e) { e.printStackTrace(); System.exit(1); } return connectionFactory; } Paso 2º: Persistir tres personas en la base de datos El método ‘persistPeople()’ crea a tres personas, usando el constructor que aparece en ‘Person.java’. Lo primero que ha de hacerse es conseguir un gestor de persistencia a través del método ‘getPersistenceManager()’. Después se ha de crear una transacción donde se hará la operación. Para persistir el array de personas llamamos al método ‘makePersistentAll()’ sobre el “Persistence 25 Análisis y Uso de Frameworks de Persistencia en Java Manager” (pm). El bucle for{}sirve para obtener un identificador por cada persona persistida, guardándolos para usarlos en el futuro. public void persistPeople() { // CREACION DEL ARRAY DE PERSONAS people = new Person[SIZE]; // CREACION DE TRES PERSONAS (FUTBOLISTAS INGLESES) people[0] = new Person("Gary Segal", "123 Foobar Lane", "123-123-1234", "[email protected]", "(608) 294-0192", "(608) 029-4059"); people[1] = new Person("Michael Owen", "222 Bazza Lane, Liverpool, MN", "111-222-3333", "[email protected]", "(720) 111-2222", "(303) 222-3333"); people[2] = new Person("Roy Keane", "222 Trafford Ave, Manchester, MN", "234-235-3830", "[email protected]", "(720) 940-9049", "(303) 309-7599)"); // PERSISTENCIA DEL ARRAY DE PERSONAS pm = pmf.getPersistenceManager(); transaction = pm.currentTransaction(); pm.makePersistentAll(people); transaction.commit(); // OBTENCION DE LOS IDENTIFICADORES for(int i = 0; i < people.length; i++) { id.add(pm.getObjectId(people[i])); } // CLAUSURA DEL GESTOR DE PERSISTENCIA // AHORA LOS OBJETOS SE LEERAN DE LA BASE DE DATOS pm.close(); } He aquí algunos otros métodos que se pueden usar sobre el gestor de persistencia. Hay tres categorías: • Hace Instancias Persistentes. Persiste un objeto transitorio. • Elimina Instancias Persistidas. Elimina información de la base de datos. 26 Análisis y Uso de Frameworks de Persistencia en Java • Hace Instancias Transitorias. Rompe la asociación de las instancias con el gestor de persistencia. Sin embargo, la base de datos mantiene la información. Hace Instancia Persitente Elimina Instancia Persistida Hace Instancia Transitoria makePersistent(Object o) deletePersistent(Object o) makeTransient(Object o) makePersistentAll(Obj ect[] os) deletePersistentAll(Obje makeTransientAll(Objec ct[] os) t[] os) makePersistentAll(Col lection os) deletePersistentAll(Coll makeTransientAll(Colle ection os) ction os) Paso 3º: Mostrar el contenido de la base de datos El código para mostrar el contenido de la base de datos comienza por obtener el gestor de persistencia (‘getPersistenceManager()’). Para mostrar las personas creadas anteriormente, es necesario obtener los identificadores guardados a través del método ‘persistPeople()’. Una vez devueltos los objetos, se puede ejecutar los métodos que tienen definidos, en este caso los Getters: public void display(int end) { Person person; int max = end <= SIZE ? end : SIZE; // OBTENCION DEL GESTOR DE PERSISTENCIA pm = pmf.getPersistenceManager(); // OBTENCION DE LAS PERSONAS for(int i = 0; i < max; i++) { person = (Person) pm.getObjectById(id.elementAt(i), false); System.out.println("Name : " + person.getName()); System.out.println("Address : " + person.getAddress()); System.out.println("SSN : " + person.getSsn()); System.out.println("Email : " + person.getEmail()); 27 Análisis y Uso de Frameworks de Persistencia en Java System.out.println("Home Phone: " + person.getHomePhone()); System.out.println("Work Phone: " + person.getWorkPhone()); } pm.close(); } Paso 4º: Cambiar el nombre de una persona El código para cambiar información de una persona de la base de datos es muy similar al necesario para mostrar la información de las personas, sencillamente, se ha de ejecutar el método ‘setName()’ sobre la persona correspondiente. Sin embargo, hay una diferencia muy importante: en este caso se está realizando una transacción, ya que la fila en la base de datos se ha de modificar. Por tanto, hay que utilizar el método ‘commit()’ para guardar los cambios, al igual que se ha hecho al crear las personas. La única diferencia entre esta operación y trabajar con objetos transitorios es que hay que verificar la transacción. public void change() { Person person; // OBTENCION DEL OBJETO pm = pmf.getPersistenceManager(); transaction = pm.currentTransaction(); person = (Person) pm.getObjectById(id.elementAt(1), false); person.setName("Steve Gerrard"); // VERIFICACIÓN DEL CAMBIO transaction.commit(); pm.close(); } 28 Análisis y Uso de Frameworks de Persistencia en Java Paso 5º: Eliminar una persona de la base de datos Para eliminar un objeto persistido hay que obtenerlo y ejecutar el método ‘deletePersistent()’ sobre el mismo, tal y como se ha mencionado en el Paso 2º. En este caso, también se está realizando una transacción. Además, el identificador del objeto también es eliminado. public void delete() { pm = pmf.getPersistenceManager(); transaction = pm.currentTransaction(); pm.deletePersistent(pm.getObjectById(id.remove(1), false)); transaction.commit(); pm.close(); } Paso 6º: Realizar las operaciones desde el método ‘main ( )’ Por último, el programa que contiene todo el código anterior, tiene un método ‘main ( )’ donde se crea la gente, se modifica y se borra. public static void main(String[] args) { System.out.println("Create PersonPersist"); PersonPersist personPersist = new PersonPersist(); 29 Análisis y Uso de Frameworks de Persistencia en Java System.out.println("Setup and persist a group of people"); personPersist.persistPeople(); System.out.println("Display the persisted people"); personPersist.display(SIZE); System.out.println("Change a name "); personPersist.change(); personPersist.display(SIZE); System.out.println("Delete a person "); personPersist.delete(); personPersist.display(SIZE - 1); } JDOEnhancer: Crear un JDO Descriptor para el JDOEnhancer Ahora se ha codificado toda la aplicación. El siguiente paso a seguir es crear un “JDO Descriptor” que usará el JDOEnhancer. ¿Qué es el JDOEnhancer? La arquitectura JDO está construida con la idea de que una implementación JDO puede “mejorar” el bytecode de las clases a persistir manipulándolas, con el fin de añadir funcionalidades necesarias para los objetos. Por ejemplo, el JDOEnhancer hará que el interface ‘PersistanceCapable’ esté implementado en la clase (tarea que ahorra al codificador), así como algunos de los métodos del interface. Por tanto, después de compilar el código, se deberá ejecutar el JDOEnhancer para que realice las manipulaciones oportunas. Es necesario crear un fichero “Descriptor” para darle al “Enhancer” la información sobre las clases que se desean persistir. He aquí el descriptor: 30 Análisis y Uso de Frameworks de Persistencia en Java <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "file:/D:/Apps/OpenFusionJDO/xml/schema/jdo.dtd"> <jdo> <package name="addressbook"> <class name="Person" identity-type="datastore"> </class> </package> </jdo> Este es un fichero de mapeo sencillo. Sin embargo, he aquí un extracto de uno un poco más complejo que pretende mapear un objeto Departamento con su correspondiente ‘collection’ de empleados: <class name="Department" identity-type="datastore"> <field name="name"/> <field name="employees"> <collection elementtype="com.prismt.j2ee.jdo.examples.appKeyDepartment.Employee"> </collection> </field> <field name="manager"/> </class> CONSTRUCCIÓN: Pasos para construir y ejecutar la aplicación Estos son los pasos a seguir para construir el sistema que permitirá ejecutar la aplicación: 1. Compilar el Código 31 Análisis y Uso de Frameworks de Persistencia en Java 2. Ejecutar en JDOEnhancer 3. Preparar la base de datos (utilizando la salida del Enhancer) 4. Ejecutar la aplicación Paso 1º: Compilar el código La compilación se realizará como en cualquier código Java, usando ‘javac’. Por otra parte, es necesario asignar el ‘CLASSPATH’ correctamente. He aquí un ejemplo en Windows: % set OPENFUSION_DIR=D:\Apps\OpenFusionJDO % set CLASSPATH=%OPENFUSION_DIR%\lib\connector.jar;%OPENFUSION_DIR%\ lib\jndi.jar;%OPENFUSION_DIR%\lib\log4j.jar;%OPENFUSION_DIR%\l ib\xerces.jar;%OPENFUSION_DIR%\lib\classes12.zip;%OPENFUSION_D IR%\lib\jdo.jar;%OPENFUSION_DIR%\lib\jtaspec1_0_1.jar;%OPENFUSION_DIR%\lib\ofjdo.jar;. % javac –d . Person*.java Paso 2º: Ejecutar el JDOEnhancer El JDOEnhancer toma por un lado el código compilado y por otro el fichero descriptor. Esta es la sintaxis completa del OpenFusion JDOEnhancer: java com.prismt.j2ee.jdo.enhancer.JDOEnhancer Mandatory Options: -cp base directory to begin searching for classes to be enhanced. This is not the CLASSPATH, just where our compiled persistent classes are 32 Análisis y Uso de Frameworks de Persistencia en Java -oc -pd directory to place the enhanced classes JDO descriptor file(s) Optional: -db specific target database [oracle, sybase, etc] -od directory to generate SQL scripts to Para la aplicación compilada en el ejemplo, he aquí cómo se ejecutaría el JDOEnhancer: % java com.prismt.j2ee.jdo.enhancer.JDOEnhancer -oc . -pd person.jdo -db oracle -od db -cp . Paso 3º: Preparar la base de datos El JDOEnhancer es capaz de crear scripts para preparar la base de datos, siempre que se transmitan las opciones ‘–db baseDeDatos’ y ‘-od directorioDestino’. El Enhancer creará muchos scripts diferentes, pero uno de ellos se llama ‘load_all.sql’. El archivo se ha de abrir y cargar en la localización del SQL pertinente (ej: ‘sqlplus’ en Oracle). Este es un ejemplo de lo que contiene el fichero de sentencias SQL que ha sido creado para Oracle: CREATE SEQUENCE instid_seq INCREMENT BY 1 ; CREATE TABLE JDO_addressbook_Person_SCO 33 Análisis y Uso de Frameworks de Persistencia en Java ( inst_id INTEGER NOT NULL, class INTEGER NOT NULL, JDO_address VARCHAR2(255), JDO_email VARCHAR2(255), JDO_homePhone VARCHAR2(255), JDO_name VARCHAR2(255), JDO_ssn VARCHAR2(255), JDO_workPhone VARCHAR2(255) ) ; CREATE TABLE JDO_addressbook_Person ( inst_id INTEGER NOT NULL, class INTEGER NOT NULL, JDO_address VARCHAR2(255), JDO_email VARCHAR2(255), JDO_homePhone VARCHAR2(255), JDO_name VARCHAR2(255), JDO_ssn VARCHAR2(255), JDO_workPhone VARCHAR2(255) ) ; CREATE TABLE prismjdoProp ( name VARCHAR2(255) PRIMARY KEY, value VARCHAR2(255) ) ; CREATE TABLE prismjdoExtents ( class_id NUMBER(38,0) PRIMARY KEY, class_name VARCHAR2(255) UNIQUE, app_key VARCHAR2(255) ) ; ALTER TABLE JDO_addressbook_Person_SCO ADD PRIMARY KEY (inst_id, class) ; ALTER TABLE JDO_addressbook_Person ADD PRIMARY KEY (inst_id, class) ; INSERT INTO prismjdoExtents VALUES(0, 'addressbook.Person', 'com.prismt.j2ee.jdo.spi.DBKey') ; COMMIT WORK ; INSERT INTO prismjdoProp VALUES('USE.RDBMS.TRIGGERS', 'true') ; COMMIT WORK ; 34 Análisis y Uso de Frameworks de Persistencia en Java Paso 4º: Ejecutar la Aplicación % java addressbook.PersonPersist EL LENGUAJE DE CONSULTA JDOQL JDO define un lenguaje de consulta conocido como JDOQL. Este lenguaje está embebido dentro del interface ‘javax.jdo.Query’. El ‘PersistenceManager’ de JDO define los siguientes métodos para construir ejemplares de las clases de implementación de la consulta: • Query newQuery(); • Query newQuery(Class cls); • Query newQuery(Class cls, Collection coll); • Query newQuery(Class cls, String filter); • Query newQuery(Class cls, Collection c, String filter); • Query newQuery(Extent ext); • Query newQuery(Extent ext, String filter); El ‘filter’ se especifica como una expresión booleana que se parece algo a los operadores booleanos de SQL. El resultado de una consulta depende de si la expresión booleana se evalúa a ‘true’ por las clases candidatas especificadas. El interface ‘Query’ proporciona el siguiente método para especificar un filtro: • void setFilter(String filter); 35 Análisis y Uso de Frameworks de Persistencia en Java Las clases candidatas se pueden especificar utilizando uno de los siguientes métodos del interface ‘Query’: • void setClass(Class candidateClass); • void setCandidates(Collection candidates); • void setCandidates(Extent candidates); El interface ‘Query’ proporciona métodos para ejecutar una consulta. Los parámetros de la consulta se pueden pasar directamente a cualquiera de estos métodos: • Object execute(Object param); • Object execute(Object param1, Object param2); • Object execute(Object param1, Object param2, Object param3); • Object executeWithMap(Map paramMap); • Object executeWithArray(Object[] paramArray); El interface ‘Query’ también proporciona un método para declarar un grupo arbitrario de parámetros separados por comas: • void declareParameters(String paramStr); Este grupo de parámetros debe formatearse como una definición ‘String’ de las parejas tipo-nombre. Por ejemplo, el siguiente fragmento ilustra un grupo de parámetros para un nombre y una cuenta de usuario: query.declareParameters("String userName, int accountNumber"); 36 Análisis y Uso de Frameworks de Persistencia en Java El siguiente código muestra una consulta completa utilizando JDOQL: String filter = "userInfo.fullName == fullName && " + "userInfo.zip.startsWith(userZip)"; Extent extent = persistenceMgr.getExtent(UserInfo.class, true); Query query = persistenceMgr.newQuery(extent, filter); query.declareParameters("String city, String state"); Collection result = (Collection)query.execute("Shire", "New York"); Iterator iter = result.iterator(); while(iter.hasNext()) { UserInfo user = (UserInfo)iter.next(); System.out.println("User: " + user.getID()); } • Entity Java Beans (EJB) de Entidad – Funcionamiento PERSISTENCIA MANEJADA POR CONTENEDOR (CMP) Los beans con persistencia manejada por contenedor son los más simples para el desarrollador y los más complicados para que los soporte el servidor EJB. Esto es porque toda la lógica de sincronización del estado del bean con la base de datos es manejada automáticamente por el contenedor. Esto significa que el desarrollador del bean no necesita escribir ninguna lógica de acceso a datos, mientras que se supone que el servidor EJB tiene cuidado de toda la persistencia que necesita automáticamente. La mayoría de los vendedores EJB soportan la persistencia automática a una base de datos relacional, pero el nivel 37 Análisis y Uso de Frameworks de Persistencia en Java de soporte varía. Algunos proporcionan un mapeo O/R muy sofisticado, mientras que otros están muy limitados. La clase Bean Un bean enterprise es un componente completo que está compuesto de al menos dos interfaces y una clase de implementación del bean. import javax.ejb.EntityBean; public class CustomerBean implements EntityBean { int Address Name CreditCard customerID; myAddress; myName; myCreditCard; // CREATION METHODS public Integer ejbCreate(Integer id) { customerID = id.intValue(); return null; } public void ejbPostCreate(Integer id) { } public Customer ejbCreate(Integer id, Name name) { myName = name; return ejbCreate(id); } public void ejbPostCreate(Integer id, Name name) { } // BUSINESS METHODS public Name getName() { return myName; } public void setName(Name name) { myName = name; } public Address getAddress() { return myAddress; } 38 Análisis y Uso de Frameworks de Persistencia en Java public void setAddress(Address address) { myAddress = address; } public CreditCard getCreditCard() { return myCreditCard; } public void setCreditCard(CreditCard card) { myCreditCard = card; } // CALLBACK METHODS public void setEntityContext(EntityContext cntx) { } public void unsetEntityContext() { } public void ejbLoad() { } public void ejbStore() { } public void ejbActivate() { } public void ejbPassivate() { } public void ejbRemove() { } } Este es un buen ejemplo de un bean de entidad CMP bastante simple. Se puede observar que no hay lógica de acceso a base de datos en el bean. Esto es porque el vendedor del EJB proporciona herramientas para mapear los campos del CustomerBean a la base de datos. La clase CustomerBean, por ejemplo, podría ser mapeada a cualquier base de datos proporcionando los datos que contienen y que son muy similares a los campos del bean. En este caso lo campos de ejemplar del bean están comprendidos por un int y unos sencillos 39 Análisis y Uso de Frameworks de Persistencia en Java objetos dependientes (Name, Address, y CreditCard) con sus propios atributos. He aquí las definiciones de estos objetos dependientes. // The Name class public class Name implements Serializable { public String lastName, firstName, middleName; public Name(String lastName, String firstName, String middleName) { this.lastName = lastName; this.firstName = firstName; this.middleName = middleName; } public Name() {} } // The Address class public class Address implements Serializable { public String street, city, state, zip; public Address(String street, String city, String state, String zip) { this.street = street; this.city = city; this.state = state; this.zip = zip; } public Address() {} } // The CreditCard class public class CreditCard implements Serializable { public String number, type, name; public Date expDate; public CreditCard(String number, String type, String name, Date expDate) { this.number = number; this.type = type; this.name = name; this.expDate = expDate; 40 Análisis y Uso de Frameworks de Persistencia en Java } public CreditCard() {} } Estos campos se llaman campos manejados por contenedor porque el contenedor es el responsable de sincronizar su estado con la base de datos; el contenedor maneja los campos. Estos campos pueden ser cualquier tipo de dato primitivo o tipo serializable. Este caso usa tanto un tipo primitivo int (customerID) y objetos serializables (Address, Name, CreditCard). Para mapear los objetos dependientes a la base de datos necesitaríamos usar una herramienta de mapeado muy sofisticada. No todos los campos del bean son automáticamente campos manejados por el contenedor; algunos de ellos son sólo campos de ejemplar para el uso temporal del bean. Un desarrollador de beans distingue un campo manejado por contenedor de un campo de ejemplar normal indicando los campos manejados por el contenedor en el descriptor de desarrollo. Los campos manejados por contenedor deben tener sus tipos correspondientes (columnas en RDBMS) en la base de datos bien directamente o a través de mapeo O/R. El CustomerBean podría, por ejemplo, mapear una tabla CUSTOMER en la base de datos que tenga la siguiente definición. CREATE TABLE CUSTOMER 41 Análisis y Uso de Frameworks de Persistencia en Java { id last_name first_name middle_name street city state zip credit_number credit_date credit_name credit_type INTEGER PRIMARY KEY, CHAR(30), CHAR(20), CHAR(20), CHAR(50), CHAR(20), CHAR(2), CHAR(9), CHAR(20), DATE, CHAR(20), CHAR(10) } Con la persistencia controlada por contenedor, el vendedor debe tener alguna clase de herramienta propietaria que pueda mapear los datos manejados por el contenedor del bean a sus correspondientes columnas en la tabla específica, CUSTOMER en este caso. Una vez que los campos del bean se han mapeado a la base de datos, y se ha desarrollado el bean CustomerBean, el contenedor manejará la creacción de registros, el borrado, la carga y la actualización de registros en la tabla CUSTOMER en respuesta a los métodos invocados en los interfaces home y remoto del bean Customer. Un subconjunto (uno o más) de los campos manejados por el contenedor será identificado como la clave primaria del bean. Esta clave primaria es el índice o puntero a un único registro(s) en la base de datos que crea el estado del bean. En el caso de CustomerBean, el campo id es el campo clave primario y 42 Análisis y Uso de Frameworks de Persistencia en Java será usado para localizar los datos de los beans en la base de datos. Los campos clave de un campo sencillo primitivo se representan como su correspondiente objeto envoltura. La clave primaria del bean CustomerBean por ejemplo es un int primitivo en la clase bean pero los clientes del bean la manifestarán como del tipo java.lang.Integer. Las claves primarias que se componen de varios campos, llamadas claves primarias compuestas, serán representadas por un clase especial definida por el desarrollador del Bean. Interface Home Para crear un nuevo ejemplar de un bean de entidad CMP, y por lo tanto insertar los datos en la base de datos, se debe llamar al método create() del interface home del bean. He aquí la definición del interface ‘CustomerHome’. public interface CustomerHome extends javax.ejb.EJBHome { public Customer create( Integer customerNumber) throws RemoteException,CreateException; public Customer create(Integer customerNumber, Name name) throws RemoteException,CreateException; public Customer findByPrimaryKey(Integer customerNumber) throws RemoteException, FinderException; public Enumeration findByZipCode(int zipCode) throws RemoteException, FinderException; } 43 Análisis y Uso de Frameworks de Persistencia en Java A continuación está un ejemplo de cómo se usaría el interface ‘home’ en una aplicación cliente para crear un nuevo cliente. CustomerHome home = // Get a reference to the //CustomerHome object Name name = new Name("John", "W", "Smith"); Customer customer = home.create( new Integer(33), name); Un interface ‘home’ de un bean podría declarar cero o más métodos create(), cada uno de los cuales debe tener sus correspondientes métodos ejbCreate() y ejbPostCreate() en la clase del bean. Estos métodos de creación son enlazados en tiempo de ejecución, para que cuando se llame a un método create() del interface ‘home’, el contenedor delegue la invocación a los correspondientes métodos ejbCreate() y ejbPostCreate() de la clase Bean. Cuando se invoca al método create() de un interface ‘home’, el contenedor delega esta llamada al correspondiente método ejbCreate() en el bean. Estos métodos se usan para inicializar el estado del ejemplar antes de que el registro sea insertado en la base de datos. En este caso, inicializa los campos customerID y Name. Cuando el método ejbCreate() ha finalizado (devuelven null en CMP) el contenedor lee los campos manejados por el contenedor e inserta un nuevo registro en la tabla CUSTOMER indexado por la clave primaria, en este caso customerID que se mapea a la columna CUSOTMER.ID. 44 Análisis y Uso de Frameworks de Persistencia en Java En EJB un bean de entidad no existe técnicamente hasta después de que sus datos hayan sido insertados en la base de datos, lo que ocurre durante el método ejbCreate(). Una vez que los datos han sido insertados, el bean de entidad existe y puede acceder a su propia clave primaria y a referencias remotas, lo que no es posible hasta que se complete el método ejbCreate() y que los datos estén en la base de datos. Si el bean necesita acceder a su propia clave primaria o a una referencia remota después de haber sido creado, pero antes de servir a cualquier método de negocio, puede hacerlo en el método ejbPostCreate(). Este método permite al bean realizar cualquier proceso postcreacción antes de empezar a servir peticiones de clientes. Por cada ejbCreate() debe haber un método ejbPostCreate() correspondiente (con los mismos argumentos). Los métodos del interface ‘home’ que empiezan con "find" son llamados métodos de búsqueda. Se usan para consultar al servidor EJB sobre beans de entidad específicos, basándose en el nombre del método y los argumentos pasados. Desafortunadamente, no hay un lenguaje de consultas estándard definido para los métodos de búsqueda, por eso cada vendedor implementará estos métodos de forma diferente. En los beans de entidad CMP, los métodos de búsqueda no están implementados con los métodos correspondientes en la clase del bean; los contenedores los implementan cuando el bean se desarrolla de una forma específica del vendedor. El desarrollador usará las herramientas 45 Análisis y Uso de Frameworks de Persistencia en Java específicas del vendedor para decirle al contenedor cómo se debería comportar un método de búsqueda particular. Algunos vendedores usaran herramientas de mapeo Objeto-Relacional para definir el comportamiento de un método de búsqueda mientras que otros sólo requerirán que el desarrollador introduzca el comando SQL apropiado. Interface Remoto Cada bean de entidad debe definir una interface remoto en adición del interface ‘home’. El interface remoto define los métodos de negocio del bean de entidad. A continuación está la definición del interface remoto del bean CustomerBean. import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Customer extends EJBObject { public Name getName() throws RemoteException; public void setName(Name name) throws RemoteException; public Address getAddress() throws RemoteException; public void setAddress( Address address) throws RemoteException; public CreditCard getCreditCard() throws RemoteException; public void setCreditCard(CreditCard card) throws RemoteException; } 46 Análisis y Uso de Frameworks de Persistencia en Java Este es un ejemplo de cómo una aplicación cliente podría usar el interface remoto para interactuar con un bean. Customer customer = // ... obtain a remote //reference to the bean // get the customer's address Address addr = customer.getAddress(); // change the zip code addr.zip = "56777"; // update the customer's address customer.setAddress(addr); Los métodos de negocio en el interface remoto se delegan a los correspondientes métodos de negocio en el ejemplar del bean. En el bean Customer, los métodos de negocios son todos simples accesores y modificadores, pero podrían ser más complicados. En otras palabras, los métodos de negicio de una entidad no están limitados a leer y escribir datos, también pueden realizar tareas de cálculo. Si, por ejemplo, los clientes tuvieran un programa de fidelidad que premiara el uso frecuente, podría haber un método para mejorar los premios en el programa, basado en una acumulación de estancias superiores a una noche. import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Customer extends EJBObject { public MembershipLevel addNights(int nights_stayed) throws RemoteException; 47 Análisis y Uso de Frameworks de Persistencia en Java public MembershipLevel upgradeMembership() throws RemoteException; public MembershipLevel getMembershipLevel() throws RemoteException; ... Simple accessor business methods go here } Los métodos addNights() y upgradeMembership() son más sofisticados que los sencillos métodos accesores. Aplican reglas de negocio para cambiar el nivel del premio y van más allá de leer y escribir datos. Métodos de RetroLlamada (CallBack Methods) La clase bean define métodos create que corresponden con métodos del interface home y métodos de negocio que corresponden con métodos del interface remoto. La clase bean también implementa un conjunto de métodos callback que permiten al contenedor notificar al bean los eventos de su ciclo de vida. Los métodos de retrollamada están definidos en el interface javax.ejb.EntityBean que es implementado por todos los beans de entidad, incluyendo la clase CustomerBean. El interface EntityBean tiene la siguiente definición. Se puede observar que la clase del bean implementa estos métodos. public interface javax.ejb.EntityBean { public void setEntityContext(); public void unsetEntityContext(); public void ejbLoad(); public void ejbStore(); 48 Análisis y Uso de Frameworks de Persistencia en Java public void ejbActivate(); public void ejbPassivate(); public void ejbRemove(); } El método setEntityContext() proporciona al bean un interface con el contenedor llamado el EntityContext. Este interface contiene métodos para obtener información sobre el contexto bajo el que opera el bean en un momento particular. El interface EntityContext se usa para acceder a información de seguridad sobre su llamador; para determinar el estado de la transación actual o para forzar a deshacer una transación; o para obtener una referencia al propio bean, su interface home o su clave primaria. Este interface sólo se configura una vez en la vida de un ejemplar de bean de entidad, por eso su referencia debería ponerse en uno de los campos de ejemplar del bean si la vamos a necesitar más tarde. El bean Customer de arriba no usa EntityContext, pero podría hacerlo. Por ejemplo, podría usarlo para validad al llamador en un rol de seguridad particular. Abajo tenemos un ejemplo, donde se usa el EntityContext para validad que el llamador es un Manager, el único rol (indentidad de seguridad) que puede seleccionar el tipo de la tarjeta de crédito de un cliente para que sea una tarjeta mundial, no una tarjeta limitada. (A los clientes con esta tarjeta se le ofrecen servicios especiales). 49 Análisis y Uso de Frameworks de Persistencia en Java import javax.ejb.EntityBean; public class CustomerBean implements EntityBean { int Address Name CreditCard EntityContext customerID; myAddress; myName; myCreditCard; ejbContext; // CALLBACK METHODS public void setEntityContext( EntityContext cntx) { ejbContext = cntx } public void unsetEntityContext() { ejbContext = null; } // BUSINESS METHODS public void setCreditCard(CreditCard card) { if (card.type.equals("WorldWide")) if (ejbContext.isCallerInRole( "Manager")) myCreditCard = card; else throw new SecurityException(); else myCreditCard = card; } public CreditCard getCreditCard() { return myCreditCard; } ... } El método unsetEntityContext() se usa al final del ciclo de vida del bean - antes de que el ejemplar sea sacado de la memoria -- para quitar la refencia del EntityContext y realizar cualquier pequeña tarea de limpieza. 50 Análisis y Uso de Frameworks de Persistencia en Java Los métodos ejbLoad() y ejbStore() en entidades CMP se invocan cuando el estado del bean de entidad está siendo sincronizado con la base de datos. El ejbLoad() se invoca justo después de que el contenedor haya refrescado los campos del bean manejados por contenedor con su estado desde la base de datos. El método ejbStore() se invoca justo antes de que el contenedor escriba los campos del bean manejados por contenedor en la base de datos. Estos métodos se usan para modificar datos que están siendo sincronizados. Los métodos podrían usarse, por ejemplo, para comprimir los datos antes de almacenarlos y descomprimirlos cuando son recuperados de la base de datos. En el bean Customer los métodos ejbLoad() y ejbStore() podrían usarse para converitir objetos dependientes (Name, Address, CreditCard) a sencillos objetos String y tipos primitivos, si el contenedor EJB no es lo suficientemente sofisticado para mapear los objetos dependientes a la tabla CUSTOMER. Abajo tenemos un ejemplo de como se podría modificar el Bean. import javax.ejb.EntityBean; public class CustomerBean implements EntityBean { //container-managed fields int customerID; String lastName; String firstName; String middleName; ... // not-container-managed fields Name myName; Address myAddress; CreditCard myCreditCard; // BUSINESS METHODS 51 Análisis y Uso de Frameworks de Persistencia en Java public Name getName() { return myName; } public void setName(Name name) { myName = name; } ... public void ejbLoad() { if (myName == null) myName = new Name(); myName.lastName = lastName; myName.firstName = firstName; myName.middleName = middleName; ... } public void ejbStore() { lastName = myName.lastName; firstName = myName.firstName; middleName = myName.middleName; ... } } Los métodos ejbPassivate() y ejbActivate() se invocan sobre el bean justo antes de que el bean sea "pasivizado" o justo después de que se activado, respectivamente. "Pasivizado" en un bean de entidad significa que el ejemplar del bean ha sido des-asociado con su referencia remota para que el contenedor pueda eliminarlo de la memoria o reutilizarlo. Es una medida de conservación de recursos que emplea el contenedor para reducir el número de ejemplares en memoria. Un bean podría ser "pasivizado" si no ha sido utilizado durante un tiempo o como una operación normal realizada por el contenedor para maximizar la reutilización del los recursos. Algunos contenedores eliminarán los beans de la memoria, mientras que otros reutilizarán ejemplares para otros interfaces remotos más activos. Los métodos ejbPassivate() y ejbActivate() 52 Análisis y Uso de Frameworks de Persistencia en Java proporcionan al bean una notificación cuando está apunto de ser "pasivizado" (des-asociado con la referencia remota) o activado (asociado con un referencia remota). PERSISTENCIA CONTROLADA POR EL BEAN (BMP) En un bean enterprise con la persistencia manejada por el Bean (BMP) maneja la sincronización de su estado con la base de datos directamente. El Bean usa el API de bases de datos (normalmente JDBC) para leer y escribir sus campos en la base de datos, pero el contenedor le dice cuando hacer cada operación de sincronización y maneja las transacciones por el bean automáticamente. La persistencia manejada por el bean le da al desarrollador de bean la flexibilidad de realizar operaciones de persistencia que son demasiado complicadas para el contenedor o para usar una fuente de datos que no está soportada por el contenedor -- por ejemplo bases de datos legales y personalizadas. En esta sección, modificaremos la clase CustomerBean para crear un bean con persistencia manejada por el Bean (BMP). Esta modificación no impactará en absoluto en los interfaces home o remoto. De hecho, no modificaremos directamente el CustomerBean original. En su lugar, lo 53 Análisis y Uso de Frameworks de Persistencia en Java cambiaremos a persistencia manejada por el bean extendiendo el bean y sobreescribiendo la clase para crear una entidad BMP. En la mayoría de los casos, no extenderemos el bean para hacerlo BMP, sólo implementaremos el bean como BMP directamente. Esta estrategia (extender el bean CMP) se hace por dos razones: permite al bean ser un bean CMP o BMP; y acorta significativamente la cantidad de código a mostrar. Abajo tenemos la definición de la clase BMP. public class CustomerBean_BMP extends CustomerBean { public void ejbLoad() { // override implementation } public void ejbStore() { // override implementation } public void ejbCreate() { // override implementation } public void ejbRemove() { // override implementation } private Connection getConnection() { // new helper method } } Con beans BMP, los métodos ejbLoad() y ejbStore() los usa el contenedor de forma diferente a como lo hacía con un bean CMP. En BMP, estos métodos contienen código para leer los datos de la base de datos y para escribir los cambios en la base de datos, respectivamente. Estos métodos se llaman sobre el bean cuando el servidor EJB decide que es un buen momento para leer o escribir los datos. 54 Análisis y Uso de Frameworks de Persistencia en Java El bean CustomerBean_BMP maneja su propia persistencia. En otras palabras, los métodos ejbLoad() y ejbStore() deben incluir lógica de acceso a base de datos para que el bean pueda cargar y almacenar sus datos cuando el contenedor EJB se lo diga. El contenedor ejecutará automátiamente los métodos ejbLoad() y ejbStore() cuando crea apropiado. El método ejbLoad() se invoca normalmente al principio de una transación, justo antes de que el contenedor delege un método de negocio del bean. El código mostrado abajo muestra cómo implementar el método ejbLoad() usando JDBC. import java.sql.Connection; public class CustomerBean_BMP extends CustomerBean { public void ejbLoad() { Connection con; try { Integer primaryKey = (Integer)ejbContext.getPrimaryKey(); con = this.getConnection(); Statement sqlStmt = con.createStatement("SELECT * FROM Customer " + " WHERE customerID = " + primaryKey.intValue()); ResultSet results = sqlStmt.executeQuery(); if (results.next()) { // get the name information //from the customer table myName = new Name(); myName.first = results.getString(" FIRST_NAME"); myName.last = results.getString(" LAST_NAME"); myName.middle = results.getString(" MIDDLE_NAME"); // get the address information from //the customer table myAddress = new Address(); myAddress.street = 55 Análisis y Uso de Frameworks de Persistencia en Java results.getString("STREET"); myAddress.city = results.getString("CITY"); myAddress.state = results.getString("STATE"); myAddress.zip = results.getInt("ZIP"); // get the credit card information //from the customer table myCreditCard = new CreditCard(); myCreditCard.number = results.getString("CREDIT_NUMBER"); myCreditCard.expDate = results.getString("CREDIT_DATE"); myCreditCard.type = results.getString("CREDIT_TYPE"); myAddress.name = results.getInt("CREDIT_NAME"); } } catch (SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } } El método ejbLoad(), usa la referencia ejbContext() hacia el EntityContext del bean para obtener la clave primaria del ejemplar. Esto asegura que usamos el índice correcto en la base de datos. Obviamente, el CustomerBean_BMP necesitará usar los métodos heredados setEntityContext() y unsetEntityContext(). El método ejbStore() es invocado sobre el bean, al final de la transación, justo antes de que el contenedor intente enviar todos los cambios a la base de datos. import java.sql.Connection; public class CustomerBean_BMP extends CustomerBean { 56 Análisis y Uso de Frameworks de Persistencia en Java public void ejbLoad() { ... read data from database } public void ejbStore() { Connection con; try { Integer primaryKey = (Integer)ejbContext.getPrimaryKey(); con = this.getConnection(); PreparedStatement sqlPrep = con.prepareStatement( "UPDATE customer set " + "last_name = ?, first_name = ?, middle_name = ?, " + "street = ?, city = ?, state = ?, zip = ?, " + "card_number = ?, card_date = ?, " + "card_name = ?, card_name = ?, " + "WHERE id = ?" ); sqlPrep.setString(1,myName.last); sqlPrep.setString(2,myName.first); sqlPrep.setString(3,myName.middle); sqlPrep.setString(4,myAddress.street); sqlPrep.setString(5,myAddress.city); sqlPrep.setString(6,myAddress.state); sqlPrep.setString(7,myAddress.zip); sqlPrep.setInt(8, myCreditCard.number); sqlPrep.setString(9, myCreditCard.expDate); sqlPrep.setString(10, myCreditCard.type); sqlPrep.setString(11, myCreditCard.name); sqlPrep.setInt(12,primaryKey.intValue()); sqlPrep.executeUpdate(); } catch (SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } } En ambos métodos ejbLoad() y ejbStore() el bean sincroniza su propio estado con la base de datos usando JDBC. El código muestra que el bean obtiene sus conexiones a la base de datos desde una invocación misteriosa al método this.getConnection(). Un método todavía no implementado. El método getConnection() no es un método estándard EJB, sólo una ayuda privada 57 Análisis y Uso de Frameworks de Persistencia en Java implementada para ocultar los mecanismos de obtención de conexiones a bases de datos. Este es del método getConnection(). import java.sql.Connection; public class CustomerBean_BMP extends CustomerBean { public void ejbLoad() { ... read data from database } public void ejbStore() { ... write data to database } private Connection getConnection() throws SQLException { } InitialContext jndiContext = new InitialContext(); DataSource source = (DataSource) jndiContext.lookup(" java:comp/env/jdbc/myDatabase"); return source.getConnection(); } Las conexiones a bases de datos se obtienen desde el contenedor usando un contexto JNDI por defecto llamado "JNDI Environment Naming Context" (ENC). El ENC proporciona acceso a almacenes de conexiones JDBC transaccionales a través de la factoría de conexiones estándar, del tipo javax.sql.DataSource. En BMP, los métodos ejbLoad() y ejbStore() los invoca el contenedor para sincronizar el ejemplar del bean con los datos en la base de datos. Para insertar y eliminar entidades en la base de datos, se implementan los métodos ejbCreate() y ejbRemove() con una lógica de acceso a bases de datos muy similar. Los métodos ejbCreate() se implementan para insertar un nuevo registro 58 Análisis y Uso de Frameworks de Persistencia en Java en la base de datos y los métodos ejbRemove() para eliminar los datos de una entidad de la base de datos. Estos métodos de una entidad son invocados por el contenedor en respuesta a invocaciones a sus correspondientes métodos en los interface home y remoto. Estas son las implementaciones de estos métodos. public void ejbCreate(Integer id) { this.customerID = id.intValue(); Connection con; try { con = this.getConnection(); Statement sqlStmt = con.createStatement("INSERT INTO customer id VALUES (" + customerID + ")"); sqlStmt.executeUpdate(); return id; } catch(SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } public void ejbRemove() { Integer primaryKey = (Integer)ejbContext.getPrimaryKey(); Connection con; try { con = this.getConnection(); Statement sqlStmt = con.createStatement("DELETE FROM customer WHERE id = " primaryKey.intValue()); sqlStmt.executeUpdate(); } catch(SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } 59 Análisis y Uso de Frameworks de Persistencia en Java En BMP, la clase bean es responsable de implementar los métodos de búsqueda definidos en el interface home. Por cada método de búsqueda definido en el interface home debe haber el correspondiente método ejbFind() en la clase bean. Estos métodos localizan los registros del bean apropiado en la base de datos y devuelve sus claves primarias al contenedor. El contenedor convierte la clave primara en referencias a beans y los devuelve al cliente. Abajo tenemos un ejemplo de implementación del método ejbFindByPrimaryKey() en la clase CustomerBean_BMP, que corresponde al findByPrimaryKey() definido en el interface CustomerHome. public Integer ejbFindByPrimaryKey( Integer primaryKey) throws ObjectNotFoundException { Connection con; try { con = this.getConnection(); Statement sqlStmt = con.createStatement("SELECT * FROM Customer " + " WHERE customerID = " + primaryKey.intValue()); ResultSet results = sqlStmt.executeQuery(); if (results.next()) return primaryKey; else throw ObjectNotFoundException(); } catch (SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } Los métodos de búsqueda de entidades simples como el de arriba devuelven una sola clave primara y lanza una ObjectNotFoundException si no se 60 Análisis y Uso de Frameworks de Persistencia en Java localiza un registro correspondiente. Los métodos de búsqueda multi-entidad devuelven una colección (java.util.Enumeration o java.util.Collection) de claves primarias. El contenedor convierte la colección de claves primarias en una colección de referencias remotas, que son devueltas al cliente. Abajo tenemos un ejemplo de cómo el método ejbFindByZipCode() multi-entidad, que corresponde al método findByZipCode() definido en el interface CustomerHome, sería implementado en la clase CustomerBean_BMP. public Enumeration ejbFindByZipCode( int zipCode) { Connection con; try { con = this.getConnection(); Statement sqlStmt = con.createStatement("SELECT id FROM Customer " + " WHERE zip = " +zipCode); ResultSet results = sqlStmt.executeQuery(); Vector keys = new Vector(); while(results.next()){ int id = results.getInt("id"); keys.addElement(new Integer(id)); } return keys.elements(); } catch (SQLException sqle) { throw new EJBException(sqle); } finally { if (con!=null) con.close(); } } Si no se encuentran registros correspondientes, se devuelve una colección vacía al contenedor, que devuelve una colección vacía al cliente. 61 Análisis y Uso de Frameworks de Persistencia en Java Con la implementación de todos estos métodos y unos pocos cambios menores en el descriptor de desarrollo del bean, el CustomerBean_BMP esta listo para ser desplegado como una entidad BMP. 3.3.2. Tecnología Open Source: HIBERNATE – Funcionamiento Para comprender el funcionamiento de Hibernate se pretende describir los diversos pasos a seguir para construir una aplicación que permita realizar el proceso de persistencia. Primero, se creará un objeto Evento (Event) para persistirlo en la base de datos. A continuación, se verá cómo persistir el objeto Evento en la base de datos y realizar una transacción para obtenerlo de nuevo. Después se procederá a construir un archivo XML de mapeo para decirle a Hibernate qué es lo que deseamos persistir. A través de otro archivo XML se configurarán las propiedades de Hibernate y la base de datos. Por último se verá qué pasos se deben seguir para construir y ejecutar el sistema Lo primero que tenemos que hacer es configurar nuestro directorio de trabajo y poner en él todos los ficheros jar que necesitamos. Tenemos que descargar la distribución de Hibernate de su página de descarga. Extraer los jars necesarios desde el archivo de Hibernate. Los situaremos en un directorio lib 62 Análisis y Uso de Frameworks de Persistencia en Java bajo el directorio de trabajo, tu despliegue de directorios debería parecerese a esto: +lib cglib2.jar commons-logging.jar hibernate2.jar jta.jar odmg.jar commons-collections.jar dom4j.jar jdbc2_0-stdext.jar log4j.jar OBJETO: Definición del objeto Evento Lo primero será crear una clase que represente los eventos que se desean persistir. Esta será un simple Java Bean, que contenga algunas propiedades. package de.gloegl.road2hibernate; public class Event { private String title; private Date date; private Long id; public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } 63 Análisis y Uso de Frameworks de Persistencia en Java public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } La propiedad id es una identificador único para el Event. Todos los objetos persistentes necesitarán dicha id. Cuando se construyen aplicaciones Hibernate es recomendable mantener dichas ids únicas separadas de la lógica de la aplicación. Esto impide manipular la id en nuestro código, dejando que Hibernate se ocupe de ella. De ahí viene porqué el método set de la id es privado, permitiendo que Hibernate lo utilice (Hibernate podría acceder a los métodos set y get de propiedades para todas las visibilidades), pero se aísla del programador. Este fichero se debe situar en un directorio llamado src en nuestro directorio de trabajo. El directorio debería aparecer de esta forma: +lib <hibernate jars> +src +de +gloegl +road2hibernate Event.java 64 Análisis y Uso de Frameworks de Persistencia en Java PERSISTENCIA: 1º Creación del Objeto El siguiente código muestra cómo se crea un objeto Evento desde el método main: public static void main(String[] args) throws java.text.ParseException { EventManager instance = new EventManager(); if (args[0].equals("store")) { String title = args[1]; Date theDate = new Date(); instance.store(title, theDate); } System.exit(0); } Para este ejemplo se están pasando parámetros desde la línea de comandos y se podrán leer en ‘args[]’. Si el primer argumento de recibido por la aplicación es store, se toma el segundo argumento como un título, se crea un nuevo Date y se pasan los dos al método store, donde se realiza la transacción con la base de datos: private void store(String title, Date theDate) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); tx.commit(); session.close(); } catch (HibernateException e) { 65 Análisis y Uso de Frameworks de Persistencia en Java } e.printStackTrace(); } 2º Mostrar los objetos almacenados Modificando el método main, cuando el parámetro de entrada sea ‘list’, se mostrarán los eventos persistidos: public static void main(String[] args) throws java.text.ParseException { EventManager instance = new EventManager(); if (args[0].equals("store")) { String title = args[1]; Date theDate = new Date(); instance.store(title, theDate); } else if (args[0].equals("list")) { List events = instance.listEvents(); for (int i = 0; i<events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event " + theEvent.getTitle() + " Time: " + theEvent.getDate()); } } System.exit(0); } Se está llamando a listEvents() para imprimir todos los objetos Event contenidos en la lista devuelta. Al llamar a listEvents() es donde se realizan las transacciones: 66 Análisis y Uso de Frameworks de Persistencia en Java private List listEvents() { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); List result = session.find("from Event"); tx.commit(); session.close(); return result; } catch (HibernateException e) { throw new RuntimeException(e.getMessage()); } } EL FICHERO DE MAPEO Una vez definida la clase para persistir en la base de datos, hay que especificar a Hibernate cómo persistirla. Para ello es necesario construir un fichero de mapeo. El fichero de mapeo le dice a Hibernate qué debería almacenar en la base de datos y cómo. La estructura exterior de un fichero de mapeo se parece a esto: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> </hibernate-mapping> 67 Análisis y Uso de Frameworks de Persistencia en Java Entre las dos etiquetas <hibernate-mapping>, hay que incluir un elemento class, donde se declara a que clase se refiere este mapeo y a qué tabla de la base de datos SQL se debería mapear. La estructura interior del documento de mapeo se debería parecer a esto: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="de.gloegl.road2hibernate.Event" table="EVENTS"> </class> </hibernate-mapping> Se le ha indicado a Hibernate que persista la clase Event en la tabla EVENTS. Ahora hay que dar a Hibernate la propiedad a utilizar como identificador único: la propiedad id. Además hay que especificar cómo generar los ids. Incluyendo esto, así queda el fichero de mapeo: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="de.gloegl.road2hibernate.Event" table="EVENTS"> <id name="id" column="uid" type="long"> <generator class="increment"/> </id> </class> </hibernate-mapping> 68 Análisis y Uso de Frameworks de Persistencia en Java El elemento <id> es la declaración de la propiedad id. name="id" es el nombre de la propiedad. Hibernate usará los métodos getId y setId para acceder a ella. El atributo column le dice a Hibernate que cólumna de la tabla EVENTS contendrá el id. El atributo type le dice a Hibernate el tipo de la propiedad - en este caso un long. El elemento <generator> especifica la técnica que se usará para la generación de id. En este caso se usa un incremento. Finalmente se deben incluir las declaraciones para las propiedades persistentes en el fichero de mapeo: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="de.gloegl.road2hibernate.Event" table="EVENTS"> <id name="id" column="uid" type="long"> <generator class="increment"/> </id> <property name="date" type="timestamp"/> <property name="title" column="eventtitle"/> </class> </hibernate-mapping> 69 Análisis y Uso de Frameworks de Persistencia en Java Al principio, igual que en el elemento <id>, el atributo name del elemento <property> le dice a Hibernate que métodos get y set utilizar. Sin embargo, la propiedad title contiene un atributo column, y el atributo date no lo tiene. Esto es posible porque cuando no se utiliza el atributo column, Hibernate usará por defecto el nombre de la propiedad como el nombre de columna. Por otra parte, la propiedad title carece de un atributo type. Otra vez, Hibernate intentará determinar el tipo correcto por sí mismo. Sin embargo, algunas veces Hibernate simplemente no puede hacer esto y hay que especificar el tipo, como es el caso de la propiedad date. Hibernate no puede saber si la propiedad se mapeará a una columna date, timestamp o time, por eso se debe que especificar. El mapeo se sitúa en un fichero llamado Event.hbm.xml en el mismo directorio donde está la clase Event. Así quedaría la estructura del directorio de trabajo: +lib <hibernate jars> +src +de +gloegl +road2hibernate Event.java Event.hbm.xml 70 Análisis y Uso de Frameworks de Persistencia en Java CONFIGURACIÓN DE LA BASE DE DATOS Para configurar Hibernate se necesitan la información de la base de datos, en este caso HSQLDB, una base de datos SQL en-memoria basada en Java. Se debe copiar el fichero hsqldb.jar del directorio lib de la descarga al directorio lib dentro del directorio de trabajo, que quedará de esta forma: . +lib <hibernate jars> hsqldb.jar +src <Aquí va el fichero fuente y el de mapeo> Además se creará un directorio data justo debajo del directorio de trabajo, donde hsqldb almacenará sus ficheros. Ahora se puede configurar Hibernate utilizando un fichero XML, que se llamará hibernate.cfg.xml y que se situará directamente en el directorio src del directorio de trabajo. Así queda el fichero: <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//hibernate/hibernate Configuration DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd"> 71 Análisis y Uso de Frameworks de Persistencia en Java <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</proper ty> <property name="hibernate.connection.url">jdbc:hsqldb:data/test</property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.connection.password"></property> <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property> <property name="show_sql">true</property> <property name="transaction.factory_class"> net.sf.hibernate.transaction.JDBCTransactionFactory </property> <property name="hibernate.cache.provider_class"> net.sf.hibernate.cache.HashtableCacheProvider </property> <property name="hibernate.hbm2ddl.auto">update</property> <mapping resource="de/gloegl/road2hibernate/data/Event.hbm.xml"/> </session-factory> </hibernate-configuration> Los primeros cuatro elementos <property> contienen la configuración necesaria para la Conexión JDBC que utilizará Hibernate. La propiedad dialect especifica el SQLdialect que Hibernate deberá generar. Luego se especifica que Hibernate delegará las transacciones a la conexión JDBC subyacente y se asigna un proveedor de caché. La siguiente propiedad le dice a Hibernate que ajuste automáticamente las tablas en la base de datos de acuerdo al mapeos. Finalmente se le da el path al fichero de mapeo. 72 Análisis y Uso de Frameworks de Persistencia en Java CONSTRUCCIÓN Y EJECUCIÓN DEL SISTEMA 1º Construcción Por conveniencia, se crea un fichero batch en el directorio de trabajo que contenga todos los comandos necesarios para la complicación. Bajo Windows, este sería su contenido: javac -classpath .\lib\hibernate2.jar -d bin src\de\gloegl\road2hibernate\*.java copy /Y src\hibernate.cfg.xml bin copy /Y src\de\gloegl\road2hibernate\*.xml bin\de\gloegl\road2hibernate Este fichero se situará con el nombre de build.bat en nuestro directorio de trabajo. Finalmente se crea el subdirectorio bin en nuestro directorio de trabajo para situar ahí las clases compiladas. 2º Ejecución El código que viene a continuación es una clase que arrancará Hibernate. package de.gloegl.road2hibernate; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.HibernateException; import net.sf.hibernate.cfg.Configuration; public class EventManager { private SessionFactory sessionFactory; 73 Análisis y Uso de Frameworks de Persistencia en Java public EventManager() { try { System.out.println("Initializing Hibernate"); sessionFactory = Configuration().configure().buildSessionFactory(); System.out.println("Finished Initializing Hibernate"); } catch (HibernateException e) { e.printStackTrace(); } } new public static void main(String[] args) { EventManager instance = new EventManager(); System.exit(0); } } Esta clase simplemente crea un ejemplar de sí mismo, y crea un ejemplar de SessionFactory en su constructor. El fichero EventManager.java debe situarse en el directorio adecuado. La estructura de directorios sería la que figura a continuación: . +lib cglib2.jar commons-logging.jar hibernate2.jar jta.jar odmg.jar commons-collections.jar dom4j.jar jdbc2_0-stdext.jar log4j.jar hsqldb.jar +src +de +gloegl +road2hibernate Event.java Event.hbm.xml EventManager.java hibernate.cfg.xml +data 74 Análisis y Uso de Frameworks de Persistencia en Java build.bat Para ejecutar nuestra aplicación se crea otro fichero batch en el directorio de trabajo bajo el nombre de run.bat, con el siguiente contenido (todo en una línea): java -classpath .\lib\hibernate2.jar;.\lib\jta.jar;.\lib\commonslogging.jar;.\lib\hsqldb.jar; .\lib\cglib2.jar;.\lib\commonscollections.jar;.\lib\dom4j.jar;.\lib\odmg.jar; .\lib\jdbc2_0-stdext.jar;.\bin de.gloegl.road2hibernate.EventManager %1 %2 %3 %4 %5 Ahora se deben compilar todos los ficheros fuentes ejecutando el fichero build.bat desde el directorio de trabajo y lo ejecutamos utilizando el fichero run.bat. Esta sería la salida: Initializing Hibernate log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. Finished Initializing Hibernate 75 Análisis y Uso de Frameworks de Persistencia en Java 3.4.- EVALUACIÓN JDO vs. EJB Desde la aparición de JDO se ha especulado que esta tecnología podría sustituir a las EJB de entidad. Para entender esta afirmación se debe examinar que es exactamente un EJB de entidad. Como ya se ha explicado, los beans de entidad se dividen en dos categorías: persistencia manejada por contenedor (CMP) y persistencia manejada por bean (BMP). Los beans BMP contienen un código que puede almacenar el contenido del bean en un almacén de datos permanente. Los BMP tienden a ser independientes y no forman relaciones directas con otros beans BMP. No sería correcto decir que los beans BMP pueden ser sustituidos por JDO, puesto que un bean BMP hace uso directo de código JDBC. Esto viola uno de los principios de diseño de JDO ya que esta tecnología pretende abstraer al usuario de codificar con JDBC. Los beans CMP permiten manejar la persistencia al contenedor. El contenedor es cualquier servidor que esté ejecutando el bean, y se encarga de manejar todo el almacenamiento actual. Los beans CMP también pueden formar las típicas relaciones [1 – n] ó [n a m] con otros beans CMP. La función de un bean CMP es muy similar a la de JDO. Ambas tecnologías permiten persistir datos con una perspectiva orientada a objetos ya 76 Análisis y Uso de Frameworks de Persistencia en Java que siempre se persisten objetos y evitan tener que conocer los detalles de cómo los objetos están almacenados. JDO y CMP también son capaces de manejar relaciones entre objetos. Por tanto sí se puede hablar de que JDO puede sustituir a CMP. Se debe tener en cuenta el crecimiento de las tecnologías. Los beans CMP todavía necesitan aumentar su aceptación en la comunidad de programadores. La mayor parte de las mejoras y crecimiento en general de las EJBs ha sido en el área de sesión. CMP y JDO padecen los mismos problemas a la hora de ser acogidos ya que ambas tecnologías abstraen demasiado al programador de lo que realmente sucede a nivel SQL. A la hora de realizar consultas complicadas el programador debe dedicar mucho tiempo intentando descubrir cómo generar dicha consulta equivalente a la sentencia SQL. En estos casos los programadores preferirían haber programado en SQL desde un primer momento. JDO vs. JDBC/SQL Sustituir a JDBC no es exactamente lo que busca JDO de la misma forma que podría hacerlo con CMP. JDO puede ser realmente una buena capa en el nivel superior a JDBC. En la mayoría de las instancias de JDO, se debe especificar una fuente de datos JDBC que establezca una referencia a la base de datos que JDO va a estar manejando. Por tanto, comparar a JDO con JDBC es 77 Análisis y Uso de Frameworks de Persistencia en Java algo que se debe hacer si se duda entre usar directamente JDBC o permitir que JDO lo use en lugar del programador. Por una parte JDO libera al programador de la tarea de construir consultas SQL y de distribuir entre los atributos de un objeto Java los resultados obtenidos en un result set. Si se considera que la mayor parte de consultas JDBC se realizan con el fin de dar valores a los atributos de objetos Java, JDO debería ser una alternativa a tener en cuenta, puesto que en lugar de ejecutar una consulta y copiar los campos desde el result set de JDBC a objetos Java, JDO se puede hacer cargo de todo ello. Las críticas a JDO vienen precisamente por las grandes cantidades de accesos innecesarios que realiza para llevar a cabo su tarea. JDO debe coger su consulta JDOQL y convertirla en su consulta SQL correspondiente. Entonces, esta consulta SQL es enviada a la base de datos, los resultados son recibidos y almacenados en sus respectivos objetos. Si hubiera un gran número de relaciones entre los objetos, es muy fácil que, como consecuencia, JDO haya accedido a muchos más datos de los necesarios. Es evidente que JDBC siempre va a ser más rápido que JDO ya que es más directo. Será elección del programador si la comodidad en la forma de trabajar que le ofrece JDO compensa su peor rendimiento. Otro de los aspectos discutidos de JDO es la posibilidad de sustituir SQL por JDOQL. Cuando se usa JDBC se debe acudir a SQL para componer las consultas mientras que con JDO se usa JDOQL. JDOQL es un lenguaje de 78 Análisis y Uso de Frameworks de Persistencia en Java consultas basado en Java. Por una parte, JDOQL es mucho más sencillo de componer que SQL, especialmente cuando nos referimos a consultas sencillas. Sin embargo, no existen muchos programadores que dominen JDOQL. De momento, este problema va a seguir existiendo ya que, como se ha comentado anteriormente, JDO no ha sido muy acogido en la industria del desarrollo de software. La mayoría de los programadores dominan SQL, y además son capaces de construir consultas SQL muy optimizadas a pesar de tener una gran complejidad. Para muchos programadores, una herramienta que crea consultas automáticamente no es de gran utilidad, sobre todo si nos referimos a JDOQL, que solamente actúa en aplicaciones Java. Antes o después SQL será sustituido, pero para ello tendrá que llevarlo a cabo una tecnología más universal. Hibernate vs. JDO Hibernate se caracteriza por su completa transparencia para el programador. Al contrario que JDO, Hibernate se encarga de todo el proceso de persistencia. No hay que pasar por la tarea de ejecutar nada parecido al JDOEnhancer. Hibernate se encarga de hacer todo el proceso transparente, ya que basta con añadir sus librerías a la aplicación y rellenar su archivo de 79 Análisis y Uso de Frameworks de Persistencia en Java configuración para asignar la base de datos con la que se va a trabajar. Una vez dispuestos los ficheros de mapeo, se puede trabajar con la misma facilidad con la que se codifica con cualquier librería Java. Otro punto muy a su favor es que Hibernate mantiene la posibilidad de realizar sus consultas en SQL. El HQL es el lenguaje para consulta específico de Hibernate, al igual que el JDOQL es el lenguaje a través del cual se realizan las consultas cuando se trabaja con JDO. Para usar JDO, el JDOQL es indispensable, ofreciendo una gran comodidad al programador a la hora de componer consultas sencillas. Sin embargo, cuando se trata de realizar sentencias más complejas, un programador que domine SQL con un nivel alto seguramente eche de menos la alternativa estándar. Hibernate ofrece ambas posibilidades. 3.5.- CONCLUSIONES Se han encontrado muchos puntos de interés a través de la búsqueda de información para el análisis de las diferentes tecnologías. Entre tantos, cabe destacar la cantidad de usuarios que defienden una tecnología advirtiendo que es la que mejor se adapta a su sistema. 80 Análisis y Uso de Frameworks de Persistencia en Java El uso de los frameworks de persistencia se lleva a cabo, generalmente, cuando un equipo de programadores busca una alternativa para intentar mejorar el rendimiento de sus aplicaciones o para incrementar la sencillez de su codificación y portabilidad. Las tecnologías que se han propuesto brindan esa oportunidad a los usuarios, pero la clave a la hora de decidir sobre su implantación está en su forma de adaptarse al sistema, y esto depende de algunos factores que solamente se pueden identificar por medio de la experiencia obtenida al probar varias alternativas. Para mostrar la viabilidad de estas tecnologías se ha de escoger una de las alternativas estudiadas. Siendo coherentes con la reflexión precedente y con el apartado de evaluación, Hibernate es la alternativa más apropiada para ocuparse de la persistencia en la aplicación que se va a desarrollar en el siguiente capítulo. Estas son las razones fundamentales de por qué Hibernate se adapta a las necesidades que se presentan: o Es 100% orientado a objetos: Hibernate es capaz de persistir clases que siguen una orientación orientada a objetos, incluyendo herencia, polimorfismo, relaciones de asociación y composición, y colecciones. 81 Análisis y Uso de Frameworks de Persistencia en Java o Máxima transparencia: Hibernate es capaz de que el programador se olvide de la tarea extraordinaria que se realiza al trabajar con una base de datos. Todo el código para utilizar Hibernate será Java puro, ya que existen cómodos asistentes para crear los archivos de configuración y mapeo, que son XML. Además, en ninguna fase de su utilización (instalación, configuración, desarrollo y ejecución) será necesario escribir una sola instrucción en línea de comandos, como sucede con JDO a la hora de ejecutar el modificador de byte codes, el JDOEnhancer. o Ofrece HQL pero también permite SQL: Hibernate proporciona HQL, su propio lenguaje de consulta cuya sintaxis busca mayor sencillez en cada sentencia. Este lenguaje es similar a SQL, lo que aporta mucha facilidad a la hora de aprender sus cláusulas. Sin embargo, para los programadores que lo prefieran, Hibernate permite el uso de sentencias SQL, con el fin de proporcionar libertad de elección, sobre todo cuando hay que componer sentencias complejas. o Herramientas oficiales de ayuda al mapeo: Dentro de las diferentes herramientas oficiales que ofrece Hibernate para facilitar su implantación, se encuentra el Mapping Editor. Esta herramienta para la creación de los ficheros XML de mapeo proporciona auto-relleno de campos como el nombre de la 82 Análisis y Uso de Frameworks de Persistencia en Java clase, de los atributos, de la tabla correspondiente y de sus columnas. o Respaldo de una comunidad Open Source activa: Hibernate es un proyecto profesional Open Source y un componente crítico del catálogo de productos de JBoss Enterprise Middleware System (JEMS). JBoss es una división de Red Hat, y ofrece servicios de mantenimiento, consulta y entrenamiento para asistir a sus usuarios. 83 Análisis y Uso de Frameworks de Persistencia en Java 4- Diseño y Desarrollo de la Aplicación 84 Análisis y Uso de Frameworks de Persistencia en Java 4- DISEÑO Y DESARROLLO DE LA APLICACIÓN 4.1- OBJETIVOS Se pretende diseñar una aplicación que verifique la viabilidad de una de las alternativas de persistencia O/R. La aplicación consistirá en un portal Web que proporcione un interfaz mediante el cual se realicen múltiples accesos a una base de datos relacional. Dicho portal presentará los resultados solicitados de forma vistosa y ordenada. Con esto se pretende aplicar la tecnología a las actividades cotidianas empresariales. Puesto que el fin elemental del portal es la explotación de tablas y la distribución de su información por todos los departamentos interesados en una empresa, se pretende situar la aplicación en un entorno de intranet, de forma que solamente tengan acceso a ella los empleados. Para hacer del portal una herramienta adecuada para todo tipo de departamentos se deberán gestionar los perfiles de los usuarios con el fin de ofrecer los servicios correspondientes a cada uno de ellos. Finalmente, para ser coherentes con la motivación del proyecto, se deberá conseguir que la aplicación sea una herramienta intuitiva y de uso cotidiano por parte de empleados de cualquier departamento de la empresa, independientemente de su nivel de conocimientos. La aplicación deberá adaptarse a MySQL Server, siendo este el gestor de bases de datos que se va a utilizar. Se trabajará con MySQL por ser un sistema que admite 85 Análisis y Uso de Frameworks de Persistencia en Java varias conexiones simultáneas, además de ser Open Source. Además, MySQL está acompañado de herramientas como MySQL Administrator y MySQL Query Browser, que, gracias a sus cuidados interfaces, son de gran ayuda a la hora de gestionar las bases de datos. 4.2- DESCRIPCIÓN DE LA APLICACIÓN La aplicación a desarrollar consiste en un portal Web para uso cotidiano, que será accesible a través de la intranet en una pequeña empresa. Dado el carácter del proyecto, la aplicación se basa en una empresa ficticia, pero gracias a un diseño sencillo y sólido, añadiendo nuevas funcionalidades se podría adaptar a múltiples usos reales. La empresa en cuestión consiste en una clínica privada de alergología. El portal pretende servir de punto de encuentro entre todos los empleados, desde los médicos hasta el personal informático. Además de ser una fuente de información para todos los usuarios (noticias, enlaces de interés, etc…) el portal proporciona servicios a los empleados, convirtiéndose en una útil herramienta para su trabajo cotidiano. Al existir diferentes tipos de empleados, el portal adoptará un aspecto determinado para cada perfil de usuario, ofreciendo a cada uno los servicios correspondientes. En este caso, el personal informático tomará un papel de 86 Análisis y Uso de Frameworks de Persistencia en Java administrador del portal, al servicio de las funcionalidades que están a disposición del personal médico y administrativo de la clínica. Para realizar un sencillo diseño de la aplicación Web se van a seguir las siguientes etapas: • Identificación de los requisitos funcionales mediante la definición de los Casos de Uso y su representación mediante diagramas. • Diseño externo mediante la asociación entre los Casos de Uso y los diseños de páginas Web correspondientes realizados con HTML. • Diseño interno a partir de los Casos de Uso, definiendo las herramientas, las clases Java y el modelo de datos. 4.3– REQUISITOS FUNCIONALES 4.3.1. Actores Participantes El portal está dirigido a todos los empleados de la clínica, entre los que se distinguen tres grupos con perfiles diferentes: a) Personal Informático (INF): 87 Análisis y Uso de Frameworks de Persistencia en Java Este grupo desempeña el papel de administrador del portal, ocupándose principalmente de la gestión de las cuentas. Por otra parte, debe tener acceso a otras funcionalidades con el fin de supervisar la integridad de los datos. b) Personal Médico (MED): En este grupo se encuentran los médicos de la clínica. Éstos tendrán acceso privilegiado a los datos de los pacientes que tengan asignados, así como a su historia médica. c) Personal Administrativo (AMD) Los administrativos son los empleados encargados de recibir a los clientes. De este modo gestionan los datos de todos los pacientes y los asocian con un médico determinado. 4.3.2. Casos de Uso Los requisitos funcionales de la aplicación vienen definidos por los siguientes casos de uso: 1. Inicio 1.1. Solicitud de cuenta 1.2. Identificación 2. Navegación básica 2.1. Consulta de noticias 2.2. Cambio de contraseña 2.3. Consulta de enlaces 3. Acceso a Tablas 88 Análisis y Uso de Frameworks de Persistencia en Java 3.1. Gestión de pacientes 3.1.1. Creación de paciente 3.1.2. Edición de paciente 3.1.3. Eliminación de paciente 4. Acceso a Pacientes 4.1. Gestión de alergias 4.1.1. Asignación de alergia 4.1.2. Edición de alergia 4.1.3. Eliminación de alergia 5. Acceso a Portal 5.1. Gestión de noticias 5.1.1. Creación de noticia 5.1.2. Edición de noticia 5.1.3. Eliminación de noticia 5.2. Gestión de cuentas 5.2.1. Activación/Desactivación de cuenta 5.2.2. Edición de cuenta 5.2.3. Eliminación de cuenta 6. Cierre de sesión 6.1. Cierre de Sesión 89 Análisis y Uso de Frameworks de Persistencia en Java DESCRIPCIÓN DETALLADA DE LOS CASOS DE USO 1. Inicio ESCENARIO: 1.1. Solicitud de cuenta ACTORES: INF, MED, ADM Precondición: Seleccionar la opción “Registrarse” en la pantalla de inicio. Flujo Primario: 1. El sistema pide los datos del usuario y su perfil. 2. El usuario introduce sus datos y envía el formulario. 3. El sistema valida los datos y advierte que la cuenta ha sido solicitada. Flujo Secundario: - El campo “Usuario” tiene un valor que ya existe: El sistema advierte del error en la pantalla y vuelve a pedir los datos. - La confirmación de contraseñas es incorrecta: El sistema advierte del error en la pantalla y vuelve a pedir los datos. - El campo “Dni” está vacío o no se ha seleccionado ningún perfil: El sistema vuelve a pedir los datos. ESCENARIO: 1.2. Identificación ACTORES: INF, MED, ADM Precondición: Flujo Primario: 1. El sistema pide los datos de identificación: Usuario y Contraseña. 2. El usuario introduce sus datos y envía el formulario. 90 Análisis y Uso de Frameworks de Persistencia en Java 3. El sistema valida los datos y da acceso al portal habilitando las opciones correspondientes según su perfil. Flujo Secundario: - Los datos introducidos son válidos pero la cuenta no ha sido activada: El sistema advierte del error y vuelve a pedir los datos. - Los datos introducidos no son válidos: El sistema advierte del error y vuelve a pedir los datos. 2. Navegación Básica ESCENARIO: 2.1. Consulta de noticias ACTORES: INF, MED, ADM Precondiciones: - El usuario acaba de acceder al portal. - El usuario ha seleccionado la opción “Noticias” en la barra de navegación. Flujo Primario: 1. El sistema muestra todas las noticias que se encuentran en la base de datos, ordenadas por Fecha descendientemente. Flujo Secundario: - ESCENARIO: 2.2. Cambio de contraseña ACTORES: INF, MED, ADM Precondición: El usuario ha seleccionado la opción “Usuario” en la barra de navegación. 91 Análisis y Uso de Frameworks de Persistencia en Java Flujo Primario: 1. El sistema pide la contraseña actual del usuario y la nueva contraseña. 2. El usuario introduce los datos y envía el formulario. 3. El sistema valida los datos y advierte que la contraseña ha cambiado. Flujo Secundario: - La contraseña actual o la confirmación de nueva contraseña es incorrecta: El sistema advierte del error y vuelve a pedir los datos. ESCENARIO: 2.3. Consulta de enlaces ACTORES: INF, MED, ADM Precondición: El usuario ha seleccionado la opción “Enlaces” en la barra de navegación. Flujo Primario: 1. El sistema muestra diversos enlaces de interés. 2. El usuario pulsa sobre un enlace, que se abrirá en otra ventana o pestaña de su navegador. Flujo Secundario: - 3. Acceso a Tablas 3.1. Gestión de Pacientes ESCENARIO: 3.1.1. Creación de paciente ACTORES: INF, ADM 92 Análisis y Uso de Frameworks de Persistencia en Java Precondición: El usuario ha seleccionado la opción “Nuevo Paciente” dentro de la sección “Tablas” de la barra de navegación. Flujo Primario: 1. El sistema pide los datos del nuevo paciente y muestra los médicos disponibles en la base de datos. 2. El usuario introduce los datos del nuevo paciente, asignándole un médico y envía el formulario. 3. El sistema valida los datos y advierte que el paciente ha sido dado de alta. Flujo Secundario: - El dni del paciente ya ha sido registrado anteriormente: El sistema advierte del error y vuelve a pedir los datos. - No se ha asignado ningún médico: El sistema advierte del error y vuelve a pedir los datos. ESCENARIO: 3.1.2. Edición de paciente ACTORES: INF, ADM Precondición: El usuario ha seleccionado un paciente y pulsa sobre la opción “Editar” dentro de la sección “Tablas” de la barra de navegación. Flujo Primario: 1. El sistema muestra los datos del paciente y da opción a cambiarlos. 2. El usuario hace las modificaciones pertinentes y envía el formulario. 3. El sistema advierte que los datos del paciente se han actualizado. Flujo Secundario: - 93 Análisis y Uso de Frameworks de Persistencia en Java ESCENARIO: 3.1.3. Eliminación de paciente ACTORES: INF, ADM Precondición: El usuario ha seleccionado un paciente y pulsa sobre la opción “Borrar” dentro de la sección “Tablas” de la barra de navegación. Flujo Primario: 1. El sistema elimina el paciente seleccionado de la base de datos. Flujo Secundario: - 4. Acceso a Pacientes 4.1. Gestión de Alergias ESCENARIO: 4.1.1. Asignación de Alergia ACTORES: MED Precondición: El usuario ha seleccionado la opción “Pacientes” en la barra de navegación. Flujo Primario: 1. El sistema muestra los pacientes que tiene asignados el usuario. 2. El usuario selecciona un paciente y elige la opción “Ver Alergias”. 3. El sistema muestra las alergias que tiene asignadas el paciente. 4. El usuario elige la opción “Asignar Alergia”. 5. El sistema pide los datos de la alergia. 6. El usuario introduce los datos de la alergia y envía el formulario. 7. El sistema valida los datos y advierte que se ha asignado una alergia. Flujo Secundario: 94 Análisis y Uso de Frameworks de Persistencia en Java - No se ha especificado el tipo de alergia: El sistema advierte del error y vuelve a pedir los datos. - El tipo de alergia especificado ya ha sido asignado a ese paciente: El sistema advierte del error y vuelve a pedir los datos. ESCENARIO: 4.1.2. Edición de Alergia ACTORES: MED Precondición: El usuario ha seleccionado la opción “Pacientes” en la barra de navegación. Flujo Primario: 1. El sistema muestra los pacientes que tiene asignados el usuario. 2. El usuario selecciona un paciente y elige la opción “Ver Alergias”. 3. El sistema muestra las alergias que tiene asignadas el paciente. 4. El usuario selecciona un tipo de alergia y elige la opción “Ver Detalles”. 5. El sistema muestra los datos de la alergia. 6. El usuario modifica los datos de la alergia y envía el formulario. 7. El sistema valida los datos y advierte que se han modificado los detalles. Flujo Secundario: - ESCENARIO: 4.1.3. Eliminación de Alergia ACTORES: MED Desencadenante: El usuario ha seleccionado la opción “Pacientes” en la barra de navegación. Flujo Primario: 1. El sistema muestra los pacientes que tiene asignados el usuario. 95 Análisis y Uso de Frameworks de Persistencia en Java 2. El usuario selecciona un paciente y elige la opción “Ver Alergias”. 3. El sistema muestra las alergias que tiene asignadas el paciente. 4. El usuario selecciona un tipo de alergia y elige la opción “Borrar”. 5. El sistema elimina la alergia asignada y vuelve a mostrar la lista. Flujo Secundario: - 5. Acceso a Portal 5.1. Gestión de Noticias ESCENARIO: 5.1.1. Creación de noticia ACTORES: INF Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario selecciona la opción “Gestión de Noticias”. 3. El sistema muestra las noticias existentes. 4. El usuario selecciona la opción “Nueva Noticia”. 5. El sistema pide los datos de la nueva noticia. 6. El usuario introduce los datos y envía el formulario. 7. El sistema crea la noticia. Flujo Secundario: - ESCENARIO: 5.1.2. Edición de noticia ACTORES: INF 96 Análisis y Uso de Frameworks de Persistencia en Java Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario elige la opción “Gestión de Noticias”. 3. El sistema muestra las noticias existentes. 4. El usuario selecciona una noticia y elige la opción “Editar”. 5. El muestra los datos de la noticia seleccionada y permite su modificación. 6. El usuario modifica los datos de la noticia y envía el formulario. 7. El sistema actualiza los datos y advierte que la noticia ha sido modificada. Flujo Secundario: - ESCENARIO: 5.1.3. Eliminación de noticia ACTORES: INF Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario elige la opción “Gestión de Noticias”. 3. El sistema muestra las noticias existentes. 4. El usuario selecciona una noticia y elige la opción “Borrar”. 5. El sistema elimina la noticia y vuelve a mostrar la lista. Flujo Secundario: - 5.2. Gestión de Cuentas de Usuario 97 Análisis y Uso de Frameworks de Persistencia en Java ESCENARIO: 5.2.1. Activación / Desactivación de cuenta ACTORES: INF Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario elige la opción “Gestión de Cuentas”. 3. El sistema muestra las cuentas existentes, incluyendo las que están desactivadas. 4. El usuario selecciona una cuenta y elige la opción “Activar / Desactivar”. 5. El sistema cambia el estado de actividad de la cuenta y vuelve a mostrar la lista. Flujo Secundario: - ESCENARIO: 5.2.2. Edición de cuenta ACTORES: INF Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario elige la opción “Gestión de Cuentas”. 3. El sistema muestra las cuentas existentes, incluyendo las que están desactivadas. 4. El usuario selecciona una cuenta y elige la opción “Editar”. 5. El sistema muestra los datos de la cuenta y permite su modificación, exceptuando el perfil. 6. El usuario modifica los datos pertinentes y envía el formulario. 98 Análisis y Uso de Frameworks de Persistencia en Java 7. El sistema valida los datos y advierte que la cuenta ha sido actualizada. Flujo Secundario: - ESCENARIO: 5.2.3. Eliminación de cuenta ACTORES: INF Precondición: El usuario ha seleccionado la opción “Portal” en la barra de navegación. Flujo Primario: 1. El sistema muestra las opciones de gestión del portal. 2. El usuario elige la opción “Gestión de Cuentas”. 3. El sistema muestra las cuentas existentes, incluyendo las que están desactivadas. 4. El usuario selecciona una cuenta y elige la opción “Borrar”. 5. El sistema elimina la cuenta y vuelve a mostrar la lista. Flujo Secundario: - 6. Cierre de Sesión ESCENARIO: 6.1. Cierre de Sesión ACTORES: INF, MED, ADM Precondición: El usuario ha seleccionado la opción “Cerrar Sesión” en la barra de navegación. Flujo Primario: 1. El sistema muestra la pantalla inicial pidiendo los datos de identificación. Flujo Secundario: 99 Análisis y Uso de Frameworks de Persistencia en Java DIAGRAMAS DE LOS CASOS DE USO • Casos de uso del Personal Informático (INF): 100 Análisis y Uso de Frameworks de Persistencia en Java • Casos de uso del Personal Administrativo (ADM): 101 Análisis y Uso de Frameworks de Persistencia en Java • Casos de uso del Personal Médico (MED): 102 Análisis y Uso de Frameworks de Persistencia en Java 4.4- DISEÑO EXTERNO 4.4.1. Diseño del Interfaz del Usuario • Inicio - Identificación Caso de uso: 1.2. Login de Acceso Esta es la pantalla de inicio de la aplicación. A través del botón “Registrarse” en la parte inferior derecha, el sistema pedirá los datos a un nuevo usuario para que realice su solicitud de una nueva cuenta. 103 Análisis y Uso de Frameworks de Persistencia en Java - Solicitud de Cuenta Caso de uso: 1.1. Solicitud de Cuenta Después de realizar la solicitud el sistema conducirá de nuevo al inicio de la aplicación, donde se pedirán otra vez los datos de identificación. 104 Análisis y Uso de Frameworks de Persistencia en Java • Navegación básica - Consulta de noticias Caso de uso: 2.1. Consulta de Noticias Este es el aspecto que tendrá el portal. Desde aquí se accede a todas las funcionalidades disponibles para el usuario a través de la barra de navegación situada en el lateral izquierdo. En la parte superior derecha, el sistema muestra el perfil y el nombre del usuario que ha iniciado la sesión. La aplicación da la bienvenida con las últimas noticias ordenadas por fecha descendentemente. Esta información será accesible en cualquier momento desde la opción “Noticias” que aparece en la barra de navegación. 105 Análisis y Uso de Frameworks de Persistencia en Java - Cambio de contraseña Caso de uso: 2.2. Cambio de Contraseña Esta funcionalidad se ubicará en el apartado “Usuario” de la barra de navegación. Como se aprecia en la imagen, el sistema pedirá que el usuario introduzca de nuevo su contraseña actual por cuestiones de seguridad. 106 Análisis y Uso de Frameworks de Persistencia en Java - Consulta de enlaces Caso de uso: 2.3. Consulta de Enlaces En el apartado “Enlaces” los usuarios tendrán acceso a diversos enlaces de utilidad para su uso cotidiano. Al pulsar sobre uno de ellos, la página Web correspondiente se abrirá en una nueva ventana o pestaña del navegador. 107 Análisis y Uso de Frameworks de Persistencia en Java • Gestión de pacientes - Creación de paciente Caso de uso: 3.1.1. Creación de Paciente Este es el formulario que presentará el sistema al usuario para recibir los datos de un nuevo paciente. Los médicos que figuren en la base de datos aparecerán en la casilla “Médico” para realizar la asignación del paciente. 108 Análisis y Uso de Frameworks de Persistencia en Java - Edición de paciente Caso de uso: 3.1.2. Edición de Paciente Este formulario muestra los datos del paciente correspondiente, así como el médico que tiene asignado. Se permitirá el cambio de médico entre otras cosas. 109 Análisis y Uso de Frameworks de Persistencia en Java - Eliminación de paciente Caso de uso: 3.1.3. Eliminación de paciente Esta será la pantalla que presenta el sistema para gestionar los pacientes. El sistema muestra una lista con todos los pacientes registrados en la base de datos. Desde aquí se accederá a los formularios para la creación y edición de los pacientes. Para eliminar un paciente, se debe seleccionar uno de ellos y pulsar el botón “Borrar” situado en la parte superior de la lista. 110 Análisis y Uso de Frameworks de Persistencia en Java • Gestión de alergias - Asignación de alergia Caso de uso: 4.1.1. Asignación de Alergia Mediante este formulario el médico asignará un tipo de alergia a uno de sus pacientes. El sistema no permitirá registrar dos veces un tipo de alergia determinado a un mismo paciente. 111 Análisis y Uso de Frameworks de Persistencia en Java - Edición de alergia Caso de uso: 4.1.2. Edición de Alergia A través de este formulario el médico podrá ver los detalles de la alergia correspondiente, así como cambiar el tratamiento. 112 Análisis y Uso de Frameworks de Persistencia en Java - Eliminación de alergia Caso de uso: 4.1.3. Eliminación de Alergia Esta será la pantalla que presenta el sistema para gestionar las alergias. El sistema muestra una lista con las alergias asignadas a un paciente determinado. Desde aquí se accederá a los formularios para la asignación y edición de las alergias. Para eliminar una alergia, se debe seleccionar una de ellas y pulsar el botón “Borrar” situado en la parte superior de la lista. 113 Análisis y Uso de Frameworks de Persistencia en Java • Gestión del Portal (Noticias y Cuentas de Usuario) Esta es la pantalla que presenta el sistema al informático para gestionar el portal. Desde aquí se accederá a la “Gestión de Noticias” y a la “Gestión de Cuentas”. 114 Análisis y Uso de Frameworks de Persistencia en Java - Creación de noticia Caso de uso: 5.1.1. Creación de Noticia Este es el formulario que presentará el sistema al informático para recibir los datos de una nueva noticia. 115 Análisis y Uso de Frameworks de Persistencia en Java - Edición de noticia Caso de uso: 5.1.2. Edición de Noticia A través de este formulario el informático podrá ver los detalles de la noticia correspondiente y modificar cualquiera de sus datos. 116 Análisis y Uso de Frameworks de Persistencia en Java - Eliminación de noticia Caso de uso: 5.1.3. Eliminación de Noticia Esta será la pantalla que presenta el sistema para gestionar las noticias. El sistema muestra una lista con las noticias existentes en la base de datos. Desde aquí se accederá a los formularios para la creación y edición de las noticias. Para eliminar una noticia, se debe seleccionar una de ellas y pulsar el botón “Borrar” situado en la parte superior de la lista. 117 Análisis y Uso de Frameworks de Persistencia en Java - Activación/Desactivación de cuenta y Eliminación de cuenta Casos de uso: 5.2.1. Activación / Desactivación de Cuenta 5.2.3. Eliminación de cuenta Esta será la pantalla que presenta el sistema para gestionar las cuentas. El sistema muestra una lista con las cuentas de usuario, incluyendo las que no están activas. Desde aquí se accederá al formulario para la edición de las cuentas. Para eliminar una noticia o cambiar su estado de actividad, se debe seleccionar una de ellas y pulsar el botón “Borrar” o “Activar / Desactivar” respectivamente. 118 Análisis y Uso de Frameworks de Persistencia en Java - Edición de cuenta Caso de uso: 5.2.2. Edición de Cuenta A través de este formulario el informático podrá ver los detalles de la cuenta de usuario correspondiente y modificar cualquiera de sus. Además, el sistema mostrará su perfil, su dni y su estado de actividad en la parte superior. Para cambiar el perfil o el dni, el usuario correspondiente a la cuenta deberá realizar una nueva solicitud. 119 Análisis y Uso de Frameworks de Persistencia en Java • Cierre de Sesión Caso de uso: 6.1. Cierre de Sesión Con el fin de abandonar el portal el usuario deberá elegir la opción “Cerrar Sesión” situada en la parte inferior de la barra de navegación. El sistema volverá a pedir los datos de identificación en la página de inicio de la aplicación. 120 Análisis y Uso de Frameworks de Persistencia en Java 4.5- DISEÑO INTERNO 4.5.1- JSP y Servlets Para hacer posible el desarrollo de la aplicación Web se va a diseñar una estructura unida de páginas dinámicas JSP. Las páginas JSP permiten ejecutar código Java con el fin de generar HTML dinámicamente. Esto será esencial, entre otras cosas, para mostrar resultados de consultas a las bases de datos y para mostrar mensajes de error cuando sea necesario. Para la recepción de los formularios y la validación de sus datos se utilizarán Servlets. Mediante algoritmos de validación y accesos a las bases de datos, los diversos Servlets se encargarán de ejecutar un código u otro dependiendo de los datos introducidos en los formularios. Posteriormente devuelven el control a las páginas JSP. Para realizar el diseño se analizarán los diferentes subsistemas de casos de uso y se definirán las páginas JSP y Servlets necesarios para su viabilidad. Los subsistemas son: Inicio, Navegación Básica, Gestión de Pacientes, Gestión de Tablas, Gestión de Alergias, Gestión de Noticias y Gestión de Cuentas. 121 Análisis y Uso de Frameworks de Persistencia en Java Subsistema: INICIO Caso de Uso: 1.1. Solicitud de cuenta - JSP: registro.jsp - Servlet asociado: ValidaRegistro.java Caso de Uso: 1.2. Identificación - JSP: index.jsp - Servlet asociado: ValidaUsuario.java Subsistema: NAVEGACIÓN BÁSICA Caso de Uso: 2.1. Consulta de Noticias - JSP: home.jsp - Servlet asociado: - Caso de Uso: 2.2. Cambio de Contraseña - JSP: usuario.jsp - Servlet asociado: CambiaPass.java Caso de Uso: 2.3. Consulta de Enlaces - JSP: enlaces.jsp - Servlet asociado: - 122 Análisis y Uso de Frameworks de Persistencia en Java Subsistema: GESTIÓN DE PACIENTES Caso de Uso: 3.1.1. Creación de Paciente - JSP: creacionPaciente.jsp - Servlet asociado: CreaPaciente.java Caso de Uso: 3.1.2. Edición de Paciente - JSP: edicionPaciente.jsp - Servlet asociado: EditaPaciente.java Caso de Uso: 3.1.3. Eliminación de Paciente - JSP: tablas.jsp - Servlet asociado: GestionaPaciente.java Subsistema: GESTIÓN DE ALERGIAS Caso de Uso: 4.1.1. Asignación de Alergia - JSP: creacionAlergia.jsp - Servlet asociado: CreaAlergia.java Caso de Uso: 4.1.2. Edición de Alergia - JSP: edicionAlergia.jsp - Servlet asociado: EditaAlergia.java 123 Análisis y Uso de Frameworks de Persistencia en Java Caso de Uso: 4.1.3. Eliminación de Alergia - JSP: pacientesAlergias.jsp - Servlet asociado: GestionaEnfermedad.java Subsistema: GESTIÓN DE NOTICIAS Caso de Uso: 5.1.1. Creación de Noticia - JSP: creacionNoticia.jsp - Servlet asociado: CreaNoticia.java Caso de Uso: 5.1.2. Edición de Noticia - JSP: edicionNoticia.jsp - Servlet asociado: EditaNoticia.java Caso de Uso: 5.1.3. Eliminación de Noticia - JSP: portalNoticias.jsp - Servlet asociado: GestionaNoticia.java Subsistema: GESTIÓN DE CUENTAS Caso de Uso: 5.2.1. Activación / Desactivación de Cuenta - JSP: portalCuentas.jsp 124 Análisis y Uso de Frameworks de Persistencia en Java - Servlet asociado: GestionaCuenta.java Caso de Uso: 5.2.2. Edición de Cuenta - JSP: edicionCuenta.jsp - Servlet asociado: EditaCuenta.java Caso de Uso: 5.2.3. Eliminación de Cuenta - JSP: portalCuentas.jsp - Servlet asociado: GestionaCuenta.java 125 Análisis y Uso de Frameworks de Persistencia en Java 4.5.2- Diagrama de Navegación En el siguiente diagrama viene reflejada la intaracción entre todas las páginas JSP que forman el sistema. Todas las líneas de conexión implican navegación bidireccional, exceptuando aquellas que terminan con punta de flecha. Las páginas JSP situadas dentro del recuadro gris sólo son accesibles después de realizar la identificación. 126 Análisis y Uso de Frameworks de Persistencia en Java 4.5.3- Diagramas de clases La aplicación va a trabajar con dos tipos de clases: a) Clases objeto b) Clases de acceso a la base de datos Las clases objeto, que van a situarse en un paquete llamado “obj”, vienen definidas por los subsistemas de gestión, debiendo crear una clase por cada uno de ellos: GESTIÓN DE PACIENTES Paciente.java GESTIÓN DE ALERGIAS Alergia.java GESTIÓN DE NOTICIAS Noticia.java GESTIÓN DE CUENTAS DE USUARIO Usuario.java Sin embargo, las clases de acceso a base de datos, que van a situar se en un paquete llamado “util”, vienen definidas por la necesidad de persistencia de cada uno de los objetos. En un principio, para desarrollar una primera versión del portal, utilizaremos clases DAO para trabajar con JDBC. En el capítulo 5 se verá cómo se va a sustituir JDBC por un framework de persistencia. Hasta entonces se construirá la aplicación con JDBC, y éstas serán sus clases DAO: 127 Análisis y Uso de Frameworks de Persistencia en Java Paciente.java DAO_paciente.java Alergia.java DAO_alergia.java Noticia.java DAO_noticia.java Usuario.java DAO_usuario.java Las clases no tienen relaciones entre sí, por tanto se representan por separado: DIAGRAMAS UML DE CLASES OBJETO Paciente.java 128 Análisis y Uso de Frameworks de Persistencia en Java Alergia.java Noticia.java 129 Análisis y Uso de Frameworks de Persistencia en Java Usuario.java 130 Análisis y Uso de Frameworks de Persistencia en Java DIAGRAMAS UML DE CLASES DE ACCESO A LA BASE DE DATOS DAO_paciente.java DAO_alergia.java 131 Análisis y Uso de Frameworks de Persistencia en Java DAO_noticia.java DAO_usuario.java 132 Análisis y Uso de Frameworks de Persistencia en Java 4.5.4- El modelo de datos En esta aplicación el modelo de datos se debe ajustar perfectamente a la estructura de las clases objeto. De esta forma se consigue simplificar la persistencia y poder incorporar cómodamente la tecnología que va a sustituir a JDBC. Las clases están definidas, y cada una de ellas será correspondida con una tabla en la base de datos. Todos sus atributos van a ser reflejados por columnas en las tablas, y las claves se definirán por la lógica de la aplicación. Tabla PACIENTES Tabla ALERGIAS En el caso de las Alergias, la clave debe ser compuesta, ya que la aplicación no permite tener más de un registro de un tipo de alergia para un mismo paciente. 133 Análisis y Uso de Frameworks de Persistencia en Java Tabla NOTICIAS Tabla USUARIOS 134 Análisis y Uso de Frameworks de Persistencia en Java 5- La alternativa a JDBC: Hibernate 135 Análisis y Uso de Frameworks de Persistencia en Java 5- LA ALTERNATIVA A JDBC: HIBERNATE Después de haber diseñado la aplicación, se pueden identificar varias entidades que, a la hora de persistirlas en su base de datos, se van a corresponder con tablas. En este capítulo se pretende describir cómo Hibernate abstrae al programador de la persistencia, creando las tablas a partir de las clases con las que se trabaja en la aplicación. La correspondencia entre clases y tablas es fundamental para mantener esa perspectiva orientada a objetos. Además, se van a enfrentar las clases gestoras de objetos de Hibernate con las DAO equivalentes de JDBC, con el fin de observar cómo Hibernate evita el proceso de encapsulamiento y desencapsulamiento. Por último se verá cómo es el proceso de implantación, echando un vistazo a los requisitos del sistema y a la configuración de la tecnología. 5.1- LAS CLASES A PERSISTIR - Usuario.java La clase Usuario es la más extensa de todas las que se van a persistir. La aplicación se encarga de que los valores correspondientes a 136 Análisis y Uso de Frameworks de Persistencia en Java sus atributos “dni” y “user” sean únicos para cada usuario real que representa la clase. Este es el código final de la clase “Usuario.java”: package obj; public class Usuario { private private private private private private private private private int dni; String user; String pass; String nombre; String ap1; String ap2; String email; String cod_perfil; int activo; //Cuenta 0:inactiva, 1:activa public static String PERFIL_MEDICO = "med"; public static String PERFIL_ADMINISTRATIVO = "adm"; public static String PERFIL_INFORMATICO = "inf"; // CONSTRUCTORES public Usuario( int dni, String user, String pass, String nombre, String ap1, String ap2, String email, String cod_perfil, int activo) { this.dni = dni; this.user = user; this.pass = pass; this.nombre = nombre; this.ap1 = ap1; this.ap2 = ap2; this.email = email; this.cod_perfil = cod_perfil; this.activo = activo; } //Constructor para la validación de usuarios public Usuario (String user, String pass){ this.user = user; this.pass = pass; } // Constructor por defecto: necesario para Hibernate 137 Análisis y Uso de Frameworks de Persistencia en Java public Usuario(){ } // ----- Getters y Setters public String getPass() { return pass; } public void setPass(String pass) { this.pass = pass; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public int getActivo() { return activo; } public void setActivo(int activo) { this.activo = activo; } public String getAp1() { return ap1; } public void setAp1(String ap1) { this.ap1 = ap1; } public String getAp2() { return ap2; } public void setAp2(String ap2) { this.ap2 = ap2; } public String getCod_perfil() { return cod_perfil; } public void setCod_perfil(String cod_perfil) { this.cod_perfil = cod_perfil; } public int getDni() { return dni; } public void setDni(int dni) { 138 Análisis y Uso de Frameworks de Persistencia en Java } this.dni = dni; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } // ----- Otros métodos // Devuelve un String con el nombre completo del usuario public String getNombreCompleto() { String res = this.getAp1() +" "+ this.getAp2()+", "+ this.getNombre(); return res; } // Devuelve el código HTML para mostrar una imagen del Perfil public String perfilToHTML() { String res; if ((this.cod_perfil).equals(Usuario.PERFIL_MEDICO)) { res = "<img src=\"Iconos/perfilMed.jpg\">"; } else { if ((this.cod_perfil).equals(Usuario.PERFIL_ADMINISTRATIVO)) { res = "<img src=\"Iconos/perfilAdm.jpg\">"; } else { res = "<img src=\"Iconos/perfilInf.jpg\">"; } } return res; } // Devuelve el código HTML para mostrar una fila de tabla // con la información de la cuenta. public String toListaHTML(){ String iconoPerfil; String iconoActividad; // Asignación de perfil if ((this.cod_perfil).equals(Usuario.PERFIL_MEDICO)) { iconoPerfil = "<img src=\"Iconos/perfilMedMini.jpg\"width=\"40\" height=\"15\">"; } 139 Análisis y Uso de Frameworks de Persistencia en Java else { if ((this.cod_perfil).equals(Usuario.PERFIL_ADMINISTRATIVO)) { iconoPerfil = "<img src=\"Iconos/perfilAdmMini.jpg\" width=\"40\" height=\"15\">"; } else { iconoPerfil = "<img src=\"Iconos/perfilInfMini.jpg\" width=\"40\" height=\"15\">"; } } // Asignación de actividad if (this.activo == 1) { iconoActividad = "<img src=\"Iconos/cuentaActiva.jpg\" width=\"100\" height=\"15\">"; } else { iconoActividad = "<img src=\"Iconos/cuentaInactiva.jpg\" width=\"100\" height=\"15\">"; } return "<tr>" + "<td width=\"9%\" align=\"center\" valign=\"middle\">"+ iconoPerfil +"</td>" + "<td width=\"70%\" align=\"left\" valign=\"middle\">" + "<div align=\"left\"><font face=\"Verdana, Arial, Helvetica, sans-serif\" size=\"2\">"+ this.getNombreCompleto() +"</font></div>" + "</td>" + "<td width=\"9%\" align=\"center\" valign=\"middle\">" + "<div align=\"center\"><input type=\"radio\" name=\"rbCuenta\" value=\""+ this.dni +"\"></div>" + "</td>" + "<td width=\"12%\" align=\"center\" valign=\"middle\">" + "<div align=\"center\">"+ iconoActividad +"</div>" + "</td>" + "</tr>"; } } Cabe destacar que, entre los constructores se encuentra el constructor por defecto, necesario para que Hibernate trabaje con los objetos “Usuario”. Como se describe en el siguiente apartado (5.2- Los ficheros de mapeo), “dni” será la clave de la tabla correspondiente a la hora de persistir esta clase. 140 Análisis y Uso de Frameworks de Persistencia en Java - Paciente.java En la clase Paciente nos encontramos con el atributo “dni”, que será la clave de la tabla correspondiente. Este es el código final de la clase “Paciente.java”: package obj; public class Paciente { // Atributos private int dni; private int dni_med; private String nombre; private String ap1; private String ap2; private String telefono; // Constructores public Paciente (){ } public Paciente (int dni){ this.dni = dni; } public Paciente (int dni, int dni_med, String nombre, String ap1, String ap2, String telefono){ this.dni = dni; this.dni_med = dni_med; this.nombre = nombre; this.ap1 = ap1; this.ap2 = ap2; this.telefono = telefono; } // Getters y Setters public String getAp1() { return ap1; } public void setAp1(String ap1) { this.ap1 = ap1; } 141 Análisis y Uso de Frameworks de Persistencia en Java public String getAp2() { return ap2; } public void setAp2(String ap2) { this.ap2 = ap2; } public int getDni() { return dni; } public void setDni(int dni) { this.dni = dni; } public int getDni_med() { return dni_med; } public void setDni_med(int dni_med) { this.dni_med = dni_med; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getTelefono() { return telefono; } public void setTelefono(String telefono) { this.telefono = telefono; } //------- Otros Métodos public String getNombreCompleto() { String res = this.getAp1() +" "+ this.getAp2()+", "+ this.getNombre(); return res; } } 142 Análisis y Uso de Frameworks de Persistencia en Java - Alergia.java Esta clase es, aparentemente igual que las demás, pero en el código se pueden observar algunos detalles interesantes: package obj; import java.io.Serializable; public class Alergia implements Serializable{ // Atributos private int dni; private String tipo; private String tratamiento; private String notas; // Constructores public Alergia (int dni, String tipo, String tratamiento, String notas){ this.dni = dni; this.tipo = tipo; this.tratamiento = tratamiento; this.notas = notas; } public Alergia (){ } // Getters y Setters public String getTipo() { return tipo; } public void setTipo(String tipo) { this.tipo = tipo; } public int getDni() { return dni; } public void setDni(int dni) { this.dni = dni; } public String getNotas() { 143 Análisis y Uso de Frameworks de Persistencia en Java } return notas; public void setNotas(String notas) { this.notas = notas; } public String getTratamiento() { return tratamiento; } public void setTratamiento(String tratamiento) { this.tratamiento = tratamiento; } // OTROS MÉTODOS public boolean equals(Object obj){ boolean res = false; if (obj instanceof Alergia){ Alergia a = (Alergia)obj; if (dni==a.getDni() && tipo.equals(a.getTipo())){ res = true; } } System.out.println("Ejecuta EQUALS"); return res; } public int hashCode(){ System.out.println("Ejecuta HASHCODE"); return dni + tipo.hashCode(); } } La clase “Alergia.java” difiere del resto porque: implementa el Interface “Serializable” redefine los métodos “equals()” y “hashCode()” Esto se debe a que la aplicación se encarga de que un paciente determinado tenga un registro de “Alergia” por cada tipo de alergia que padezca. Lógicamente esto quiere decir que los valores de “dni” y “tipo” formarán una clave compuesta en la tabla correspondiente. Para manejar 144 Análisis y Uso de Frameworks de Persistencia en Java clases con este tipo de clave, Hibernate necesita que se codifiquen dichas requerimientos en la clase correspondiente. - Noticia.java Esta es la clase más sencilla e independiente de la aplicación. Este el código final de la clase: package obj; public class Noticia { // Atributos private String fecha; private String titulo; private String cuerpo; private int codigo; // Constructores public Noticia (){ } public Noticia (int codigo){ this.codigo = codigo; } public Noticia (String fecha, String titulo, String cuerpo, int codigo){ this.fecha = fecha; this.titulo = titulo; this.cuerpo = cuerpo; this.codigo = codigo; } // MÉTODOS // Getters y Setters public String getCuerpo() { return cuerpo; } public void setCuerpo(String cuerpo) { this.cuerpo = cuerpo; } 145 Análisis y Uso de Frameworks de Persistencia en Java public String getFecha() { return fecha; } public void setFecha(String fecha) { this.fecha = fecha; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getCodigo() { return codigo; } public void setCodigo(int codigo) { this.codigo = codigo; } // Otros Métodos public String toHTML(){ return "<table width=\"75%\" border=\"0\" height=\"108\">" + "<tr>" + "<td height=\"11\" bgcolor=\"#000000\"><font face=\"Verdana, Arial, Helvetica, sans-serif\" color=\"#FFFFFF\">" + "<font size=\"2\"><img src=\"Iconos/dot.gif\" width=\"12\" height=\"12\"> - " + this.fecha +" - <b>"+ this.titulo +"</b></font></font></td>" + "</tr>" + "<tr>" + "<td valign=\"top\" align=\"left\" height=\"70\"><font face=\"Verdana, Arial, Helvetica, sans-serif\" size=\"1\">" + this.cuerpo +"</font></td>" + "</tr>" + "<tr>" + "<img src=\"Iconos/separador.jpg\" width=\"600\" height=\"6\">" + "</tr>" + "</table>"; } public String getInfo() { return " " + this.fecha + " - " + this.titulo; } } 146 Análisis y Uso de Frameworks de Persistencia en Java 5.2- LOS FICHEROS DE MAPEO Hibernate necesita saber de qué forma ha de hacer corresponder una clase con una tabla de la base de datos. Aquí entran en juego los ficheros de mapeo XML. El ellos se especifica, entre otras cosas, el nombre de la tabla que se quiere crear, el nombre de las columnas y los atributos que forman la clave. Con esta información Hibernate es capaz de relacionar los atributos con las columnas de la tabla, definiendo los tipos de datos basándose en el código de la clase correspondiente. A continuación figuran los ficheros de mapeo de las diferentes clases a persistir. 147 Análisis y Uso de Frameworks de Persistencia en Java - Usuario.hbm.xml Este es el código para mapear la clase “Usuario.java”: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="obj.Usuario" table="USUARIOS"> <id name="dni" column="dni"> <generator class="assigned"/> </id> <property <property <property <property <property <property <property <property name="user" column="user"/> name="pass" column="pass"/> name="nombre" column="nombre"/> name="ap1" column="apellido1"/> name="ap2" column="apellido2"/> name="email" column="email"/> name="cod_perfil" column="cod_perfil"/> name="activo" column="activado"/> </class> </hibernate-mapping> Entre otras cosas, el fichero le dice a Hibernate que persista la clase “obj.Usuario” en la tabla “USUARIOS”. Además en la sección <id> se define el atributo “dni” como clave de la tabla. Al elegir “assigned” en el apartado <generator> se está indicando que no se use ningún generador, que el valor que lleve el atributo “dni” se persista en la base de datos. 148 Análisis y Uso de Frameworks de Persistencia en Java - Paciente.hbm.xml Este es el código para mapear la clase “Paciente.java”: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="obj.Paciente" table="PACIENTES"> <id name="dni" column="dni"> <generator class="assigned"/> </id> <property <property <property <property <property name="dni_med" column="dni_med"/> name="nombre" column="nombre"/> name="ap1" column="ap1"/> name="ap2" column="ap2"/> name="telefono" column="telefono"/> </class> </hibernate-mapping> 149 Análisis y Uso de Frameworks de Persistencia en Java - Alergia.hbm.xml Este es el código para mapear la clase “Alergia.java”: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="obj.Alergia" table="ALERGIAS"> <!-- ID COMPUESTO: Requiere que obj.Alergia implemente Serializable--> <composite-id > <key-property name="dni" column="dni" /> <key-property name="tipo" column="tipo" /> </composite-id> <property name="tratamiento" column="tratamiento"/> <property name="notas" column="notas"/> </class> </hibernate-mapping> Aquí se puede observar cómo se mapea una clave compuesta. Se ha de utilizar la cláusula <composite-id> indicando qué atributos van a formar la clave. En este caso “dni” y “tipo” serán la clave compuesta de la tabla “ALERGIAS”. 150 Análisis y Uso de Frameworks de Persistencia en Java - Noticia.hbm.xml Este es el código para mapear la clase “Noticia.java”: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//hibernate/hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="obj.Noticia" table="NOTICIAS"> <id name="codigo" column="codigo"> <generator class="assigned"/> </id> <property name="fecha" column="fecha"/> <property name="titulo" column="titulo"/> <property name="cuerpo" column="cuerpo"/> </class> </hibernate-mapping> 151 Análisis y Uso de Frameworks de Persistencia en Java 5.3- LAS CLASES GESTORAS DE OBJETOS Al igual que en JDBC se trabaja con clases DAO para definir los métodos necesarios para que la aplicación interaccione con la base de datos, Hibernate utiliza unas clases similares, que denominamos Clases Gestoras. En las clases gestoras figuran todos los métodos que usa el programador para realizar transacciones con las tablas. Se debe crear una clase gestora por cada clase que se desee persistir. De este modo se deberá crear una instancia de la clase gestora siempre que se quiera realizar una transacción de cualquiera de los objetos. A continuación se muestran las clases gestoras que se han implementado para utilizar Hibernate en la aplicación diseñada. Se pretende comparar estas clases con las DAO equivalentes que se usan al trabajar con JDBC. 152 Análisis y Uso de Frameworks de Persistencia en Java - GestorUsuario.java Tal y como se puede observar en el código, el constructor de todas estas clases crea una instancia de “SessionFactory”, sobre la cual se van a realizar todas y cada una de las transacciones. package util; import java.util.Iterator; import obj.Usuario; import import import import import import org.hibernate.HibernateException; org.hibernate.Query; org.hibernate.Session; org.hibernate.SessionFactory; org.hibernate.Transaction; org.hibernate.cfg.Configuration; public class GestorUsuario { private SessionFactory sessionFactory; public GestorUsuario() { try { System.out.println("Initializing Hibernate"); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println("Finished Initializing Hibernate"); } catch (HibernateException e) { e.printStackTrace(); } } public Usuario getUsuario(int dni) { Usuario res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); /* Con .get() */ res = (Usuario)session.get(Usuario.class, new Integer(dni)); /* Con QUERY: */ 153 Análisis y Uso de Frameworks de Persistencia en Java // String dniString = String.valueOf(dni); // res = (Usuario)session.createQuery("from Usuario as usuario where usuario.dni = ?") // .setString(0,dniString) // .uniqueResult(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } public void insertarUsuario (Usuario u) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.save(u); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } // Comprueba si existe el "User" para el login. La comprobación // se realiza durante la solicitud de cuentas. public boolean existeUsuario(String s) { boolean res = false; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Usuario u_aux = (Usuario)session.createQuery("from Usuario as usuario where usuario.user = ?") .setString(0,s) .uniqueResult(); if (u_aux != null){ res = true; } tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } 154 Análisis y Uso de Frameworks de Persistencia en Java return res; } // Comprueba si existe el "dni". La comprobación // se realiza durante la solicitud de cuentas. public boolean existeDni(String dni) { boolean res = false; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Usuario u_aux = (Usuario)session.createQuery("from Usuario as usuario where usuario.dni = ?") .setString(0,dni) .uniqueResult(); if (u_aux != null){ res = true; } tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return res; } public void borrarCuenta(int dni) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Usuario u = this.getUsuario(dni); session.delete(u); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public Iterator obtenerUsuarios(){ Iterator res = null; try { 155 Análisis y Uso de Frameworks de Persistencia en Java Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = session.createQuery("from Usuario") .list() .iterator(); tx.commit(); session.close(); } } catch (HibernateException e) { e.printStackTrace(); } return res; public Iterator obtenerMedicos(){ Iterator res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = session.createQuery("from Usuario as usuario where usuario.cod_perfil=?") .setString(0,"med") .list() .iterator(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } public void cambiarActivado(int dni) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Usuario u_aux = this.getUsuario(dni); int actividad = u_aux.getActivo(); if (actividad==0){ u_aux.setActivo(1); } else{ u_aux.setActivo(0); } session.update(u_aux); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); 156 Análisis y Uso de Frameworks de Persistencia en Java } } public void editarCuenta(Usuario u) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.update(u); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public void cambiarPass(int dni, String nuevaPass) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Usuario u = this.getUsuario(dni); u.setPass(nuevaPass); session.update(u); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } // Validación del LOGIN // Si los datos son válidos, devuelve el usuario con todos sus datos. public Usuario validarUsuario(String user, String pass) { Usuario res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = (Usuario)session.createQuery("from Usuario as usuario where usuario.user = ? and usuario.pass= ?") .setString(0,user) .setString(1,pass) .uniqueResult(); tx.commit(); 157 Análisis y Uso de Frameworks de Persistencia en Java session.close(); } catch (HibernateException e){ e.printStackTrace(); } } return res; } Para hacerse una idea de cómo Hibernate evita las tareas de encapsulamiento y desencapsulamiento, se puede observar el método “editarCuenta(Usuario u)“ y compararlo con otro equivalente de una clase DAO: // Con HIBERNATE: public void editarCuenta(Usuario u) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.update(u); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } // Con JDBC: public void editarCuenta(Usuario u) { if (this.conexion != null) { // DESENCAPSULAMIENTO: int dni = u.getDni(); String user = u.getUser(); String pass = u.getPass(); String nom = u.getNombre(); String ap1 = u.getAp1(); String ap2 = u.getAp2(); String email = u.getEmail(); 158 Análisis y Uso de Frameworks de Persistencia en Java try { Statement st = (this.conexion).createStatement(); st.executeUpdate("UPDATE personal.usuarios SET usuario='"+user+"' WHERE dni='"+dni+"'"); st.executeUpdate("UPDATE personal.usuarios SET pass='"+pass+"' WHERE dni='"+dni+"'"); st.executeUpdate("UPDATE personal.usuarios SET nombre='"+nom+"' WHERE dni='"+dni+"'"); st.executeUpdate("UPDATE personal.usuarios SET apellido1='"+ap1+"' WHERE dni='"+dni+"'"); st.executeUpdate("UPDATE personal.usuarios SET apellido2='"+ap2+"' WHERE dni='"+dni+"'"); st.executeUpdate("UPDATE personal.usuarios SET email='"+email+"' WHERE dni='"+dni+"'"); } catch (SQLException e) { System.out.println("ERROR query update"); } } } Es fácil comprobar que la diferencia en cuanto a cantidad de líneas de código es muy “session.update(u)”, notable. comprobando Hibernate el se campo sirve clave con para identificar el registro que se va a actualizar y sustituyendo todos los valores que haya en la tabla por los que se encuentran poblando los atributos del objeto “u”. Lo mismo sucede con instrucciones como “session.save(u)” o “session.delete(u)” que aparecen en de inserción y borrado, respectivamente. Otro punto interesante es que la clase Query de Hibernate redefine el método List(), de forma que facilita el uso del resultado y no hay que recorrer el ResultSet como ocurre cuando se trabaja con JDBC. 159 Análisis y Uso de Frameworks de Persistencia en Java - GestorPaciente.java Para trabajar con la clase Paciente se usará esta clase gestora: package util; import java.util.Iterator; import obj.Paciente; import import import import import import org.hibernate.HibernateException; org.hibernate.Query; org.hibernate.Session; org.hibernate.SessionFactory; org.hibernate.Transaction; org.hibernate.cfg.Configuration; public class GestorPaciente { private SessionFactory sessionFactory; public GestorPaciente() { try { System.out.println("Initializing Hibernate"); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println("Finished Initializing Hibernate"); } catch (HibernateException e) { e.printStackTrace(); } } public void insertarPaciente(Paciente p) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.save(p); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public Paciente getPaciente(int dni) { 160 Análisis y Uso de Frameworks de Persistencia en Java Paciente res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); /* Con .get() */ res = (Paciente)session.get(Paciente.class, new Integer(dni)); /* Con QUERY: */ // String dniString = String.valueOf(dni); // res = (Paciente)session.createQuery("from Paciente as paciente where paciente.dni = ?") // .setString(0,dniString) // .uniqueResult(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } public void borrarPaciente(Paciente p) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.delete(p); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public boolean existePaciente(int dni) { boolean res = false; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Paciente p = (Paciente)session.get(Paciente.class, new Integer(dni)); if (p != null){ res = true; } tx.commit(); session.close(); } catch (HibernateException e) { 161 Análisis y Uso de Frameworks de Persistencia en Java e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return res; } public void editarPaciente(Paciente p) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.update(p); // Otro método: // Paciente p_old = (Paciente)session.get(Paciente.class, new Integer(p.getDni())); // // p_old.setDni_med(p.getDni_med()); // p_old.setNombre(p.getNombre()); // p_old.setAp1(p.getAp1()); // p_old.setAp2(p.getAp2()); // p_old.setTelefono(p.getTelefono()); // // session.flush(); //Hibernate guarda los cambios en "p_old" tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public Iterator obtenerPacientes(){ Iterator res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = session.createQuery("from Paciente") .list() .iterator(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } 162 Análisis y Uso de Frameworks de Persistencia en Java public Iterator obtenerPacientesAsignados(int dni_med){ Iterator res = null; String dniString = String.valueOf(dni_med); try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = session.createQuery("from Paciente as paciente where paciente.dni_med= ?") .setString(0,dniString) .list() .iterator(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } } 163 Análisis y Uso de Frameworks de Persistencia en Java - GestorAlergia.java Para trabajar con la clase Alergia se usará esta clase gestora: package util; import java.util.Iterator; import obj.Alergia; import import import import import import org.hibernate.HibernateException; org.hibernate.Query; org.hibernate.Session; org.hibernate.SessionFactory; org.hibernate.Transaction; org.hibernate.cfg.Configuration; public class GestorAlergia { private SessionFactory sessionFactory; public GestorAlergia() { try { System.out.println("Initializing Hibernate"); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println("Finished Initializing Hibernate"); } catch (HibernateException e) { e.printStackTrace(); } } public void insertarAlergia(Alergia a) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.save(a); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public Alergia getAlergia(int dni, String tipo) { 164 Análisis y Uso de Frameworks de Persistencia en Java /* Al tener una clave compuesta, para ejecutar el método load(), el identificador del objeto es una instancia del propio objeto con los atributos que forman la clave compuesta poblados con los datos correspondientes. */ Alergia res = null; Alergia a = new Alergia (dni,tipo,"",""); try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); /* Con .load() */ res = (Alergia)session.load(Alergia.class, a); System.out.println(res.getNotas()); /* Con QUERY: */ // String dniString = String.valueOf(dni); // res = (Alergia)session.createQuery("from Alergia as alergia where alergia.dni = ? and alergia.tipo = ?") // .setString(0,dniString) // .setString(1,tipo) // .uniqueResult(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } public void borrarAlergia(Alergia a) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.delete(a); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public void editarAlergia(Alergia a) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); 165 Análisis y Uso de Frameworks de Persistencia en Java String tratamiento = a.getTratamiento(); String notas = a.getNotas(); Alergia a_old = (Alergia)session.load(Alergia.class, a); a_old.setTratamiento(tratamiento); a_old.setNotas(notas); session.flush(); //Hibernate guarda los cambios en "a_old" tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public boolean existeRegistro(int dni, String tipo) { boolean res = false; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Alergia a = (Alergia)session.get(Alergia.class, new Alergia(dni,tipo,"","")); if (a != null){ res = true; } tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } return res; public Iterator obtenerAlergiasAsignadas(int dni){ Iterator it = null; String dniString = String.valueOf(dni); try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); it = session.createQuery("from Alergia as alergia where alergia.dni = ?") .setString(0,dniString) .list() .iterator(); tx.commit(); 166 Análisis y Uso de Frameworks de Persistencia en Java session.close(); } catch (HibernateException e) { e.printStackTrace(); } return it; } } 167 Análisis y Uso de Frameworks de Persistencia en Java - GestorNoticia.java Para trabajar con la clase Paciente se usará esta clase gestora: package util; import java.util.Iterator; import java.util.ListIterator; import obj.Alergia; import obj.Noticia; import obj.Paciente; import import import import import org.hibernate.HibernateException; org.hibernate.Session; org.hibernate.SessionFactory; org.hibernate.Transaction; org.hibernate.cfg.Configuration; public class GestorNoticia { private SessionFactory sessionFactory; public GestorNoticia() { try { System.out.println("Initializing Hibernate"); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println("Finished Initializing Hibernate"); } catch (HibernateException e) { e.printStackTrace(); } } public Noticia getNoticia(int codigo) { Noticia res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); /* Con .get() */ res = (Noticia)session.get(Noticia.class, new Integer(codigo)); /* Con QUERY: */ // String codigoString = String.valueOf(codigo); // res = (Noticia)session.createQuery("from Noticia as noticia where noticia.codigo = ?") // .setString(0,codigoString) 168 Análisis y Uso de Frameworks de Persistencia en Java // tx.commit(); session.close(); .uniqueResult(); } catch (HibernateException e) { e.printStackTrace(); } return res; } public void nuevaNoticia(Noticia n) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.save(n); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } public void borrarNoticia(Noticia n) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.delete(n); tx.commit(); session.close(); } } catch (HibernateException e) { e.printStackTrace(); } public void editarNoticia(Noticia n) { try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.update(n); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } 169 Análisis y Uso de Frameworks de Persistencia en Java public ListIterator obtenerNoticias(){ ListIterator res = null; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); res = session.createQuery("from Noticia") .list() .listIterator(); tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } return res; } // Vuelve a enumerar los códigos de las noticias: // ... se utilizará despues de una operación de baja. // Devuelve el número de noticias que hay. public Integer recodificarNoticias() { int cont = 1; try { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); ListIterator noticias = this.obtenerNoticias(); if (noticias!=null) { while (noticias.hasNext()) { Noticia n = (Noticia)noticias.next(); int codigo_aux = n.getCodigo(); Noticia n_aux = this.getNoticia(codigo_aux); n_aux.setCodigo(cont); this.borrarNoticia(n); this.nuevaNoticia(n_aux); } cont++; } tx.commit(); session.close(); } catch (HibernateException e) { System.out.println("ERROR: hib exception en recodificarNoticias()"); 170 Análisis y Uso de Frameworks de Persistencia en Java } } e.printStackTrace(); Integer res = new Integer(cont - 1); return res; } 171 Análisis y Uso de Frameworks de Persistencia en Java 5.4- IMPLANTACIÓN 5.4.1. Instalación y Configuración Primero hay que bajarse Hibernate 3.0 de su página de descarga. Después hay que extraerlo y colocar los .jar la carpeta lib del proyecto Web, así como el conector de la base de datos MySQL. La configuración de Hibernate se realiza a través de un archivo XML llamado “hibernate.cfg.xml” que se situará en la carpeta “JavaSource”. En la página siguiente se puede ver el código del archivo, con los datos necesarios para adaptarlo a esta aplicación. Como se puede apreciar, en las secciones <property> se especifica todo lo necesario para realizar la conexión a la base de datos MySQL “clinica_paya”, incluyendo el usuario y la contraseña. Posteriormente, con las etiquetas <mapping> se está indicando a Hibernate que tiene cuatro objetos para persistir, uno por cada uno de los ficheros de mapeo creados en el apartado anterior. Hibernate tomará esta información y será capaz de crear las tablas especificadas por los ficheros de mapeo. 172 Análisis y Uso de Frameworks de Persistencia en Java Hibernate.cfg.xml <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration3.0.dtd"> <!-- Generated by MyEclipse Hibernate Tools. <hibernate-configuration> --> <session-factory> <property name="connection.username">anonimo</property> <property name="connection.url">jdbc:mysql://localhost:3306/clinica_paya</proper ty> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="myeclipse.connection.profile">MySQL_personal</property> <property name="connection.password">anonimo</property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.hbm2ddl.auto">update</property> <mapping <mapping <mapping <mapping resource="obj/Alergia.hbm.xml"></mapping> resource="obj/Noticia.hbm.xml"></mapping> resource="obj/Usuario.hbm.xml"></mapping> resource="obj/Paciente.hbm.xml"></mapping> </session-factory> </hibernate-configuration> Cabe destacar que esta forma de configurar Hibernate permite una sencillísima portabilidad entre diferentes entornos de bases de datos. Si se quiere trasladar a otra base de datos con otro gestor, basta con editar algunas de las propiedades de este fichero referentes a la base de datos. Para no tener que trabajar con los XML existen asistentes para la edición de este archivo y para la creación de ficheros de mapeo. 173 Análisis y Uso de Frameworks de Persistencia en Java 5.4.2. Requisitos del Sistema (Hibernate 3.0): o JDK 1.3 o superior o 128 Mb de RAM o 50 Mb de espacio en disco o 400 Mhz CPU 174 Análisis y Uso de Frameworks de Persistencia en Java 6- Presupuesto 175 Análisis y Uso de Frameworks de Persistencia en Java 6- PRESUPUESTO Los costes establecidos se basan en que la empresa contratante dispone del hardware requerido y que el autor del proyecto no tiene la condición de ingeniero técnico titulado. COSTES DE DESARROLLO • Estudio...................................................................... 80 horas x 26 €/h = 2.080 € • Análisis..................................................................... 80 horas x 26 €/h = 2.080 € • Programación..........................................................100 horas x 24 €/h = 2.400 € COSTES DE IMPLANTACIÓN • Configuración y pruebas............................................. 40 horas x 20 €/h = 800 € TOTAL..................................7.360 € 176 Análisis y Uso de Frameworks de Persistencia en Java 7- Conclusiones 177 Análisis y Uso de Frameworks de Persistencia en Java 7- CONCLUSIONES Este trabajo ha hecho posible confirmar que el manejo de las bases de datos es crítico para el comportamiento de una aplicación. La aplicación Web se ha desarrollado utilizando Hibernate, pero paralelamente se ha codificado el módulo equivalente en JDBC. Esto ha permitido que percepción del contraste sea completa, haciendo posible reafirmar las conclusiones obtenidas tras realizar el estudio teórico. Tras la conclusión del desarrollo se ha comprobado que la programación con Hibernate es mucho más sencilla y llevadera, ya que nos libera de tareas tan ásperas como el encapsulamiento y desencapsulamiento de los objetos, y pone a nuestra disposición una API completísima y fácil de usar. Sin embargo, después de concluir con la implantación del portal, ha quedado patente que los accesos con JDBC son siempre más rápidos que los gestionados por un framewok como Hibernate, tal y como se había predecido en el estudio. Estas conclusiones se corresponden perfectamente con las pretensiones del proyecto. La intención del trabajo era confrontar y mostrar el funcionamiento de las alternativas, creando finalmente una herramienta de trabajo sencilla y versátil. 178 Análisis y Uso de Frameworks de Persistencia en Java La realización de este proyecto me ha aportado una grandísima experiencia personal y académica. Este es un proyecto muy completo en muchos aspectos. Tiene gran variedad de fases, las cuales se han abordado de formas distintas, lo que requiere una planificación diferente para cada una de ellas. Por otra parte, el desarrollo de la aplicación ha sido posible gracias al aprendizaje de múltiples herramientas software, ampliando así mis conocimientos en un gran rango de materias. El director del proyecto me ha sabido transmitir qué tipo de proyecto tenía en mente. La filosofía seguida a lo largo del mismo ha conseguido que la documentación tome continuidad entre sus capítulos y sea entretenida su lectura. 179 Análisis y Uso de Frameworks de Persistencia en Java 8- Bibliografía 180 Análisis y Uso de Frameworks de Persistencia en Java 8- BIBLIOGRAFÍA Libros consultados: [MONS04] Monson-Haefel R. “Enterprise JavaBeans”, O’Reilly, 4ª Edición, 2004. [BERG03] Bergsten H. “JavaServer Pages”, O’Reilly, 3ª Edición, 2003. [SING02] Singh I. “Designing Enterprise Applications with the J2EE Platform”, Addison Wesley, 2ª Edición, 2002. [HUNT01] Hunter J. “Java Servlet Programming”, O’Reilly, 2ª Edición, 2001. [BARR01] Barranco J. “Metodología del análisis estructurado de sistemas”, Publicaciones Universidad Pontificia de Comillas, 2ª Edición, Madrid, 2001. [FOWL99] Fowler, M. “UML gota a gota”, Addison Wesley, México 1999. [GOSL96] Gosling, J. “The Java Programming Language”, Addison Wesley, Massachusetts 1996. 181 Análisis y Uso de Frameworks de Persistencia en Java Otras fuentes: Página oficial de Hibernate: www.hibernate.org Web sobre JDO: www.JDOcentral.com Programación en castellano: www.programación.com 182