Está en la página 1de 52

Persistencia

Autor: Antonio J. Martn

NDICE

1. 2.

INTRODUCCIN ................................................................................................... 5 JAVA PERSISTENCE API (JPA) ......................................................................... 6 2.1. 2.2. LA ESPECIFICACIN JPA ................................................................................... 6 ENTIDADES ........................................................................................................ 6 Definicin de una entidad ........................................................................... 7 Requerimientos de una clase de entidad ..................................................... 8

2.2.1. 2.2.2. 2.3. 2.4.

UNIDAD DE PERSISTENCIA ................................................................................. 9 LA INTERFAZ ENTITYMANAGER ..................................................................... 10 Obtencin de un objeto EntityManager .................................................... 10 Ciclo de vida de una entidad ..................................................................... 11 Mtodos de la interfaz EntityManager ...................................................... 12

2.4.1. 2.4.2. 2.4.3. 3.

DESARROLLO DE UNA APLICACIN JPA CON NETBEANS ................. 14 3.1. 3.2. 3.3. CONFIGURACIN DE PERSISTENCIA EN NETBEANS ......................................... 14 DEFINICIN DE ENTIDADES. ............................................................................ 16 CODIFICACIN DE LAS INSTRUCCIONES JPA ................................................... 20 EJERCICIO 1 ................................................................................................. 21

4.

CLAVES PRIMARIAS......................................................................................... 25 4.1. 4.2. 4.3. CLAVE PRIMARIA SIMPLE ................................................................................ 25 CLAVE PRIMARIA COMPUESTA ........................................................................ 25 GENERACIN AUTOMATICA DE CLAVES .......................................................... 27

5.

RELACIONES ENTRE ENTIDADES ............................................................... 27 5.1. RELACIN UNO A UNO ..................................................................................... 28 Relaciones uno a uno unidireccionales. .................................................... 29 Relaciones uno a uno bidireccionales ....................................................... 31

5.1.1. 5.1.2. 5.2. 5.3.

RELACIN UNO A MUCHOS Y MUCHOS A UNO ................................................. 32 RELACIN MUCHOS A MUCHOS ....................................................................... 34

EJERCICIO 2 ................................................................................................. 36 5.4. 6. ACTUALIZACIONES EN CASCADA .................................................................... 41

LENGUAJE JPQL Y CONSULTAS ................................................................... 41 6.1. 6.2. 6.3. 6.4. SINTAXIS JPQL ................................................................................................ 41 CREACIN DE CONSULTAS .............................................................................. 42 EJECUCIN DE UNA CONSULTA ....................................................................... 43 CONSULTAS CON PARMETROS ....................................................................... 44 EJERCICIO 3 ................................................................................................. 45

1. INTRODUCCIN
En los desarrollos que hemos estado realizando a lo largo de los captulos precedentes, las operaciones de acceso a datos forman parte de la lgica de negocio de la aplicacin y, por tanto, son realizadas desde la capa de modelo en una arquitectura MVC. Estas operaciones, implementadas mediante instrucciones JDBC con cdigo SQL embebido, representan un alto porcentaje del total de cdigo de esta capa; en muchos casos, adems, las instrucciones resultan excesivamente tediosas y aumentan la probabilidad de errores dentro de la aplicacin. Si en vez de tratar directamente con los datos reales residentes en la base de datos relacional, las aplicaciones pudieran tratar con objetos que encapsulasen dichos datos, el cdigo de la capa de negocio se reducira considerablemente y permitira al programador concentrarse en la lgica de negocio de la aplicacin, en vez de dedicarse a la codificacin de pesadas instrucciones JDBC. Esta es precisamente la funcin de la capa de persistencia de una aplicacin. La capa de persistencia se coloca entre el modelo y la capa de datos (figura 1); su objetivo es exponer los datos a la aplicacin en forma de objetos a fin de que esta no tenga que preocuparse de los detalles relativos al acceso a la base de datos; ser la propia capa de persistencia la que implemente todo el cdigo JDBC/SQL necesario para realizar el mapeo entre los objetos y la base de datos, as como la que se encargue de mantener la sincronizacin entre ambos bloques.

Capa intermedia
Capa Persistencia
HTTP

Capa Web

Capa Negocio

Motor

JPA

JDBC BD

Figura. 1. Para reducir el costoso proceso de mapeo de objetos a tablas de una base de datos y conseguir una automatizacin del proceso, surgen los llamados frameworks de persistencia, conocidos tambin como mapeadores Objeto/relacional (ORM). Estos frameworks incorporan un motor de persistencia que implementa toda la lgica JDBC/SQL para realizar el mapeo de los datos. Entre los framework ORM ms populares se encuentran hibernate, toplink o ibatis. El objetivo de este tema es analizar la manera en la que podemos incorporar una capa de persistencia a nuestras aplicaciones, as como conocer los elementos necesarios para manipular dicha capa desde la lgica de negocio de la aplicacin.

2. JAVA PERSISTENCE API (JPA)


La manipulacin de objetos persistentes desde la capa de negocio requiere la utilizacin de un API que permita al programador realizar las operaciones tpicas de creacin, actualizacin, eliminacin y recuperacin de objetos. Adems de los APIs propios de cada framework, Sun Microsystem ha incluido en la edicin Java EE una nueva especificacin, conocida como Java Persistence API (JPA) que incluye un API para la manipulacin de objetos persistentes, compatible con la mayora de los motores de persistencia utilizados en la actualidad. La utilizacin de JPA, por tanto, permite abstraerse de los detalles relativos al motor de persistencia utilizado, al igual que, por ejemplo, JDBC nos proporciona total independencia respecto al tipo de base de datos.

2.1. LA ESPECIFICACIN JPA Realmente, la especificacin JPA abarca tres reas: El API JPA. Se trata de un conjunto de clases e interfaces, incluidas dentro del paquete javax.pesistence, que sern empleadas por la capa de negocio para operar con los objetos persistentes. Se calcula que con la utilizacin de este API, en vez de las clsicas instrucciones JDBC para acceso a los datos, se puede conseguir un ahorro de hasta un 40% en el cdigo de la capa de negocio. Mapeo objeto-relacional. Representa la informacin que permite especificar al motor de persistencia la manera en que se deben mapear los objetos con las tablas de la base de datos. JPA soporta tanto ficheros de configuracin XML como anotaciones para la definicin de estos metadatos. Java Persistence Query Language (JPQL). Aunque muchas de las operaciones habituales con datos pueden ser realizadas a travs de los mtodos del API estndar JPA, la especificacin incluye un lenguaje de manipulacin de objetos, conocido como JPQL, con el que podemos definir operaciones complejas de tratamiento de objetos. Su sintaxis es similar a la del lenguaje SQL estndar, pero adaptadas al tratamiento de objetos.

2.2. ENTIDADES Las entidades constituyen las piezas clave de la capa de persistencia. Una entidad no es ms que una simple clase POJO de tipo JavaBean cuyos campos representan el estado persistente asociado a la entidad. Los valores almacenados en los campos de un objeto de una clase de entidad representan informacin existente en una base de datos. Tpicamente, una entidad se encuentra asociada a una tabla de la base de datos y define un campo por cada columna de la misma, de manera que cada objeto de entidad contendr los datos de un registro de dicha tabla (figura 2).

class Nombre_Entidad{ tipo campo_a; tipo campo_b; tipo campo_c; : }

Tabla
campo_a campo_b campo_c

objeto1 objeto2

Figura. 2. De esta manera, las operaciones sobre datos realizadas desde la capa de negocio se limitarn a instanciar, eliminar, recuperar y modificar objetos de entidad; ser el gestor de persistencia el que se encargue de trasladar a la BD las operaciones realizadas por la aplicacin sobre la entidad.

2.2.1. Definicin de una entidad Como hemos indicado, una entidad no es ms que una clase POJO tipo JavaBean con una serie de mtodos set/get para permitir el acceso a los campos desde el exterior. Para que el gestor de persistencia interprete que se trata de una entidad y pueda gestionarla como tal, es necesario que la clase est definida con la anotacin @Entity. Tanto esta como las restantes anotaciones utilizadas en la definicin de entidades estn definidas en el paquete javax.persistence. El siguiente listado de ejemplo corresponde a una clase de entidad que almacena los datos de un libro: @Entity public class Libro implements Serializable{ @Id private String isbn; private String titulo; private float precio; private int paginas; private String autor; private String editorial; //mtodos set/get : }

En el listado anterior puede verse tambin como uno de los campos, isbn, se encuentra declarado con la anotacin @Id. La anotacin @Id se utiliza para identificar al campo que hace de clave primaria, es decir, el campo cuyo valor identifica de forma nica a una entidad, de modo que no puede haber dos objetos con el mismo valor de este campo. Dicho campo puede ser de tipo primitivo, clase de envoltorio, String o Date. Como veremos ms adelante, en algunos casos la clave primaria puede estar compuesta por ms de un campo. De forma predeterminada, la clase definida como entidad es mapeada a una tabla llamada igual que ella y donde cada campo se corresponde con una columna de su mismo nombre. Si tuviramos que mapear a elementos de distinto nombre, se debera utilizar la anotacin @Table a nivel de clase para especificar el nombre de la tabla destino y las anotaciones @Column a nivel de campo para indicar los nombres de las columnas. En el siguiente listado la clase Libro sera mapeada a la tabla datoslibro mientras que el campo paginas se mapeara a la columna totalpaginas: @Entity @Table(name=datoslibro) public class Libro implements Serializable{ @Id private String isbn; private String titulo; private float precio; @Column(name=totalpaginas) private int paginas; private String autor; private String editorial; //mtodos set/get : }

2.2.2. Requerimientos de una clase de entidad Desde el punto de vista de su implementacin, para que una clase pueda definirse como una entidad es necesario que cumpla los siguientes requerimientos: La clase debe tener definido un constructor sin parmetros. Dado que el gestor de persistencia tendr que crear en determinados casos objetos de la clase de entidad, es necesario que esta tenga definido un constructor sin parmetros. Ninguno de los componentes de la clase (campos y mtodos) puede estar definido como final. Tampoco la clase podr definirse con ese modificador. Los campos de la entidad no pueden ser pbicos. Aunque pueden estar definidos con acceso por defecto o protected (nunca public), lo ms adecuado es que se definan como privados y se proporcione acceso a ellos a travs de mtodos set/get.

Es obligatorio que toda entidad cuente con una clave primaria. Esta puede ser un campo de la clase o una clave compuesta.

Por otro lado, comentar que las anotaciones utilizadas en el interior de la clase para definir las propiedades de la entidad (@Id, @Column, etc.) pueden ser aplicadas directamente a los campos (acceso basado en campos), como es el caso de los ejemplos presentados, o a los mtodos set/get (acceso basado en propiedad), pero no se pueden mezclar ambas opciones en una misma clase. Cuando se utilizar un acceso basado en campos, el gestor de persistencia utiliza reflexin para acceder a los campos persistentes, mientras que si se emplea un acceso basado en propiedad, el acceso a los campos se realiza a travs de los mtodos set/get.

2.3. UNIDAD DE PERSISTENCIA El gestor de persistencia es, como hemos indicado, es el encargado de mapear los datos representados por las entidades con las correspondientes tablas de la BD y mantener la sincronizacin entre ambas, para lo cual, utiliza la informacin definida en las anotaciones. Pero al margen de los metadatos definidos por las anotaciones de la entidad, el contenedor JPA donde se va a ejecutar la aplicacin necesita que se le proporcione determinada informacin para poder manipular la BD a travs de lo que se conoce como una unidad de persistencia. Una unidad de persistencia describe los siguientes datos: Clases de entidad que van a ser gestionadas por el proveedor de persistencia. Clase con la implementacin JPA del proveedor de persistencia utilizado. Propiedades necesarias por el proveedor para conectar con la BD.

Toda esta informacin debe quedar registrada en un archivo llamado persistence.xml que tendr que estar situado en el subdirectorio META-INF de la aplicacin. En el listado de la figura 3 se muestra el contenido de un archivo persistance.xml de ejemplo que utiliza el proveedor de persistencia JPA de eclipse. A partir de las unidades de persistencia se definen los contextos de persistencia. Un contexto de persistencia representa un conjunto de instancias, pertenecientes a las clases de entidad definidas en la unidad de persistencia, que estn siendo gestionadas durante un periodo de tiempo dado. La gestin de estas instancias es realizada por el EntityManager.

Clase de implementacin del proveedor de persistencia

Clases d e entidad gestionadas

<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="ejemplo1PU" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>entidades.Libros</class> <properties> <property name="eclipselink.jdbc.password" value="admin"/> <property name="eclipselink.jdbc.user" value="root"/> <property name="eclipselink.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="eclipselink.jdbc.url" value="jdbc:mysql://localhost:3306/libreria"/> </properties> </persistence-unit> </persistence>

Propiedades de conexin con la BD

Figura. 3.

2.4. LA INTERFAZ ENTITYMANAGER La interfaz EntityManager, incluida en el paquete javax.persistence, representa el elemento ms importante del API JPA, pues proporciona todos los mtodos necesarios para que la aplicacin pueda operar sobre los objetos de entidad. Los proveedores de persistencia JPA son capaces de generar objetos que implementan esta interfaz, partiendo de la configuracin definida en una determinada unidad de persistencia. Como hemos indicado, el objeto EntityManager se encarga de gestionar las instancias que forman parte del contexto de persistencia asociado a la unidad, traduciendo las llamadas a sus mtodos en operaciones reales sobre la base de datos.

2.4.1. Obtencin de un objeto EntityManager Para obtener un objeto EntityManager, se deber crear inicialmente un EntityManagerFactory asociado a la unidad de persistencia. La propia interfaz EntityManagerFactory, tambin incluida en javax.persistence, dispone del mtodo esttico createEntityManagerFactory() que crea un objeto de este tipo a partir del nombre de la unidad de persistencia: EntityManagerFactory emf = EntityManagerFactory. createEntityManagerFactory(testunit); Una vez que se dispone de la factora se puede crear fcilmente un EntityManager, utilizando el mtodo createEntityManager() del objeto EntityManagerFactory: EntityManager em = emf.createEntityManager();

Lo anterior representa la forma general de obtener una referencia a un EntityManager; ms adelante analizaremos otro mecanismo para realizar esta tarea, basado en la inyeccin de dependencia. Independientemente del medio utilizado, una vez que disponemos de la referencia al objeto EntityManager ya podemos hacer uso de sus mtodos para operar sobre los datos, pero antes de pasar al estudio de estos mtodos es necesario que analicemos en detalle el ciclo de vida de una entidad.

2.4.2. Ciclo de vida de una entidad Desde el momento en que se crea una instancia de una entidad hasta que es destruida, puede transitar por los estados que se indican en el diagrama de la figura 4.
persist()

Gestionada

Nueva
merge()

Desconectada

remove()

Finalizada

Figura. 4. Vamos a describir a continuacin estos cuatro estados: Nueva. En este estado se encuentra la entidad cuando la aplicacin crea un objeto de la clase. En este momento, la entidad an no est gestionada por el EntityManager, por lo que no tendr asociado an ningn valor en la base de datos. Gestionada. En este estado la entidad es gestionada por el EntityManager y, por tanto, existir una correspondencia entre los datos del objeto y algn registro de la base de datos. Desde el estado nueva, se puede llegar a gestionada utilizando el mtodo persist() de EntityManager, al que proporcionaramos como parmetro la referencia al objeto recin creado con sus datos. Al llamar a este mtodo, el EntityManager crear en la base de datos un nuevo registro con los datos de la instancia. Desde este estado, cualquier cambio que la aplicacin realice sobre el objeto, invocando a los mtodos set del mismo, ser trasladado a la base de datos por el EntityManager, si bien es importante indicar que los cambios realizados en los campos de la entidad gestionada solo se vern reflejados en la base de datos si el cdigo se ejecuta dentro de una transaccin (como por ejemplo, cuando las instrucciones se encuentran en el interior de un mtodo de un EJB) y una vez que la misma ha sido confirmada. Por otro lado, para que el EntityManager actualice de los datos de la instancia con la informacin existente en la BD, ser necesario indicarlo explcitamente a travs del mtodo refresh(), tal y como veremos en la siguiente seccin.

Desconectada. En este estado (conocido en ingls como detached) la instancia deja de estar gestionada por el EntityManager, de modo que cualquier cambio que se realice en la misma durante el tiempo que est en este estado no se ver reflejado en la BD. Las entidades desconectadas son tiles cuando se quieren pasar stas a travs de las capas de la aplicacin, por ejemplo, la capa de negocio transfiere a la capa Web instancias desconectadas para que este realice la presentacin de los datos. La capa Web tambin puede realizar cambios sobre una instancia desconectada y devolverla despus a la capa de negocio para que se vuelva a gestionar la instancia y se transfieran a la BD los cambios realizados sobre ella. Eliminada. Cuando una instancia pasa al estado de eliminada, el EntityManager procede a eliminar de la BD los datos asociados a la misma. La eliminacin de una instancia se lleva a cabo a travs del mtodo remove() de EntityManager. A pesar de estar en este estado, cuando una instancia est eliminada puede seguir siendo manipulada por la aplicacin, si bien estos datos existirn slo en memoria, no en BD.

2.4.3. Mtodos de la interfaz EntityManager Una vez analizado el ciclo de vida de una entidad, vamos a presentar los principales mtodos de la interfaz EntityManager, mtodos que sern utilizados por la aplicacin (habitualmente, la capa de negocio) para operar con las entidades. Estos mtodos son: persist(). Invocaremos a este mtodo despus de crear e inicializar una instancia de la entidad. Al hacerlo, el EntityManager insertar los datos de la entidad en la base de datos y la llevar al estado gestionada. El mtodo necesita que se le proporcione como parmetro el objeto a persistir. El siguiente cdigo crea un objeto de la entidad Libro utilizada anteriormente como ejemplo y procede a persistirla llamado al mtodo persist(): Libro lb=new Libro(); lb.setIsbn("4444444"); lb.setAutor("pepito perez"); lb.setEditorial("anonima"); lb.setPaginas(400); lb.setTitulo("ASP.NET"); lb.setPrecio(40); em.persist(lb); find(). Devuelve una entidad a partir de su clave primaria. Cuando se hace la llamada a este mtodo, el EntityManager lanza una instruccin SQL de tipo Select a la base de datos para recuperar los datos de la instancia asociada a la clave primaria proporcionada como parmetro. Una vez recuperados los datos, crea una instancia, la rellena con los datos y la devuelve al programa, pero si no se encuentra ningn registro asociado a esa clave primaria, la llamada a find() devolver null. El mtodo find() tiene el siguiente formato: public <T> T find(Class<T> clase_entidad, Object clave_primaria) donde clase_entidad representa la clase de la entidad.

El siguiente ejemplo recupera un libro que tiene como ISBN asociado 287734: Libro lb; lb=em.find (Libro.class, 287734);

Una vez recuperada la instancia, la entidad pasa al estado de gestionada. Si se quiere modificar alguno de sus datos, tan slo tendremos que llamar al mtodo set correspondiente: lb.setPrecio(38.5); //cambia el precio del libro

remove(). Elimina de la base de datos el registro asociado a la instancia que se le proporciona como parmetro. Despus de invocar a remove(), la instancia deja de estar gestionada y pasa al estado de eliminada. La siguiente instruccin elimina el registro asociado a la instancia referenciada por lb: em.remove(lb);

Tras la eliminacin del registro, la instancia sigue existiendo en memoria y se puede acceder a ella a travs de la variable lb. refresh(). Refresca una entidad desde la base de datos, es decir, actualiza los campos de la instancia con los valores existentes actualmente en la BD. La siguiente instruccin refresca los datos de la instancia referenciada por lb con la informacin existente en BD: em.refresh(lb); Si la instancia se encuentra en estado desconectada al llamar a refresh(), pasar de nuevo a conectada tras invocar al mtodo. merge(). Pasa al estado de gestionada la instancia que se le proporciona como parmetro, actualizando la base de datos con los datos de la entidad. Este mtodo es til cuando, tras actualizar una entidad no gestionada por parte de la capa Web, se devuelve a la capa de negocio para que actualice los cambios en la BD. La siguiente instruccin trasladara a la BD toda la informacin contenida en la variable lb: em.merge(lb); Al igual que en el caso de refresh(), si la instancia se encuentra en estado desconectada al llamar a mege(), pasar de nuevo a conectada tras su ejecucin. detach(). Pasa al estado desconectada la instancia que se le proporciona como parmetro. La siguiente instruccin provocar que la instancia apuntada por lb pase al estado desconectada: em.detach(lb); clear(). Provoca que todas las instancias que forman parte del contexto de persistencia pasen al estado desconectada.

close(). Cierra el EntityManager.

A parte de los mtodos anteriores, hay otros mtodos de inters de EntityManager que sern analizados ms adelante cuando se estudie el apartado de consultas.

3. DESARROLLO DE UNA APLICACIN JPA CON NETBEANS


Una aplicacin JPA puede ser un servlet, un EJB (es lo ms habitual) o incluso una aplicacin Java de escritorio. En cualquier caso, las tareas que ser necesario realizar en la aplicacin que va a hacer uso de entidades a travs de JPA son las siguientes: Configurar la unidad de persistencia. Definir la entidades de la aplicacin Codificar las instrucciones JPA

En las secciones anteriores ya hemos analizado de forma terica en qu consisten estas tareas, seguidamente vamos a explicar cmo realizarlas utilizando el entorno de desarrollo NetBeans.

3.1. CONFIGURACIN DE PERSISTENCIA EN NETBEANS Si vamos a desarrollar una aplicacin JPA con NetBeans, podemos hacer uso del asistente de configuracin de persistencia que genera automticamente el archivo de unidad de persistencia presistence.xml. Independientemente del tipo de aplicacin que sea (aplicacin de escritorio, mdulo EJB, etc.), podemos hacer uso del asistente situndonos encima del proyecto y eligiendo en el men contextual la opcin New->Other. Despus, en el cuadro de dilogo nuevo fichero, elegiremos Persistence unit dentro de la categora persistence (figura 5).

Figura. 5.

A continuacin, aparecer un cuadro de dilogo en el que se nos solicitar el nombre que queremos asignar a la unidad de persistencia, el tipo de gestor de persistencia y la conexin a la base de datos (figura 6).

Figura. 6. En este ltimo campo Database Connection, si an no tuviramos configurada ninguna conexin, elegiremos la opcin New Database Connection para configurar una nueva conexin con la base de datos donde se persistirn los objetos. Si la aplicacin que estamos desarrollando se va a ejecutar en un servidor de aplicaciones GlassFish, en vez de Database Connection aparecer un campo llamado Datasource donde tendremos que elegir el DataSource (o crear uno nuevo) que utilizar el contenedor para acceder a la base de datos. En la opcin Table Generation Strategy elegiremos none si vamos a utilizar tablas ya existentes en la base de datos. Al pulsar el botn Finish se crear el directorio META-INF dentro de la aplicacin y en su interior el archivo persistence.xml con la configuracin de persistencia establecida. Si queremos aadir una nueva unidad de persistencia deberamos seguir de nuevo los pasos descritos, registrndose los datos de la nueva unidad en el archivo persistence.xml existente.

3.2. DEFINICIN DE ENTIDADES. La creacin de entidades en un proyecto resulta tremendamente sencilla en NetBeans; a partir de la informacin relativa a la base de datos el asistente es capaz de generar una entidad sin necesidad de que tengamos que escribir una sola lnea de cdigo. Situndonos sobre el proyecto elegiremos la opcin New->Other en el men contextual y en cuadro de dilogo que aparece a continuacin elegiremos la opcin Entitiy Clases from Database dentro de la categora persistence (figura 7).

Figura. 7. En el siguiente paso, una vez seleccionada la conexin con la base de datos, elegiremos la tabla o tablas desde las que queremos construir la entidad (figura 8)

Figura. 8. A continuacin, indicaremos el nombre la clase que queremos generar para cada tabla elegida y el paquete en el que se almacenarn. La casilla Generate Named Query Annotations for Persistence Fields la mantendremos desactivada (figura 9).

Figura. 9. Por ltimo, se nos solicita determinadas opciones adicionales de mapeo donde, habitualmente, dejaremos los valores definidos por defecto.

Figura. 10. Al pulsar el botn Finish se generar automticamente la entidad y se registrar en la unidad de persistencia definida en el proyecto (figura 11).

Figura. 11. Si vemos el cdigo de la clase de entidad generada, observaremos como el asistente genera una serie de anotaciones para cada campo (@Column y @Basic) que no seran necesarias cuando se utiliza la configuracin por defecto, pero que se crean para facilitar al programador la personalizacin de propiedades para cada campo. El siguiente listado corresponde al cdigo de la clase Libro generada por el asistente de NetBeans: @Entity @Table(name = "libros") @NamedQueries({@NamedQuery(name = "Libro.findAll", query = "SELECT l FROM Libro l")}) public class Libro implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "isbn")

private String isbn; @Basic(optional = false) @Column(name = "titulo") private String titulo; @Basic(optional = false) @Column(name = "precio") private float precio; @Basic(optional = false) @Column(name = "paginas") private int paginas; @Basic(optional = false) @Column(name = "autor") private String autor; @Basic(optional = false) @Column(name = "editorial") private String editorial; //mtodos set/get : } En este caso, podramos prescindir de las anotaciones @Column, puesto que todos los nombres de campo coinciden con los de las columnas de la BD. En cuanto a la anotacin @Basic, se utiliza para definir algunas propiedades adicionales sobre los campos de tipos primitivos, String y clases de envoltorio. Sus atributos son: optional. Indica si el campo admite o no valor null, por lo que solo tiene sentido en campos de tipo objeto como String y clases de envoltorio. Si los campos son de tipo primitivo su valor es ignorado. El valor por defecto de este atributo es false. fetch. Indica la forma en que se cargar el valor del campo. Su valor puede influir en el rendimiento de la aplicacin. Los posibles valores de este atributo estn definidos en la enumeracin FetchType, y son: o EAGER. El valor del campo se carga al crear la instancia de la entidad a partir de la BD. Es el valor por defecto. Si alguno de los campos es de gran capacidad y su valor no va a ser utilizado inicialmente, no conviene emplear este modo de carga para no consumir recursos de forma innecesaria. LAZY. El campo no se carga inicialmente, slo cuando es accedido desde la aplicacin. Si se van a realizar accesos continuos al campo, conviene utilizar el valor EAGER en vez de LAZY, de forma que el dato est disponible desde el primer momento y no tenga que recargarse cada vez que se invoque al mtodo get.

Tambin podemos observar la anotacin @NamedQueries delante de la declaracin de la clase. Dicha anotacin ser estudiada ms adelante en el apartado dedicado al lenguaje de consultas JPQL, por el momento podemos ignorarla e incluso eliminarla.

3.3. CODIFICACIN DE LAS INSTRUCCIONES JPA Una vez creadas las entidades, podemos hacer uso de las mismas desde el cdigo de la aplicacin. Para poder manipular entidades desde cdigo tendremos que realizar las siguientes acciones:

Obtencin del EntityManager Llamadas a los mtodos del EntityManager.

Anteriormente vimos como se puede obtener un objeto EntityManager a partir de la interfaz EntityManagerFactory, lo que se conoce como EntityManager gestionado por aplicacin (la aplicacin debe encargarse de cerrarlo explcitamente). Sin embargo, si quien va a manipular las entidades es un EJB de sesin (como va a suceder en la mayora de las ocasiones) se puede utilizar la anotacin @PersistenceContext para inyectar una referencia en un campo del EJB al objeto EntityManager asociado a una unidad de persistencia, simplemente indicando en el atributo name de la anotacin el nombre de dicha unidad de persistencia. El siguiente cdigo de ejemplo corresponde a un EJB de sesin que utiliza esta tcnica para obtener una referencia a un EntityManager: @Stateless public class PruebasJPABean implements PruebasJPALocal { @PersistenceContext(name="pruebas_jpa-ejbPU") private EntityManager em; : } A los EntityManager obtenidos mediante inyeccin de dependencia se les conoce como EntityManagers gestionados por el contenedor, en donde el contenedor se encarga de la obtencin y cierre del mismo. Este tipo de EntityManager slo puede ser utilizado en aplicaciones que se ejecuten en un contendor EJB. Desde NetBeans es posible generar de forma automtica las instrucciones para la obtencin del objeto EntityManager gestionado por el contenedor. Para ello, nos situaremos en la vista de cdigo de la clase de implementacin del EJB cliente y en el men contextual elegiremos la opcin Persistence -> Use Entity Manager. En el caso de que se quiera obtener un EntityManager de aplicacin desde un servlet o una aplicacin cliente Java de escritorio, el proceso es el mismo: en el interior de la vista de cdigo de la clase se elegir Persistence -> Use Entity Manager. En este caso, el asistente no solo genera las instrucciones necesarias para obtener el EntityManager, sino que tambin define una transaccin en la que se incluye una llamada al mtodo persist(). El siguiente cdigo de ejemplo corresponde a una clase estndar Java en la que se incluye el cdigo generado por el asistente al incluir la referencia al EntityManager: public class Test { public static void main(String[] args) { } public void persist(Object object) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("ejemplo2PU");

EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); try { em.persist(object); em.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); em.getTransaction().rollback(); } finally { em.close(); } }

} Obsrvese en el ejemplo anterior la utilizacin del mtodo getTransaction() del EntityManager para obtener un objeto EntityTransaction y la posterior llamada a los mtodos begin(), commit() y rollback() para controlar la transaccin. Estas operaciones no son necesarias cuando se emplea el EntityManager desde un EJB, puesto que el contenedor de ste se encarga de la gestin automtica de trasacciones. Independientemente de la manera en que se haya obtenido, una vez que disponemos de la referencia al EntityManager puede hacerse uso de los mtodos explicados anteriormente para manipular las entidades.

EJERCICIO 1

En este ejercicio vamos a desarrollar las capas de persistencia y negocio de una aplicacin para la gestin de alumnos. La aplicacin atacar una base de datos, llamada formacin, que, en un principio, cuenta con una tabla alumnos donde se almacenan los datos de los alumnos registrados. En la tabla de figura 12 se indica el nombre y tipo de estos campos, siendo dni el que contiene la clave primaria.

Campo
dni nombre edad email curso

Tipo de dato
varchar(12) varchar(100) integer varchar(45) varchar(100)
Figura. 12.

La capa de negocio estar implementada mediante un EJB de sesin que dispondr de los siguientes mtodos: agregarAlumno(). Recibir los datos de un alumno y lo dar de alta en el sistema. eliminarAlumno(). A partir del dni del alumno, realizar la eliminacin del registro correspondiente.

recuperarAlumno(). A partir del dni del alumno, devolver un objeto que encapsule el nombre, email y curso del alumno. actualizarAlumno(). Proceder a actualizar en la base de datos las modificaciones realizadas en el objeto Alumno recibido como parmetro.

Por su parte, la capa de persistencia ser implementada mediante un entidad Alumno. El cdigo de la misma se muestra en el siguiente listado, donde podemos ver la sobrescritura automtica de algunos mtodos heredados de Object que realiza NetBeans al crear la entidad: package entidades; import java.io.Serializable; import javax.persistence.*;

@Entity @Table(name = "alumnos") public class Alumnos implements Serializable { @Id private String dni; private String nombre; private int edad; private String email; private String curso; public Alumnos() { } public Alumnos(String dni) { this.dni = dni; } public Alumnos(String dni, String nombre, int edad, String email, String curso) { this.dni = dni; this.nombre = nombre; this.edad = edad; this.email = email; this.curso = curso; } public String getDni() { return dni; } public void setDni(String dni) { this.dni = dni; } public String getNombre() { return nombre;

} public void setNombre(String nombre) { this.nombre = nombre; } public int getEdad() { return edad; } public void setEdad(int edad) { this.edad = edad; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getCurso() { return curso; } public void setCurso(String curso) { this.curso = curso; } @Override public int hashCode() { int hash = 0; hash += (dni != null ? dni.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { if (!(object instanceof Alumnos)) { return false; } Alumnos other = (Alumnos) object; if ((this.dni == null && other.dni != null) || (this.dni != null && !this.dni.equals(other.dni))) { return false; } return true;

} @Override public String toString() { return "entidades.Alumnos[dni=" + dni + "]"; } } El componente ser implementado como un EJB sin estado que, a travs de los mtodos del EntityManager inyectado, realizar las operaciones de negocio sobre la entidad Alumno. Su cdigo se muestra en el siguiente listado: package ejbs;

import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import entidades.*;

@Stateless public class GestionAlumnosBean implements GestionAlumnosLocal { @PersistenceContext(name="pruebas_jpa-ejbPU") private EntityManager em;

public void agregarAlumno(String dni, String nombre, int edad, String email, String curso) { //crea la instancia de la entidad y la pesiste Alumno al=new Alumno(dni,nombre,edad,email,curso); em.persist(al); } public void eliminarAlumno(String dni) { //recupera la instancia asociada y despus la elimina Alumno al=(Alumno)em.find(Alumno.class, dni); if(al!=null){ em.remove(al); } } public Alumno recuperarAlumno(String dni){ Alumno al=(Alumno)em.find(Alumno.class, dni); return al; } public void actualizararAlumno(Alumno al){ em.merge(al); }

4. CLAVES PRIMARIAS
Toda entidad debe definir una clave primaria que la identifique de forma unvoca y que permita asociar cada instancia de la entidad a un registro especfico de la base de datos. El valor de la clave primaria de un entidad se almacena en los campos de la misma. En funcin del nmero de campos que forman la entidad, la clave primaria puede ser simple o compuesta.

4.1. CLAVE PRIMARIA SIMPLE Se dice que la clave primaria de una entidad es simple cuando est definida por un nico valor. Este valor es almacenado en uno de los campos de la entidad, cuyo tipo puede corresponder a cualquiera de los tipos primitivos Java, as como a las clases de envoltorio, String y Date, y deber estar marcado con la anotacin @Id: @Entity @Table(name = "libros") public class Libro implements Serializable { @Id private String isbn; //campo que almacenar //la clave primaria private String titulo; :

4.2. CLAVE PRIMARIA COMPUESTA Est formada por la combinacin de dos o ms valores. En este caso, se deber definir una clase a parte, conocida como clase identificadora, que encapsule los campos que forman la clave. Esta clase tendr que sobrescribir los mtodos equals() y hashCode() para permitir las operaciones de ordenacin, adems de implementar la interfaz Serializable. El siguiente listado corresponde a una clase que define una clave primaria para entidades de tipo Alumno a partir de los campos nombre y curso: public class DatosPK implements Serializable{ String nombre; String curso; public boolean equals(Object obj){ if(obj instanceOf DatosPK){ DatosPK pk=(DatosPK)obj; return (pk.nombre.equals(nombre) && pk.curso.equals(curso)); } return false;

} public int hashCode(){ return (nombre+curso).length(); } } Una vez definida la clase identificadora, existen dos posibilidades a la hora de indicar en la entidad la clave primaria utilizada: Utilizar la anotacin @IdClass. En la declaracin de la clase de entidad, se utilizar la anotacin @IdClass para indicar la clase identificadora. En este caso, los campos utilizados en dicha clase debern estar definidos tambin en la clase de entidad (con los mismos nombres) y tendrn que ser declarados con la anotacin @Id. El siguiente listado representa la entidad Alumno que hace uso de la clase DatosPK definida anteriormente y donde vemos que los campos nombre y curso, definidos en la clase identificadora, tambin lo estn en la entidad: @Entity @IdClass(DatosPK.class) public class Alumno{ @Id String nombre; @Id String curso; //resto de cdigo de la clase : } Utilizar claves embebidas. En este caso se incluir en la clase de entidad un campo del tipo de la clase identificadora, definindose dicho campo mediante la anotacin @EmbeddedId. De esta manera, se evita duplicar la definicin de los campos que forman la clave en las clases identificadora y entidad. Para poder aplicar esta tcnica la clase identificadora deber estar declarada con la anotacin @Embeddable: @Embeddable public class DatosPK implements Serializable{ String nombre; String curso; : } @Entity public class Alumno{ @EmbeddedId DatosPK dt; //resto de cdigo de la clase

: }

4.3. GENERACIN AUTOMATICA DE CLAVES Las claves constituidas por datos que forman parte de la lgica de aplicacin, como un DNI o el ISBN de un libro, son conocidas como claves naturales. Sin embargo hay veces que las tablas que almacenan los datos de la entidad disponen de campos explcitamente creados para servir como clave primaria, pero cuyos valores no tienen ningn significado para la lgica de aplicacin; son las conocidas como claves sustitutas. Un ejemplo de clave sustituta es el id de cliente en una tabla de registro de clientes, el id de producto, etc. Los valores de estas claves sustitutas son generados automticamente por el gestor de base de datos en el momento en que un registro es aadido a la tabla. Si la entidad que vamos a crear dispone de este tipo de clave, ser necesario especificar durante la definicin de la misma que el valor del campo clave es autogenerado. Esto se lleva a cabo utilizando la anotacin @GeneratedValue en la declaracin del campo clave. Esta anotacin dispone de un atributo strategy que indica la forma en que ser generada la clave. El siguiente ejemplo define el campo clave id_persona de la entidad Persona con esta caracterstica: @Entity public class Alumno{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) Long id_persona; : } En el ejemplo anterior se ha indicado como estrategia de generacin automtica el tipo IDENTITY, que indica que la columna correspondiente es de tipo identidad, es decir, se trata de un valor numrico entero correlativo generado de forma automtica por el gestor de base de datos durante la insercin el registro. Cuando una entidad incluye un campo de tipo clave autogenerada, no habr que asignar explcitamente ningn valor al mismo para poder llamar al mtodo persist(). Dicho campo ser rellenado automticamente por el gestor de persistencia una vez que la entidad pase al estado gestionada despus de haberse persistido.

5. RELACIONES ENTRE ENTIDADES


Cuando hablamos de mapeo objeto-relacional nos referimos al establecimiento de polticas que definen la correspondencia entre las entidades y las tablas de la base de datos, informacin sta que puede suministrarse a travs de anotaciones o archivos de configuracin. En los apartados anteriores hemos visto la manera de indicar algunos de estos datos ms bsicos, como asociacin de campos a columnas o la definicin de claves primarias. Durante esta seccin nos vamos a ocupar de uno de los aspectos ms complejos del mapeo objeto-relacional, como es la definicin de relaciones entre entidades.

Las relaciones entre entidades establecen, como su nombre indica, la manera en que se relacionan las entidades de una aplicacin a fin de que el gestor de persistencia pueda realizar adecuadamente el mapeo de los datos en una base de datos relacional. Segn la manera en que unas entidades se refieren a otras, distinguimos cuatro tipos de relaciones: Uno a uno. Se define con la anotacin @OneToOne. Uno a muchos. Se define con la anotacin @OneToMany. Muchos a uno. Se define con la anotacin @ManyToOne. Muchos a muchos. Se define con la anotacin @ManyToMany.

Vamos a analizar en qu consisten cada una de estas relaciones y la manera de definirlas en las entidades mediante el uso de anotaciones.

5.1. RELACIN UNO A UNO Se da una relacin uno a uno entre dos entidades cuando un objeto de una entidad tiene asociado un objeto de la otra. Por ejemplo, en un contexto de entidades que representan elementos de una obra de teatro, una entidad de tipo Actor estara relacionada con la entidad Personaje en una relacin uno a uno, pues cada actor interpreta un personaje y un personaje es interpretado por un nico actor. Desde el punto de vista de las tablas de la base de datos, estas relaciones se traducen en que una de las tablas contiene una referencia al campo clave de la otra a travs de lo que se conoce como clave externa. La figura 13 indica un ejemplo de cmo podran estar relacionadas las tablas Actores y Personajes.

Tabla actores
Primary key

Tabla personajes id_personaje nombre grado_protagonista


Primary key

Foreign key

dni nombre edad sexo id_personaje_actor

Figura. 13. La entidad cuya tabla asociada contiene la clave externa de la otra tabla se dice que es la propietaria de la relacin, mientras que la otra entidad es el lado inverso de la relacin. En el ejemplo anterior, la entidad Actor sera la propietaria de la relacin y Personaje la inversa. A la hora de implementar las entidades en una relacin uno a uno existen dos posibilidades que dan lugar a dos tipos de relaciones: Unidireccionales. Bidireccionales.

5.1.1. Relaciones uno a uno unidireccionales. Uno de los campos de una entidad (propietaria) contiene una referencia a un objeto de la otra entidad. Por ejemplo, el campo personaje de la clase Actor sera de tipo Personaje y contendra una referencia al personaje asociado, sin embargo, la clase Personaje no incluira ningn campo que lo relacionase con Actor. Para moldear estas relaciones utilizaremos las anotaciones @OneToOne y @JoinColumn en la definicin del campo de la entidad propietaria que contiene la referencia a la otra entidad. La anotacin @JoinColumn deber incluir los siguientes atributos: name. Nombre de la columna de la tabla propietaria que contiene la clave externa. referencedColumnName. Nombre de la columna primary key de la tabla inversa.

El siguiente listado corresponde a la definicin de las clases Actor y Personaje para el caso de una relacin unidireccional uno a uno: package entidades; import java.io.Serializable; import javax.persistence.*;

@Entity public class Actor implements Serializable { @Id private String dni; private String nombre; private int edad; private char sexo; @OneToOne @JoinColumn(name="id_personaje_actor", referencedColumnName="id_personaje") private Personaje personaje; public String getDni() { return dni; }

public void setDni(String dni) { this.dni = dni; }

public String getNombre() { return nombre; }

public void setNombre(String nombre) { this.nombre = nombre; }

public int getEdad() { return edad; }

public void setEdad(int edad) { this.edad = edad; }

public char getSexo() { return sexo; }

public void setSexo(char sexo) { this.sexo = sexo; }

public Personaje getPersonaje() { return personaje; }

public void setPersonaje(Personaje personaje) { this.personaje = personaje; }

package entidades; import java.io.Serializable; import javax.persistence.*;

@Entity public class Personaje implements Serializable { @Id private String id_personaje; private String nombre; private int grado_protagonista;

public String getId_personaje() { return id_personaje; }

public void setId_personaje(String id_personaje) { this.id_personaje = id_personaje; }

public String getNombre() { return nombre; }

public void setNombre(String nombre) { this.nombre = nombre; }

public int getGrado_protagonista() { return grado_protagonista; }

public void setGrado_protagonista(int grado_protagonista) { this.grado_protagonista = grado_protagonista; }

5.1.2. Relaciones uno a uno bidireccionales En este caso, ambas entidades (propietaria e inversa) contienen un campo que incluye una referencia a la otra entidad. De cara a modelar este tipo de relacin, la entidad propietaria ser definida de la misma forma que en el caso de la relacin uno a uno unidireccional, mientras que la entidad inversa deber incluir la anotacin @OneToOne en el campo que contiene la referencia a la otra entidad propietaria, indicando en el atributo mappedBy el nombre del campo del lado propietario que contiene la referencia al lado inverso. En el siguiente listado se indica la definicin la clase Personaje (Actor permanece invariable) en caso de que se establezca una relacin bidireccional entre ambas: @Entity public class Personaje implements Serializable { @Id private String id_personaje; private String nombre;

private int grado_protagonista;

@OneToOne (mappedBy="personaje") private Actor actor;

public Actor getActor() { return actor; }

public void setActor(Actor actor) { this.actor = actor; } }

5.2. RELACIN UNO A MUCHOS Y MUCHOS A UNO En una relacin uno a muchos un objeto de una entidad est asociado a varios objetos de la otra entidad. Esto se traduce en que la entidad del lado uno incluir un campo de tipo coleccin con los objetos asociados del lado muchos. Dado que la tabla asociada a la entidad del lado muchos es la que contiene la clave externa, ser esta entidad la propietaria de la relacin. Cuando la relacin uno a muchos es bidireccional, estamos tambin ante una relacin muchos a uno vista desde la entidad del lado muchos, en la que existir un campo que contendr una referencia a la entidad del lado uno con la que se relaciona. Para modelar este tipo de relaciones habr que emplear las siguientes anotaciones en las clases de entidad: @OneToMany. En la clase del lado uno habr que incluir esta anotacin en la declaracin del campo que contiene la coleccin de objetos del lado muchos, indicando en el atributo mappedBy el nombre del campo de relacin de la otra entidad (propietaria). @ManyToOne. Esta anotacin se incluir en el campo de la entidad propietaria (lado muchos) que contendr la referencia al objeto de la entidad inversa con el que est relacionado. @JoinColumn. En el campo indicado anteriormente se incluir, adems de @ManyToOne, la anotacin @JoinColumn, en la que se indicar a travs de sus atributos name y referencedColumnName, los nombres de las columnas que contienen las claves externa y primaria, respectivamente, en las tablas propietaria e inversa.

Por ejemplo, las entidades Departamento y Empleado representan este tipo de relacin, puesto que varios empleados pertenecen a un mismo departamento. Los siguientes listados corresponden al cdigo de implementacin de dichas clases: import java.io.Serializable;

import javax.persistence.*; @Entity @Table(name = "empleados") public class Empleado implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "codigo_empleado") private Integer codigoEmpleado; private String nombre; private int edad; private double salario; @Column(name = "nivel_profesional") private int nivelProfesional; @ManyToOne() @JoinColumn(name="id_departamento", referencedColumnName="id_departamento") private Departamento dep; //mtodos set/get : }

import java.io.Serializable; import javax.persistence.*; @Entity @Table(name = "departamentos") public class Departamento implements Serializable { @Id @Column(name = "id_departamento") private String idDepartamento; private String departamento; private String responsable; private double presupuesto; @OneToMany(mappedBy="dep") private List<Empleado> emps; //mtodos set/get : }

La relacin entre las tablas departamentos y empleados se muestra en la figura 14 , en la que se puede ver, como la tabla tabla Empleados contiene la clave externa que permite identificar el departamento al que pertenece.

Tabla empleados
Primary key

Tabla departamentos id_departamento departamento responsable presupuesto


Primary key

Foreign key

codigo_empleado nombre edad salario nivel_profesional id_departamento

Figura. 14.

5.3. RELACIN MUCHOS A MUCHOS En una relacin de este tipo cada objeto de una entidad se relaciona con varios objetos de otra entidad y viceversa, lo que se traduce en que ambas entidades disponen de un atributo de tipo coleccin que mantiene una referencia al conjunto de objetos de la otra entidad con la que se relaciona. Un ejemplo de este tipo de relacin sera el que se da entre los libros y autores en una aplicacin de librera, donde cada libro puede estar escrito por varios autores y cada autor puede escribir a su vez varios libros. En este caso, tendramos una entidad Libro que mantendra un dato miembro con la coleccin de autores que lo han escrito; a su vez, la entidad Autor incluira una coleccin con los libros que ha escrito. Desde el punto de vista de la base de datos, las relaciones muchos a muchos implican la utilizacin de tres tablas, las dos tablas de datos y una intermedia, conocida como tabla join, que contiene las claves primarias de las dos tablas de datos y que se relaciona con estas en una relacin uno a muchos. La figura 15 nos muestra cmo quedaran las tablas libros y autores asociadas a las entidades de ejemplo comentadas, junto con la tabla join que las relaciona.
Tabla libros
Primary key

Tabla join
foreign key

Tabla autores codigo_autor nombre premiado


Primary key

isbn titulo precio paginas editorial

isbn codigo_autor

foreign key

Foreign key

Figura. 15. En una relacin muchos a muchos no existe un criterio definido para establecer cul de las entidades debe corresponder al lado propietario de la relacin y cul al lado inverso, por lo que estos papeles pueden ser asignados libremente. Para modelar este tipo de relaciones habr que emplear las siguientes anotaciones:

@ManyToMany. Se emplear esta anotacin en la declaracin del campo de relacin de ambas clases, aunque en la entidad que se haya elegido como lado inverso de la relacin habr que indicar a travs del atributo mappedName el nombre del campo de relacin de la otra entidad. @JoinTable. A travs de esta anotacin indicamos los datos de la tabla join que relaciona las tablas asociadas a las entidades. Se utilizar en la entidad que hayamos definido como propietaria de la relacin y deber incluir los siguientes atributos: o o name. Nombre de la tabla join. joinColumns e inverseJoinColumns. A travs de las anotaciones @JoinColumns, especifican los campos que definen las relaciones entre la tabla join y las tablas de datos. Se deber incluir una anotacin @JoinColumn en cada atributo (uno por cada relacin), indicando en su atributo name el nombre del campo de la tabla join que contiene la clave externa y en referencedColumnName el nombre del campo de la tabla de datos que contiene la clave primaria.

Para aclarar la manera en la que se emplean estas anotaciones, se muestra el siguiente listado que corresponde al cdigo de las entidades de ejemplo Libro y Autor, habindose elegido Libro como entidad propietaria de la relacin: @Entity @Table(name= libros) public class Libro implements Serializable{ @Id private String isbn; private String titulo; private float precio; @Column(name= totalpaginas) private int paginas; private String editorial; @ManyToMany @JoinTable (name= libros_autores, joinColumns= @JoinColumn(name= isbn, referencedColumnName = isbn), inverseJoinColumns= @JoinColumn ( name= codigo_autor, referencedColumnName= codigo_autor)) private Set<Autor> autores; //mtodos set/get : }

@Entity @Table(name= autores)

public class Autor implements Serializable{ @Id private int codigo_autor; private String nombre; private boolean premiado; @ManyToMany (mappedBy= autores) private Set<Libro> libros; //mtodos set/get :

EJERCICIO 2

Vamos a crear una pequea aplicacin Web que, a partir de las entidades Empleado y Departamento utilizadas anteriormente de ejemplo, incluya una pgina inicial que muestre una lista con los nombres de todos los departamentos existentes. Una vez seleccionado el departamento y tras pulsar un botn mostrar empleados, se mostrarn en otra pgina todos los datos de los empleados existentes en ese departamento (figura 16).

Figura. 16. La lgica de negocio de la aplicacin ser encapsulada en EJB y el acceso a los datos se realizar a travs de una capa de persistencia. Las clases de entidad Empleado y Departamento son las mismas que se utilizaron en el ejemplo de relacin uno a muchos. La clase de implementacin del EJB que encapsula la lgica de negocio de la aplicacin se muestra en el siguiente listado: package ejbs; import entidades.*; import java.util.*; import javax.ejb.Stateless; import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext; import java.sql.*; import javax.naming.*; import javax.sql.DataSource;

@Stateless public class ManejoEntidadesBean implements ManejoEntidadesLocal { @PersistenceContext private EntityManager em; public List<Departamento> obtenerDepartamentos(){ Connection cn=null; Statement st=null; ResultSet rs=null; DataSource ds=null; ArrayList<Departamento> lista= new ArrayList<Departamento>(); try{ InitialContext ctx=new InitialContext(); //jdbc/empresas es el datasource definido en la //unidad de persistencia ds=(DataSource)ctx.lookup("jdbc/empresas"); cn=ds.getConnection(); st=cn.createStatement(); rs=st.executeQuery("select * from departamentos"); while(rs.next()){ Departamento dep=new Departamento( rs.getString("id_departamento"), rs.getString("departamento"), rs.getString("responsable"), rs.getDouble("presupuesto")); lista.add(dep); } } catch(Exception e){ e.printStackTrace(); } return lista; }

public List<Empleado> obtenerEmpleados(

String idDepartamento) { Departamento dep; dep=em.find(Departamento.class, idDepartamento); return dep.getEmps(); } }

Inicialmente, el mtodo obtenerDepartamentos() lo hemos implementado directamente con JDBC, pero cuando veamos como generar consultas con JPQL realizaremos una nueva versin basada en el uso de la capa de persistencia. Si nos fijamos en el mtodo obtenerEmpleados() veremos la parte ms interesante de este ejercicio, que es donde se utiliza la capa de persistencia y se aprecia la potencia de las relaciones. Observamos como gracias a la relacin definida en la entidades, al obtener una instancia de Departamento el EntityManager recupera automticamente la lista de empleados asociada y la deposita en el campo de relacin emps definido en dicha entidad. La aplicacin Web est implementada siguiendo el patrn MVC, siendo el siguiente listado el correspondiente al servlet controlador: package servlets;

import java.io.IOException; import javax.ejb.EJB; import ejbs.*; import javax.servlet.http.*; import javax.servlet.*; public class Control extends HttpServlet { @EJB private ManejoEntidadesLocal manejoEntidadesBean; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String op=request.getParameter("operacion"); if(op==null){ request.setAttribute("departamentos", manejoEntidadesBean.obtenerDepartamentos()); RequestDispatcher rq=request. getRequestDispatcher("/listadoDepartamentos.jsp"); rq.forward(request, response); } else if(op.equals("mostrarempleados")){ String idDep=request.getParameter("codigo"); request.setAttribute("empleados",

manejoEntidadesBean.obtenerEmpleados(idDep)); RequestDispatcher rq=request. getRequestDispatcher("/listadoEmpleados.jsp"); rq.forward(request, response); }

} }

En cuanto a las vistas, se trata de dos pginas JSP, una (pagina inicial) encargada de generar la lista de departamentos (listadoDepartamentos.jsp) y otra que genera la tabla con los datos de los empleados del departamento elegido (listadoEmpleados.jsp). He aqu el cdigo de ambas pginas: <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Departamentos</title> </head> <body> <center> <h1>Departamentos Registrados</h1> <form action="control?operacion=mostrarempleados" method="post"> <select name="codigo" > <c:forEach var="dep" items="${departamentos}" > <option value="<c:out value= '${dep.idDepartamento}'/>"> <c:out value='${dep.departamento}'/> </option> </c:forEach>

</select> <br/> <br/>

<input type="submit" value="Ver empleados"/> </form> </center> </body> </html>

<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html> <head>

<title>Empleados</title> </head> <body> <center> <h1>Listado de empleados</h1> <table width="60%" border="1"> <tr> <th>Nombre</th> <th>Edad</th> <th>Salario</th> </tr> <c:forEach var="emp" items="${empleados}" > <tr> <td><c:out value="${emp.nombre}"/>></td> <td><c:out value="${emp.edad}"/></td> <td><c:out value="${emp.salario}"/></td> </tr> </c:forEach> </table> </center> </body> </html>

5.4. ACTUALIZACIONES EN CASCADA Cuando se realiza desde cdigo una operacin sobre una entidad a travs de los mtodos de EntityManager, como la persistencia de la entidad (llamada al mtodo persist()) o la eliminacin de la entidad (llamada al mtodo remove()), y dicha entidad est relacionada con otra, dicha operacin no se refleja de forma predeterminada en la entidad relacionada. Por ejemplo, en el caso de las entidades Actor y Personaje expuestas anteriormente como ejemplo de relacin uno a uno, si se crea una nueva instancia de Actor y se le asigna un nuevo objeto personaje en el dato miembro correspondiente, la llamada a persist() pasando como parmetro la instancia de Actor provocar la insercin del nuevo registro en la tabla actores, pero no se insertar el nuevo Personaje en la tabla personajes, pues de forma predeterminada, las operaciones de persistencia sobre una entidad no se propagan a las entidades relacionadas. Si queremos cambiar este comportamiento por defecto y hacer que las operaciones sobre una entidad se propaguen en cascada a las entidades relacionadas, habr que indicarlo explcitamente en la relacin a travs del atributo cascade de la anotacin. La siguiente declaracin de relacin uno a uno de ejemplo define una actualizacin en cascada consistente en la propagacin de la operacin persist() a la entidad relacionada: public class Actor{ @OneToOne(cascade=CascadeType.PERSIST) private Personaje personaje; : } Adems de PERSIST, la enumeracin CascadeType incluye otras constantes para que definen las diferentes opciones de actualizacin, estas son: CascadeType.MERGE. Propaga la operacin merge sobre una entidad a las entidades relacionadas. CascadeType.REFRESH. Propaga la operacin refresh sobre una entidad a las entidades relacionadas. CascadeType.REMOVE. Propaga la operacin remove sobre una entidad a las entidades relacionadas. CascadeType.ALL. Propaga todas las operaciones sobre una entidad a las entidades relacionadas.

6. LENGUAJE JPQL Y CONSULTAS


Adems del mtodo find() incluido en la interfaz EntityManager para localizar entidades a partir de su clave primaria, a travs del lenguaje Java Persistence Query Language (JPQL) es posible generar consultas que nos permitan acceder a un conjunto de entidades que cumplan una determinada condicin.

6.1. SINTAXIS JPQL El lenguaje JPQL es muy similar a SQL, si bien su sintaxis est adaptada al tratamiento de entidades en vez de datos. Por ejemplo, si quisiramos recuperar todas las entidades de

tipo Libro cuyo precio fuera superior a 20, la instruccin JPQL que tendramos que utilizar sera la siguiente: Select e From Libro e where e.precio>20 Donde la letra e (podra utilizarse cualquier otra) hace referencia a la entidad indicada en la clusula From, en este caso Libro. Dicha letra se deber indicar a continuacin de Select y para hacer referencia a los campos de la entidad dentro de la condicin especificada en Where. Se pueden definir consultas JPQL que incluyan el join de dos o ms entidades. Por ejemplo, en esta instruccin se obtienen todos los libros cuyo nombre de autor comienza por Luis: Select e from Libro e join e.autores a where a.nombre like (Luis%) Las consultas JPQL tambin pueden ser de accin, de modo que en vez de devolver un conjunto de entidades lleven a cabo algn tipo de operacin sobre las mismas. Estas consultas de accin pueden ser de actualizacin (update) o eliminacin (delete). Por ejemplo, la siguiente instruccin permitira modificar el precio de un libro que tenga un determinado ISBN: Update Libro e set e.precio=35 where e.isbn=23834 Esta otra servira para realizar la eliminacin de un libro a partir del isbn: Delete Libro e where e.isbn=23834

6.2. CREACIN DE CONSULTAS La ejecucin de instrucciones JPQL en un programa se lleva a cabo a travs la interfaz Query del API JPA. Un objeto Query puede crearse utilizando uno de los siguientes mtodos de EntityManager: createQuery. Crea un objeto Query a partir de la instruccin JPQL, lo que se conoce tambin como una consulta dinmica. Por ejemplo, la siguiente instruccin creara un objeto Query asociado a la primera instruccin de ejemplo indicada anteriormente: //la variable em incluye una referencia al objeto EntityManager Query query = em.createQuery(Select e From Libro e where e.precio>20); createNamedQuery. Este mtodo crea un objeto Query a partir de una named query. Una named query es una instruccin JPQL a la que se le asigna un nombre durante la definicin de una entidad. Para definir una named query se utilizar la anotacin @NamedQuery en la entidad a la que va a estar asociada, indicando en su atributo name el nombre asignado a la consulta y en query la instruccin JPQL que la define. Por ejemplo, para definir una named query en la entidad Libro con la consulta de ejemplo anterior sera: @Entity @NamedQuery( name= buscarLibros, query= Select e From Libro e where e.precio>20) public class Libro implements Serializable{

: } La creacin del objeto Query, que se llevara a cabo en la capa de negocio de la aplicacin, se realizara de la siguiente manera: Query query=em.createNamedQuery(buscarLibros); La utilizacin de consultas named query proporciona ciertas ventajas respecto al uso de consultas dinmicas, como por ejemplo el hecho de que la consulta se define una sola vez y puede ser utilizada en distintas partes de la aplicacin. Si queremos definir ms de una named query en una entidad, deberamos emplear la anotacin @NamedQuerys para definir una coleccin de named querys. El siguiente ejemplo indica cmo realizar esta tarea en el caso de que quisiramos definir dos named querys en la entidad Libro a partir de las instrucciones JPQL de tipo select de los ejemplos anteriores: @Entity @NamedQuerys( { @NamedQuery( name= buscarLibros, query= Select e From Libro e where e.precio>20), @NamedQuery( name= buscarLibrosPorAutor, query= Select e from Libro e join e.autores a where a.nombre like (Luis%)) }) public class Libro implements Serializable{ : } Una ltima consideracin que debemos tener en cuenta respecto a las named querys es que, dado que su mbito se extiende a toda la unidad de persistencia, el nombre asignado a la misma debe ser nico, por lo que no podemos tener dos named querys con el mismo nombre aunque estn definidas en distintas entidades

6.3. EJECUCIN DE UNA CONSULTA Una vez creado el objeto Query podemos utilizar cualquiera de los siguientes mtodos de la interfaz para ejecutar la consulta: List getResultList(). Devuelve un objeto List con las entidades recuperadas por la instruccin JPQL. Por ejemplo, para ejecutar la named query buscarLibros sera: List libros = query.getResultList();

Si queremos controlar la cantidad de entidades devueltas por la instruccin select, podemos hacer uso de los siguientes mtodos de Query antes de invocar a getResultList(): o setMaxResults(int max). Establece el nmero mximo de entidades a devolver. setFirstResult(int pos). Indica la posicin de la primera entidad a devolver del conjunto.

Object getSingleResult(). Lo utilizaremos en el caso de que la consulta JPQL devuelva una nica entidad. Si la consulta devolviera ms de una entidad, se producira una excepcin NotUniqueResultException al llamar a este mtodo. int executeUpdate(). Se utiliza en el caso de que la sentencia JPQL sea de tipo Update o Delete, devolviendo el nmero de entidades afectadas: Query query = em.createQuery(Delete Libro e where e.isbn=23834); System.out.println (Libros eliminados + query.executeUpdate();

6.4. CONSULTAS CON PARMETROS Tanto si utilizamos consultas dinmicas como named queries es posible definir parmetros en la instruccin JPQL, de manera que podamos disponer de consultas ms flexibles en las que determinados datos slo son conocidos en tiempo de ejecucin. Los parmetros pueden ser definidos de dos maneras en una instruccin JPQL: Indicando su posicin. Se indicar la posicin del parmetro dentro del conjunto de estos, precedido por el smbolo ?. Por ejemplo, si queremos definir el lmite de precio como un parmetro dentro de la consulta buscarLibros, sera: @Entity @NamedQuery( name= buscarLibros, query= Select e From Libro e where e.precio>?1)

Mediante un nombre. En este caso, el parmetro es definido con un nombre precedido del smbolo :. Si en el ejemplo anterior definiramos el parmetro con nombre limite quedara: @Entity @NamedQuery( name= buscarLibros, query= Select e From Libro e where e.precio>:limite)

Independientemente de la forma en la que se hayan especificado los parmetros, antes de poder ejecutar la consulta ser necesario suministrar los valores de los mismos. Para ello contamos con el mtodo setParameter() de la interfaz Query. Podemos utilizar cualquiera de las siguientes versiones del mtodo: setParameter(int name, Object value) setParameter(String name, Object value)

En el primer caso se establece el valor del parmetro indicando la posicin del mismo, mientras que en el segundo se utiliza su nombre. El siguiente bloque de ejemplo ejecuta la named query buscarLibros definida anteriormente con el parmetro limite, recuperando un mximo de 10 libros: Query query = em.createNamedQuery(buscarLibros); query.setParameter(limite, 25); //establece valor para el parmetro query.setMaxResults(10); List libros = query.getResultList();

EJERCICIO 3

Vamos a realizar una nueva versin del ejercicio 2 consistente en mostrar la lista de empleados a partir del departamento elegido. En esta nueva versin, aadiremos una pgina de inicio con dos enlaces que nos permitan elegir entre un filtrado por departamentos y un filtrado por nombre (figura 17).

Figura. 17. El enlace Filtrar por departamento nos llevara a la pgina con la lista de departamentos desarrollada en la versin anterior, mientras que Filtrar por nombre nos llevar a una pgina donde se solicitar parte del nombre/apellido del empleado buscado (figura 18).

Figura. 18. Tras indicar este valor y pulsar el botn Mostrar Empleados, se mostrar la lista de empleados cuyo nombre contenga los caracteres indicados. Para resolver la bsqueda de empleados por nombre, definiremos una named query en la entidad Empleado con una instruccin JPQL parametrizada que devuelva el conjunto de entidades Empleado cuyo nombre contenga la cadena definida por el parmetro. Dicha consulta quedara definida como se indica en el siguiente listado: @Entity @Table(name = "empleados") @NamedQuery( name="empleadosPorNombre", query="Select e From Empleado e where e.nombre like :texto") public class Empleado implements Serializable { : } As mismo, definiremos otra named query en la entidad Departamento que recupere la lista completa de departamentos, lo que simplificar la lgica de negocio de la aplicacin respecto a la versin anterior. La nueva definicin de la entidad con la inclusin de la named query quedar como se indica en el siguiente listado: @Entity @Table(name = "departamentos") @NamedQuery( name="totalDepartamentos", query="Select d From Departamento d") public class Departamento implements Serializable { : } En cuanto a la lgica de negocio definida en el EJB ManejoEntidadesBean, incluir, adems de los mtodos obtenerEmpleados() y obtenerDepartamentos() definidos en la versin anterior, el mtodo obtenerEmpleadosPorNombre() que devolver una lista de entidades

Empleado a partir del nombre especificado. El cdigo de la clase de implementacin del EJB quedar como se indica en el siguiente listado, en el que podemos ver la simplificacin del mtodo obtenerDepartamentos() respecto a la versin anterior, gracias a la utilizacin de la named query: package ejbs; import entidades.*; import java.util.*; import javax.ejb.Stateless; import javax.persistence.*;

@Stateless public class ManejoEntidadesBean implements ManejoEntidadesLocal { @PersistenceContext private EntityManager em;

public List<Departamento> obtenerDepartamentos(){ //ejecuta una consulta a partir de la //named query definida en la entidad Departamento Query query=em.createNamedQuery("totalDepartamentos"); return (List<Departamento>)query.getResultList(); }

public List<Empleado> obtenerEmpleados( String idDepartamento) { Departamento dep; dep=em.find(Departamento.class, idDepartamento); return dep.getEmps(); } public List<Empleado> obtenerEmpleadosPorNombre( String texto) { //ejecuta una consulta a partir de la named query //definida en la entidad Empleado Query query=em.createNamedQuery("empleadosPorNombre"); //concatena los caracteres de comodn query.setParameter("texto", %+texto+%); return query.getResultList(); } } El cdigo del servlet controlador quedar como se indica en el siguiente listado: package servlets;

import java.io.IOException; import javax.ejb.EJB; import ejbs.*; import javax.servlet.http.*; import javax.servlet.*; public class Control extends HttpServlet { @EJB private ManejoEntidadesLocal manejoEntidadesBean; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String op=request.getParameter("operacion"); //peticin desde el enlace "filtrar por departamentos" if(op.equals("pordepartamento")){ request.setAttribute("departamentos", manejoEntidadesBean.obtenerDepartamentos()); RequestDispatcher rq=request. getRequestDispatcher("/listadoDepartamentos.jsp"); rq.forward(request, response); } //peticin desde el enlace "filtrar por departamentos" if(op.equals("pornombre")){ RequestDispatcher rq=request. getRequestDispatcher("/solicitarnombre.jsp"); rq.forward(request, response); } //peticin para mostrar empleados por nombre if(op.equals("mostrarpornombre")){ String texto=request.getParameter("texto"); request.setAttribute("empleados", manejoEntidadesBean. obtenerEmpleadosPorNombre(texto)); RequestDispatcher rq=request. getRequestDispatcher("/listadoEmpleados.jsp"); rq.forward(request, response); } //peticin para mostrar empleados por dep else if(op.equals("mostrarpordep")){ String idDep=request.getParameter("codigo");

request.setAttribute("empleados", manejoEntidadesBean.obtenerEmpleados(idDep)); RequestDispatcher rq=request. getRequestDispatcher("/listadoEmpleados.jsp"); rq.forward(request, response); } } } La pgina index.jsp inicial que muestra los dos enlaces con las opciones sera: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Datos de empleados</title> </head> <body> <center> <br/><br/> <a href="control?operacion=pornombre"> Filtrar por nombre </a> <br/><br/> <a href="control?operacion=pordepartamento"> Filtrar por departamento </a> </center> </body> </html> En cuanto a la pgina que solicitarnombre.jsp: <%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP Page</title> </head> <body>

<center> <br/><br/> <form action="control?operacion=mostrarpornombre" method="post"> Introduzca parte del nombre/apellido: <input type="text" name="texto"/> <br/><br/> <input type="submit" value="Ver empleados"/> </form> </center> </body> </html>

La pginas listadoDepartamentos.jsp quedara como se indica a continuacin (solo cambia el valor del parmetro enviado en la peticin): <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Departamentos</title> </head> <body> <center> <h1>Departamentos Registrados</h1> <form action="control?operacion=mostrarpordep" method="post"> <select name="codigo" > <c:forEach var="dep" items="${departamentos}" > <option value="<c:out value= '${dep.idDepartamento}'/>"> <c:out value='${dep.departamento}'/> </option> </c:forEach> </select>

<br/> <br/> <input type="submit" value="Ver empleados"/> </form> </center> </body> </html>

La pgina listadoEmpleados.jsp se mantiene sin cambios respecto a la versin anterior.

También podría gustarte