Está en la página 1de 31

Introduccion a JPA

Con esta entrada doy comienzo a una serie de cuatro capitulos que introducen JPA en su
version 2.0. En ellos vamos a ver, de forma introductoria, como realizar ORM de forma
sencilla mediante JPA. Los capitulos seguiran este orden:

1. ORM Basico
2. Asociaciones y Herencia
3. Persistencia, Transacciones, Callbacks y Listeners
4. JPQL

Tanto los capitulos publicados asi como un anexo donde se explica como poner en
marcha un entorno de desarrollo para probar el codigo que se ira mostrando estan
disponibles en la pagina de tutoriales.

1.1 INICIO
En Java solucionamos problemas de negocio a traves de objetos, los cuales tienen
estado y comportamiento. Sin embargo, las bases de datos relacionales almacenan la
informacion mediante tablas, filas y columnas, de manera que para almacenar un objeto
hay que realizar una correlacion entre el sistema orientado a objetos de Java y el sistema
relacional de nuestra base de datos. JPA (Java Persistence API) es una abstraccion sobre
JDBC que nos permite realizar dicha correlacion de forma sencilla, realizando por
nosotros toda la conversion entre objeto y base de datos. Esta conversion se llama ORM
(Object Relational Mapping - Mapeo Relacional de Objetos) y puede configurarse a
traves de metadatos (mediante xml o anotaciones). Por supuesto que JPA tambien nos
permite seguir el sentido inverso, creando objetos desde una base de datos de forma
totalmente transparente. A estos objetos los llamaremos desde ahora entidades (entities).

JPA establece una interface comun que es implementada por un proveedor de


persistencia de nuestra eleccion (como Hibernate, Eclipse Link, etc) de manera que
podemos elegir en cualquier momento el proveedor que mas se adecue a nuestras
necesidades. Asi, es el proveedor quien realiza el trabajo, pero siempre funcionando
bajo la API de JPA.

1.2 UNA ENTIDAD SIMPLE


Este es el ejemplo mas simple de entidad:

package es.davidmarco.tutorial.jpa;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Pelicula {
@Id

Pág. 1
@GeneratedValue
private Long id;
private String titulo;
private int duracion;

// Getters y Setters
}

Las entidades suelen ser POJOs. La clase Pelicula se ha anotado con @Entity, lo cual
informa al proveedor de persistencia que cada instancia de esta clase es una entidad.
Para ser valida, toda entidad debe:

- Tener un constructor por defecto


- Ser una clase de primer nivel (no interna)
- No ser final
- Implementar la interface Serializabe si es accedida remotamente

La configuracion de mapeo puede ser especificada mediante configuracion xml o


anotaciones. A esta configuracion la llamamos metadatos. Para mantener las cosas
simples, este tutorial utilizara la segunda opcion (anotaciones). Cual de ellas elegir
depende, en gran medida, del tipo de proyecto que estemos realizando. Cuando ambas
opciones son utilizadas al mismo tiempo, la configuracion xml prevalece sobre las
anotaciones, de manera que si un registro xml establece unos atributos para una
propiedad que tambien ha sido configurada mediante anotaciones, los valores xml seran
los que el proveedor de persistencia tenga en cuenta.

1.3 IDENTIDAD
Ademas, todas las entidades tienen que poseer una identidad que las diferencie del resto,
por lo que deben contener una propiedad (preferiblemente que admita un valor null)
marcada con la anotacion de identidad @Id. Existen formas mas complejas de identidad
(@EmbeddedId, @IdClass) que no vamos a explicar para mantener los ejemplos
sencillos. Esta identidad va a ser gestionada por el proveedor de persistencia, asi que
sera el quien le asigne un valor la primera vez que almacene la entidad en la base de
datos. Para ello, le añadimos la anotacion @GeneratedValue.

1.4 CONFIGURACION POR DEFECTO


JPA aplica a las entidades que maneja una configuracion por defecto, de manera que
una entidad es funcional con una minima cantidad de informacion (las anotaciones
@Entity y @Id en nuestro caso). Con esta configuracion por defecto, todas las entidades
del tipo Pelicula seran mapeadas a una tabla de la base de datos llamada PELICULA,
y cada propiedad sera mapeada a una fila del mismo nombre (id a la fila ID, titulo a
la fila TITULO, etc). Sin embargo, no siempre es posible ceñirse a los valores de la
configuracion por defecto: imaginemos que tenemos que trabajar con una base de datos
ya existente con nombres de tablas y filas ya definidos. En ese caso, podemos
configurar el mapeo de manera que se ajuste a dichas tablas y filas:

Pág. 2
@Entity
@Table(name = "TABLA_PELICULAS")
public class Pelicula {
@Id
@GeneratedValue
@Column(name = "ID_PELICULA")
private Long id;
...
}

La anotacion @Table nos permite configurar el nombre de la tabla donde queremos


almacenar la entidad mediante el atributo name. Asi mismo, mediante la anotacion
@Column podemos configurar el nombre de la columna donde se almacenara una
propiedad, si dicha propiedad puede contener un valor null, etc. Puedes descargar la
especificacion completa de JPA 2.0 desde aqui.

1.5 LECTURA TEMPRANA Y LECTURA DEMORADA


Cuando leemos una entidad desde la base de datos, ciertas propiedades pueden no ser
necesarias en el momento de la creacion del objeto. JPA nos da la opcion de leer una
propiedad la primera vez que es accedida (lectura demorada) en lugar de leerla cuando
la entidad que la contiene es creada (lectura temprana), de manera que si la propiedad
nunca es accedida nos evitamos el coste de crearla. Esto puede ser muy util si la
propiedad contiene un objeto de gran tamaño:

@Basic(fetch = FetchType.LAZY)
private Imagen portada;

La propiedad portada es una imagen que contiene la portada de una pelicula. Puesto
que el costo de crear este objeto al leer la entidad Pelicula es muy alto, lo hemos
marcado para lectura demorada con la anotacion @Basic(fetch = FetchType.LAZY).
El comportamiento por defecto de la mayoria de tipos Java es el contrario, lectura
temprana. Este comportamiento, a pesar de ser implicito, puede ser declarado
explicitamente de la siguiente manera:

@Basic(fetch = FetchType.EAGER)
private Imagen portada;

Solo los objetos de gran tamaño y ciertos tipos de asociacion (que veremos en la
segunda parte de esta entrega) deben ser leidos de forma demorada. Si, por ejemplo,
marcamos todas las propiedades de tipo int, String o Date de una entidad como de
lectura demorada, cada vez que accedamos por primera vez a cada una de ellas el
proveedor de persistencia tendra que hacer una llamada a la base de datos para leer
dicha propiedad. Esto evidentemente va a provocar que se efectuen multitud de
llamadas a la base de datos cuando con solo una (al crear la entidad en memoria)
podrian haberse leido todas con apenas coste. Por tanto, es importante recordar que

Pág. 3
donde y como configuremos la lectura de una propiedad puede afectar enormemente al
rendimiento de nuestra aplicacion.

Ten en cuenta que la anotacion @Basic solo puede ser aplicada a tipos primitivos, sus
correspondientes clases wrapper, BigDecimal, BigInteger, Date, arrays, algunos tipos
java.sql, enums y cualquier tipo que implemente la interface Serializable. Ademas del
atributo fetch, la anotacion @Basic admite otro atributo, optional, el cual permite
definir si la propiedad a la que aplica puede contener el valor null.

1.6 TIPOS ENUMERADOS


JPA puede manejar todos los tipos basicos de datos de Java, incluidos los tipos
enumerados o enum:
@Enumerated
private Genero genero;

La configuracion por defecto de JPA mapeara cada valor ordinal de un tipo enumerado
a una columna de tipo numerico en la base de datos. Por ejemplo, siguiendo el ejemplo
anterior podemos crear un tipo enum que describa el tipo de genero de una pelicula:

public enum Genero {


TERROR,
DRAMA,
COMEDIA,
ACCION,
}

Si la propiedad genero del primer ejemplo tiene el valor Genero.COMEDIA, en la


columna correspondiente de la base de datos de guardara el valor 2 (que es el valor
ordinal de Genero.COMEDIA). Sin embargo, si en el futuro introducimos un nuevo tipo
de genero en una posicion intermedia de Genero, o reordenamos las posiciones de los
generos, nuestra base de datos contendra valores erroneos que no se corresponderan con
los nuevos valores ordinales. Para evitar este potencial problema podemos forzar a la
base de datos a utilizar una columna de texto en vez de una numerica, de manera que el
valor almacenado sera el nombre del tipo enum y no su valor ordinal:

@Enumerated(EnumType.STRING)
private Genero genero;

1.7 TRANSIENT
Ciertas propiedades de una entidad pueden no representar su estado. Por ejemplo,
imaginemos que tenemos una entidad que representa a una persona:

@Entity
public class Persona {

Pág. 4
@Id
@GeneratedValue
private Long id;
private String nombre;
private String apellidos
private Date fechaNacimiento;
private int edad;

// getters y setters
}

Podemos considerar que la propiedad edad no representa el estado de Persona, ya que


(manteniendo las cosas simples) por si sola no cambiara su valor cada año y por tanto
puede contener un valor erroneo. Ya que su valor puede ser calculado gracias a la
propiedad fechaNacimiento, no vamos a almacenar la propiedad edad en la base de
datos, si no a calcularlo cada vez que lo necesitemos. Para indicar al proveedor de
persistencia que ignore una propiedad cuando realice el mapeo, usamos la anotacion
@Transient:

@Transient
private int edad;

Ahora, para obtener el valor de edad utilizamos su metodo getter:

public int getEdad() {


// calcular la edad y devolverla
}

1.8 COLECCIONES BASICAS


Una entidad puede tener propiedades que sean coleciones java.util.Collection y/o
java.util.Map que contengan tipos basicos (todos los tipos wrapper, String,
BigDecimal, BigInteger, tipos enumerados y tipos insertables). Los elementos de
estas colecciones seran almacenados en una tabla diferente. El tipo de coleccion tiene
que ser concreto (como ArrayList o HashMap) y no una interface.

private ArrayList comentarios;

El codigo de arriba es mapeado por defecto con unos valores predeterminados por JPA.
Por supuesto, podemos cambiar la configuracion por defecto con diversas anotaciones,
entre las que se encuentran:

@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "TABLA_COMENTARIOS")
private ArrayList comentarios;

Pág. 5
@ElementCollection permite definir el tipo de lectura (temprana o demorada) y la
clase de objetos que contiene la coleccion (obligatorio en caso de que la coleccion no
sea de tipo generico). @CollectionTable permite definir el nombre de la tabla donde
queremos almacenar los elementos de la coleccion. Si nuestra colecion es de tipo Map,
podemos añadir la anotacion @MapKeyColumn(name = "NOMBRE_COLUMNA") que nos
permite definir el nombre de la columna que contendra las claves.

1.9 TIPOS INSERTABLES


Los tipos insertables (embeddables) son objetos que no tienen identidad, por lo que para
ser persistidos deben ser primero insertados dentro de una entidad u otro insertable.
Cada propiedad del tipo insertable sera mapeada a la tabla de la entidad que lo contenga,
como si fuera una propiedad declarada en la propia entidad. Definimos un tipo
insertable con la anotacion @Embeddable:

@Embeddable
public class Direccion {
private String calle;
private int codigoPostal;
...
}

Y lo insertamos en una entidad (u otro tipo insertable) con la anotacion @Embedded

@Entity
public class Persona {
...
@Embedded
private Direccion direccion;
}

Ahora, la tabla que contenga las entidades de tipo Persona contendra sus propias
columnas mas las definidas en Direccion.

1.10 TIPO DE ACCESO


Para terminar este primer capitulo de introduccion a JPA, vamos a ver que son los tipos
de acceso y como aplicarlos correctamente. JPA permite dos tipos de acceso: acceso a
variables (FIELD) y acceso a propiedad (PROPERTY). El tipo de acceso esta
predeterminado por donde situemos las anotaciones de mapeo. Si las anotaciones estan
en las variables que conforman la clase (como hemos hecho hasta ahora), estaremos
indicando a JPA que debe hacer acceso a variables. Si por el contrario definimos las
anotaciones en los metodos getter correspondientes, estaremos indicando un acceso a
propiedad. A efectos practicos, no existe diferencia alguna entre ambas opciones, mas
alla de gustos personales y de organizacion de codigo. Sin embargo, en determinadas

Pág. 6
ocasiones debemos ser consecuentes con el metodo que elijamos, de manera que no
mezclemos tipos de acceso por defecto para evitar que JPA pueda actuar de forma
erronea. Por ejemplo, una clase insertable con acceso por defecto (no definido
explicitamente) heredara el tipo de acceso de la entidad que la contiene, de manera que
si la clase insertable esta utilizando un tipo de acceso diferente debemos indicarlo
explicitamente para evitar que JPA utilice el tipo de acceso erroneo. Vamos a ver un
ejemplo simple donde se muestra este problema:

@Embeddable
public class Insertable {
private int variable;

@Column(name = "VALOR_DOBLE")
public int getVariable() {
return variable * 2;
}

public void setVariable(int variable) {


this.variable = variable;
}
}

@Entity
public class Entidad
@Id
@GeneratedValue
private Long id;
@Embedded
private Insertable insertable;
}

La clase Insertable define un tipo de acceso a propiedad, ya que las anotaciones se


encuentran en los metodos getter, y queremos que se acceda a traves de estos metodos al
valor de las variables para poder procesar previamente su valor (multiplicar por dos).
Sin embargo, la clase Entidad define un tipo de acceso a variable, ya que las
anotaciones se encuentran en las variables. Puesto que el tipo insertable va a heredar el
tipo de acceso de la entidad donde se encuentra definido, cuando accedamos al valor de
Entidad.insertable.variable obtendremos un valor no esperado (el valor de
variable en vez del valor multiplicado por dos que devuelve getVariable()). Para
evitar estos problemas debemos indicar explicitamente el tipo de acceso de
Insertable:

@Embeddable
@Access(AccessType.PROPERTY)
public class Insertable { ... }

De esta manera, cuando accedamos a Entidad.insertable.variable lo haremos a


traves de su metodo getter ya que hemos indicado explicitamente a JPA que debe
acceder a traves de la propiedad anotando el tipo Insertable con
@Access(AccessType.PROPERTY) (tambien podemos indicar el tipo de acceso
directamente en la variable o el getter, segun corresponda). Un efecto de definir

Pág. 7
explicitamente el tipo de acceso es que las anotaciones que no correspondan con ese
tipo de acceso seran ignoradas, a no ser que sean a su vez definidas explicitamente con
@Access:

@Embeddable
@Access(AccessType.FIELD)
public class Insertable {
private int variable;

@Column(name = "VALOR_DOBLE")
public int getVariable() {
return variable * 2;
}

public void setVariable(int variable) {


this.variable = variable;
}
}

Se ha definido un acceso a variables explicito (@Access(AccessType.FIELD)), por lo


que la anotacion @Column sera ignorada y no accederemos a la variabla a traves del
metodo getter a pesar de estar anotado. Toda la discusion sobre tipos de acceso esta
relacionada con la capacidad JPA de acceder a todas las variables y todos los metodos
de una clase, ya sean del tipo public, protected, default o private.

Pág. 8
2.1 ASOCIACIONES
En el capitulo anterior vimos como realizar ORM basico, y una de las opciones que JPA
nos brinda es la de mapear colecciones de objetos simples (ver seccion 1.8
COLECCIONES BASICAS). Sin embargo, cuando queremos mapear relaciones o
colecciones entre entidades tenemos que utilizar asociaciones. Estas asociaciones
pueden ser unidireccionales y bidireccionales. Asociaciones unidireccionales pueden
entenderse cuando un objeto tiene una referencia a otro objeto. Asociaciones
bidireccionales pueden entenderse cuando ambos objetos mantienen referencias al
objeto contrario. Ademas del concepto de direccion, existe otro llamado cardinalidad,
que determina cuantos objetos pueden haber en cada extremo de la asociacion.

2.2 ASOCIACIONES UNIDIRECCIONALES


Para entender bien el concepto de unidireccionalidad veamos un ejemplo de una
asociacion unidireccional:

@Entity
public class Cliente {
@Id
@GeneratedValue
private Long id;
@OneToOne
private Direccion direccion;

// Getters y setters
}

@Entity
public class Direccion {
@Id
GeneratedValue
private Long id;
private String calle;
private String ciudad;
private String pais;
private Integer codigoPostal;

// Getters y setters
}

Como puedes ver en el ejemplo de arriba, cada entidad Cliente manteniene una
referencia a una entidad Direccion. Esta relacion es de tipo uno-a-uno (one-to-one)
unidireccional (puesto que UNA entidad Cliente tiene UNA referencia a Direccion y
por tanto es anotada con @OneToOne. Cliente es el dueño de la relacion de manera
implicita, y por tanto cada entidad de este tipo contendra por defecto una columna
adicional en la base de datos que sera utilizada para acceder al objeto Direccion. Esta
columna es una clave foranea (foreign key), un concepto muy importante en bases de
datos relaciones que abordaremos a lo largo de todo este capitulo. Cuando JPA realice
el mapeo de esta relacion, cada entidad sera almacenada en su propia tabla, añadiendo a
la tabla que contiene la entidad que es dueña de la relacion la clave foranea para acceder
a la direccion del cliente. Recuerda que JPA utiliza configuracion por defecto para

Pág. 9
realizar el mapeo, pero podemos personalizar como se realizara dependiendo de
nuestras necesidades. Para configurar la columna que contendra la clave foranea
podemos usar la anotacion @JoinColumn:

@OneToOne
@JoinColumn(name = "direccion_fk")
private Direccion direccion;

Otro tipo de asociacion muy tipica es uno-a-muchos (one-to-many) unidireccional.


Veamos un ejemplo:

@Entity
public class Cliente {
@Id
@GeneratedValue
private Long id;
@OneToMany
private List direcciones;

// Getters y setters
}

En este caso, cada entidad de tipo Cliente mantiene una lista de direcciones, mediante
una propiedad List anotada con @OneToMany. En este caso, en vez de utilizar una clave
foranea en Cliente, JPA utilizara por defecto una tabla de union (join table). Una tabla
de union permite que ambos lados de la asociacion tengan una clave foranea que es
almacenada en una tercera tabla con dos columnas. Como siempre, podemos configurar
el mapeo a nuestras necesidades mediante metadatos (anotaciones o xml). Veamos
como:

@OneToMany
@JoinTable(name = ...,
joinColumn = @JoinColumn(name = ...),
inverseJoinColumn = @JoinColumn(name = ...))
private List direcciones;

2.3 ASOCIACIONES BIDIRECCIONALES


En las asociaciones bidireccionales, ambos extremos de la relacion mantienen una
referencia al otro. En este caso, el dueño de la relacion debe ser especificado
explicitamente para que JPA pueda realizar el mapeo de manera adecuada. Veamos un
ejemplo de bidireccionalidad en una relacion uno-a-uno:

@Entity
public class Mujer {
@Id
@GeneratedValue
private Long id;

Pág. 10
@OneToOne
private Marido marido;

// Getters y setters
}

@Entity
public class Marido {
@Id
@GeneratedValue
private Long id;
@OneToOne(mappedBy = "marido")
private Mujer mujer;
}

En el caso de arriba, y como suele ocurrir en el mundo real, Mujer es la dueña de la


relacion. Puesto que la relacion es bidireccional, ambos lados de la relacion deben estar
anotados con @OneToOne, pero ahora uno de ellos debe indicar que la parte contraria es
dueña de la relacion añadiendo el atributo mappedBy. El valor de este atributo es el
nombre de la propiedad asociada en la entidad que es dueña de la relacion. El atributo
mappedBy puede ser usado en relaciones de tipo @OneToOne, @OneToMany y
@ManyToMany. Solamente el cuarto tipo de relacion, @ManyToOne no acepta este atributo.

2.4 LECTURA TEMPRANA Y LECTURA DEMORADA DE ASOCIACIONES


En el punto 1.5 vimos que significaba lectura temprana y demorada. Las asociaciones
son parte del mapeo relacional y por tanto tambien son afectadas por este concepto. El
tipo de lectura por defecto para las relaciones uno-a-uno y muchos-a-uno es temprana
(eager). El tipo de lectura para las dos clases de relaciones restantes, uno-a-muchos y
muchos-a-muchos es demorada (lazy). Por supuesto, estos valores pueden ser
cambiados:

@OneToMany(fetch = FetchType.EAGER)
private List pedidos;

Al igual que indique en el punto 1.5, debes ser consciente del impacto en el rendimiento
de la aplicacion que puede tener una configuracion erronea en el tipo de lectura, ya que
en las asociaciones se pueden ver involucrados muchos objetos, y cargarlos de manera
temprana puede ser ademas de innecesario, inadecuado.

2.5 ORDENACION DE ASOCIACIONES


Puedes ordenar los resultados devueltos por una asociacion con la anotacion @OrderBy:

@OneToMany
@OrderBy("nombrePropiedad desc/asc")
private List pedidos;

Pág. 11
El atributo de tipo String que hemos proporcionado a @OrderBy se compone de dos
partes: el nombre de la propiedad sobre la que queremos que se realice la ordenacion,
y ,opcionalmente, si queremos que sea en sentido ascendente (asc) o descendente (desc).
Asi mismo, podemos mantener la coleccion a la que hace referencia una asociacion
ordenada en su propia tabla de la base de datos mediante la anotacion @OrderColumn.
Sin embargo, el impacto en el rendimiento que puede producir en la base de datos es
algo que debes tener muy en cuenta, ya que las tablas afectadas por esta ordenacion
automatica tendran que reordenarse cada vez que se hayan cambios en ellas.

2.6 HERENCIA
En las aplicaciones Java el concepto de herencia es usado intensivamente. Podemos
gestionar como son mapeados los objetos cuando existe herencia entre ellos mediante
JPA de tres formas distintas:

- Una tabla por familia (por defecto)


- Union de subclases
- Una tabla por clase concreta

El mapeo por defecto es una tabla por herencia. Todas las clases que forman una familia
son almacenadas en una sola tabla. En esta tabla existe una columna por cada atributo
de cada clase y subclase que forman la herencia, ademas de una columna adicional
donde se almacena el tipo de clase al que hace referencia cada fila. Imaginemos el
ejemplo siguiente:

@Entity
public class SuperClase {
@Id
@GeneratedValue
private Long id;
private int propiedadUno;
private String propiedadDos;

// Getters y Setters
}

@Entity
public class SubClase extends SuperClase {
@Id
@GeneratedValue
private Long id;
private float propiedadTres;
private float propiedadCuatro;

// Getters y setters
}

En el ejemplo anterior, tanto las instancias de SuperClase como SubClase seran


almancenadas en una sola tabla que tendra el nombre por defecto de la clase raiz

Pág. 12
(SuperClase) y seis columnas que se corresponderan con:

- La propiedad id (que ambas clases comparten, pues ambas deben tener identidad)
- Las propiedades propiedadUno, propiedadDos, propiedadTres y
propiedadCuatro
- Una columna discriminatoria donde se almacenara el tipo de clase al que hace
referencia cada fila

La columna discriminatoria suele contener el nombre de la clase dentro de la herencia.


Esta columna es necesaria para que al recuperar un objeto desde la base de datos JPA
sepa que clase debe instanciar y que propiedades leer. Las propiedades que son propias
de cada clase no deben ser configuradas como not-null, en cuyo caso al persistir un
objeto de otra clase (y que no dispone de esa propiedad) obtendriamos un error. Aunque
una tabla por familia es la forma utilizada por defecto, podemos declararlo
explicitamente mediante la anotacion @Inheritance. Los valores definidos en
@Inheritance son heredados por todas las subclases, aunque estas pueden
sobreescribirlos si desean adoptar una politica de mapeo diferente:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class SuperClass { ... }

El nombre de la columna discriminatoria y su tipo son por defecto DTYPE y String.


Podemos cambiar estos valores mediante las anotaciones @DiscriminatorColumn y
@DiscriminatorValue:

@Entity
@Inheritance
@DiscriminatorColumn(name = "...", discriminatorType = CHAR)
@DiscriminatorValue("C")
public class SuperClase { ... }

La anotacion @DiscriminatorColumn solo debe ir en la clase raiz de la herencia, a no


ser que una subclase desee cambiar los parametros del mapeo.
El atributo discriminatorType nos permite cambiar el tipo de valor que almacenara la
clase discriminatoria. Los tipos soportados son STRING (por defecto), CHAR e
INTEGER. Si cambiamos en la clase raiz el tipo que almacenara la columna
discriminatoria, tenemos que indicar en esta clase y cada subclase suya un valor
adecuado al nuevo tipo:

<@Entity
@DiscriminatorValue("S")
public class SubClase extends SuperClase { ... }

De esta manera la columna discriminatoria, que es de tipo CHAR contendra una C si la


instancia correspondiente es de tipo SuperClase y una B si es de tipo SubClase.

Pág. 13
El segundo tipo de mapeo cuando existe herencia es union de subclases, en el que cada
clase y subclase, sea abstracta o concreta, sera almacenada en su propia tabla:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class SuperClase { ... }

La tabla raiz contiene la clave primaria usada por todas las tablas, asi como la columna
discriminatoria. Cada subclase almacenara en su propia tabla solamente los atributos
propios (no los heredados), asi como una clave primaria que hara referencia a clave
primaria de la tabla raiz. La mayor ventaja de este sistema es que es intuitivo. Por
contra, su mayor inconveniente es que para que construir un objeto de una subclase hay
que hacer una o varias operaciones JOIN en la base de datos, para poder unir los
atributos de la superclase y los de la subclase. Si la subclase esta varios niveles por
debajo de la superclase en la herencia, habra que realizar multiples operaciones JOIN
(una por nivel) produciendo un impacto en el rendimiento que tal vez no deseemos.

El tercer y ultimo tipo de mapeo cuando existe herencia es una tabla por clase concreta.
Cada entidad sera mapeada a su propia tabla, incluyendo todos los atributos propios y
heredados). Con este sistema no hay tablas compartidas, columnas compartidas ni
columna discriminatoria.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class SuperClase { ... }

El unico requerimiento de una tabla por clase concreta es que todas las tablas dentro de
una familia compartan el valor de la clave primaria, de manera que cuando
almacenamos una subclase tanto la tabla raiz como la tabla donde se almacena la
subclase contendran los valores comunes y compartiran la misma ID. Este sistema
tambien puede tener en determinadas circunstancias problemas de rendimiento, ya que
al igual que con la union de subclases la base de datos tendra que realizar operaciones
JOIN ante determinadas solicitudes.

2.7 MAPPED SUPERCLASSES, CLASES ABSTRACTAS Y NO-ENTIDADES


Mapped Superclasses son clases que no son manejadas por el proveedor de persistencia
pero que comparten sus propiedades con cualquier entidad que extienda de ellas:

@MappedSuperclass
@Inheritance(strategy = InheritanceType...)
public class MapedSuperClase {
private String propiedadUno;
private int propiedadDos;

// Getters y setters
}

Pág. 14
@Entity
public class SuperClase extends MappedSuperClase { ... }

Solo SuperClase sera manejada por el proveedor de persistencia. Sin embargo, en el


mapeo se incluiran todas las propiedades heredadas de MappedSuperClase.

Por ultimo y para terminar con ORM, indicar que las clases abstractas son tratadas
exactamente igual que las clases concretas, y que cualquier clase que no sea indicada
como entidad con la anotacion @Entity o su equivalente XML sera ignorada a la hora
de realizar el mapeo relacional.

En el proximo capitulo veremos como persistir, leer, actualizar y borrar de una base de
datos todas las entidades que hemos creado hasta ahora, asi como que son las
transacciones, metodos callback y clases listener.

Pág. 15
3.1 PERSISTENCIA
El concepto de persistencia implica almacenar nuestras entidades (objetos java) en una
sistema de almacenamiento, normalmente una base de datos relacional (tablas, filas y
columnas). Mas alla de este simple almacenamiento, todo sistema de persistencia debe
permitir recuperar, actualizar y eliminar esas entidades. Estas cuatro operaciones se
conocen como operaciones CRUD (Create, Read, Update, Delete - Crear, Leer,
Actualizar, Borrar). JPA maneja todas las operaciones CRUD a traves de la interface
EntityManager. Comencemos definiendo una entidad que manejaremos a traves de
todos los ejemplos:

package es.davidmarco.tutorial.jpa;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Pelicula {
@Id
@GeneratedValue
private Long id;
private String titulo;
private int duracion;

// Getters y Setters
}

Cuando JPA vaya a persistir esta entidad, necesitara un archivo de configuracion


llamado 'persistence.xml' que estara ubicado en el directorio META-INF de nuestra
aplicacion. En nuestro caso, este archivo mostraria este aspecto:

<?xml version="1.0" encoding="UTF-8"?>


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="introduccionJPA" transaction-
type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</pro
vider>
<class>es.davidmarco.tutorial.jpa.Pelicula</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"
/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver" />
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/introduccionJPA_DB;
create=true" />
<property name="eclipselink.jdbc.user" value="APP" />
<property name="eclipselink.jdbc.password" value="APP" />
<property name="eclipselink.ddl-generation" value="create-
tables" />
</properties>
</persistence-unit>
</persistence>

Pág. 16
El archivo persistence.xml contiene informacion necesaria para JPA como el nombre
de la unidad de persistencia, el tipo de transacciones que vamos a utilizar (concepto que
veremos a continuacion), las clases que deseamos que sean manejadas por el proveedor
de persistencia, y los parametros para conectar con nuestra base de datos.

3.2 TRANSACCIONES
En el archivo de configuracion anterior hemos incluido la siguiente linea:

<persistence-unit name="introduccionJPA" transaction-


type="RESOURCE_LOCAL">

En el se indica el nombre de la unidad de transaccion, el cual deberemos proporcionar a


EntityManager para informarle de los parametros necesarios para realizar el mapeo
para un conjunto de entidades a gestionar (en nuestro caso solo Pelicula), y el tipo de
transaccion que utilizaremos al manejar este conjunto de entidades, RESOURCE_LOCAL.
El concepto de transaccion nos permite realizar varias operaciones como si fueran una
sola, de manera que o bien todas o ninguna de ellas son completadas. Esto es muy
importante para garantizar la integridad de los datos que queremos persistir.
Imaginemos que estamos persistiendo una entidad que contiene una lista de entidades
en su interior (una asociacion uno-a-muchos). Si cualquiera de las entidades de esta lista
no pudiera ser persistida por algun tipo de error, no deseamos que el resto de entidades
de la lista, asi como la entidad que las contiene, sean persistidas tampoco. Si
permitieramos que esto ocurriera, nuestros datos no reflejarian su estado real y
completo, y ni dichos datos ni nuestro programa seria fiable. JPA nos permite
configurar en cada unidad de persistencia el tipo de transaccion, que puede ser:

- Manejada por la aplicacion (RESOURCE_LOCAL)


- Manejada por el contenedor (JTA)

Nosotros hemos decidido manejar las transacciones desde la aplicacion, y lo haremos en


nuestro codigo a traves de la interface EntityTransaction. Aunque en una aplicacion
real utilizariamos transacciones manejadas por el contenedor, para mantener los
ejemplos simples (no necesitamos instalar y configurar un contenedor) y comprender
mejor como funcionan las manejaremos desde nuestro codigo.

3.3 ESTADO DE UNA ENTIDAD


Para JPA, una entidad puede estar en dos estados diferentes: managed (gestionada) y
detached (separada). Cuando persistimos una entidad (como veremos en el punto 3.4),
automaticamente se convierte en una entidad gestionada. Todos los cambios que
efectuemos en ella dentro del contexto de una transaccion se veran reflejados tambien
en la base de datos.

El segundo estado en el que puede estar una entidad es separado, en el cual los cambios

Pág. 17
realizados en la entidad no estan sincronizados con la base de datos. Una entidad esta en
estado separado antes de ser persistida por primera vez y cuando es separada del
contexto de persistencia, accion que explicaremos en el punto 3.6.

3.4 PERSISTIR UNA ENTIDAD


Ya estamos listos para persistir nuestra primera entidad.:

package es.davidmarco.tutorial.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class Main {

public static void main(String[] args) {


EntityManagerFactory emf =
Persistence.createEntityManagerFactory("introduccionJPA");

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

Pelicula pelicula = new Pelicula();


pelicula.setTitulo("Pelicula uno");
pelicula.setDuracion(142);

tx.begin();
try {
em.persist(pelicula);
tx.commit();
} catch(Exception e) {
tx.rollback()
}

em.close();
emf.close();
}
}

En el codigo anterior hemos obtenido una instancia de EntityManager a traves de la la


clase EntityManagerFactory, a la que le hemos tenido que pasar como argumento en
el momento de la creacion el nombre de la unidad de persistencia que vamos a utilizar
(y que hemos configurado en el archivo 'persistence.xml'). A continuacion hemos
obtenido una transaccion desde EntityManager y hemos creado una instancia de la
entidad Pelicula. En las lineas de codigo siguientes, hemos iniciado la transaccion,
persistido la entidad y confirmado la transaccion con el metodo
EntityTansaction.commit(). En caso de que el proveedor de persistencia lance una
excepcion, la capturamos en el bloque catch donde cancelamos la transaccion mediante
el metodo EntityTransaction.rollback() (en los proximos ejemplos y para
simplificar el codigo, vamos a eliminar el bloque try/catch, incluyendo el codigo que
cancela la transaccion en caso de error). Cuando llamamos a em.persist(pelicula) la

Pág. 18
entidad pelicula queda automaticamente gestionada, por lo que cambios posteriores en
su estado seran sincronizados, siempre dentro del contexto de una transaccion:

tx.begin();
em.persist(pelicula);
pelicula.setTitulo("otro titulo");
tx.commit();

Es importante entender que, hasta que señalamos la transaccion para completarse, los
cambios en una entidad pueden no haber sido aun persistidos en la base de datos.
Podemos forzar a que esto ocurra llamando al metodo EntityManager.flush().
Aunque los cambios seran persistidos antes de finalizar la transaccion, si esta terminase
fallando los cambios serian desechos:

tx.begin();
em.persist(pelicula);
pelicula.setTitulo("otro titulo");
em.flush();
// otras operaciones
tx.commit();

Asi mismo, podemos actualizar una entidad con los datos que actualmente se
encuentran en la base de datos tambien sin esperar a que la transaccion finalice. Esto es
util cuando hemos persistido una entidad, hemos finalizado la transaccion, hemos
cambiado el estado de la entidad y deseamos volver al estado persistido, no al del objeto
java. El metodo para realizar esta actualizacion es
EntityManager.refresh(entidad):

tx.begin();
em.persist(pelicula);
tx.commit();
pelicula.setTitulo("otro titulo");
em.refresh(pelicula);

En el codigo anterior, la llamada a em.refresh() deshace los cambios realizados en


pelicula en la linea anterior. Otro metodo muy util que nos indica si una entidad se
encuentra gestionada es EntityManager.contains(entidad):

boolean gestionada = em.contains(pelicula);


// logica de la aplicacion

3.5 LEER UNA ENTIDAD


Podemos leer una entidad previamente persistida de dos maneras diferentes. De la
primera manera, los datos son leidos desde la base de datos y almacenados en una
instancia de la entidad:

Pág. 19
Pelicula p = em.find(Pelicula.class, id);

La segunda manera de leer una entidad, nos permite obtener una referencia a los datos
almacenados en la base de datos, de manera que el estado de la entidad sera leido de
forma demorada en el primer acceso a cada propiedad, no en el momento de la creacion
de la entidad:

Pelicula p = em.getReference(Pelicula.class, id);

En ambos casos, el valor de id debe ser el valor de id con el que fue persistida la
entidad. Esta forma de lectura es muy limitada, de manera que luego veremos un
sistema mucho mas dinamico para buscar entidades persistidas: JPQL.

3.6 ACTUALIZAR UNA ENTIDAD


Antes de explicar como actualizar una entidad, vamos a explicar como separar una
entidad del contexto de persistencia, explicacion que dejamos pendiente al final del
punto 3.3. Podemos separar una o todas las entidades gestionadas por el contexto de
persistencia mediante los metodos EntityManager.detach(entidad) y
EntityManager.clear() respectivamente. Una vez que una entidad se encuentra
separada, esta deja de estar gestionada y los cambios en su estado dejan de ser
sincronizados con la base de datos. Si intentas llamar de nuevo al metodo
persist(entidad) se lanzara una excepcion, pues dicha entidad ya se encuentra
persistida en la base de datos. De manera que si queremos queremos tener de nuevo
dicha entidad en estado gestionado y sincronizada con la base de datos tenemos que
actualizarla:

tx.begin();
em.persist(pelicula);
tx.commit();
em.detach(pelicula);
// otras operaciones
em.merge(pelicula);

En el codigo anterior, la entidad pelicula es separada del contexto de persistencia


mediante la llamada a em.detach(pelicula). La ultima linea indica al proveedor de
persistencia que vuelva a gestionar la entidad, y sincronice los cambios que se hayan
podido realizar mientras la entidad estuvo separada.

3.7 ELIMINAR UNA ENTIDAD


La ultima operacion CRUD es eliminar una entidad. Cuando realizamos esta operacion,
la entidad es eliminada de la base de datos, asi como es separada del contexto de

Pág. 20
persistencia. Sin embargo, la entidad seguira existiendo como objeto Java en nuestro
codigo hasta que el ambito de la variable termine o hasta que el colector de basura la
considere innecesaria:

em.remove(pelicula);
pelicula.setTitulo("ya no soy una entidad, solo un objeto normal
Java");

Cuando existe una asociacion uno-a-uno y uno-a-muchos entre dos entidades, y


eliminas la entidad propietaria, la entidades del otro lado de la relacion no son
eliminadas por defecto de la base de datos, pudiendo dejar asi entidades persistidas sin
ninguna entidad que haga referencia a ellas. Esto se conoce como entidad huerfana.
Podemos configurar nuestras asociaciones para que eliminen de manera automatica
todas las entidades subordinadas de una relacion:

@Entity
public class Pelicula {
...
@OneToOne(orphanRemoval = true)
private Descuento descuento;

// Getters y setters
}

En el codigo anterior informamos al proveedor de persistencia que cuando elimine una


entidad de tipo Pelicula debe eliminar tambien la entidad Descuento asociada.

3.8 OPERACIONES EN CASCADA


La operacion de eliminacion de huerfanos que acabamos de ver es un tipo de operacion
en cascada. Este tipo de operaciones permiten establecer como deben propagarse ciertos
eventos (como persistir, eliminar, actualizar, etc) entre entidades que forman parte de
una asociacion. La forma general de establecer como se realizaran estas operaciones en
cascada se define mediante el atributo cascade, que aplica a los cuatro tipos de
asociaciones JPA:

@OneToOne(cascade = CascadeType.REMOVE)
private Descuento descuento;

El codigo anterior es similar al del segundo ejemplo del punto 3.7. En el, el tipo de
operacion en cascada que deseamos propagar (CascadeType.REMOVE) se indica
mediante constantes de la clase CascadeType. Estas constantes pueden tener los
siguientes valores:

CascadeType.PERSIST

Pág. 21
.REMOVE
.MERGE
.REFRESH
.DETACH
.ALL

La ultima constante, CascadeType.ALL, hace referencia a todas las posibles


operaciones que pueden ser propagadas y por tanto engloba el comportamiento del resto
de constantes. Tambien podemos configurar varias operaciones en cascada de la lista
superior sin tener que seleccionarlas todas con CascadeType.ALL:

@OneToOne(cascade = {
CascadeType.MERGE,
CascadeType.REMOVE,
...
})
private Descuento descuento;

Recuerda que cualquier entidad que utilices en tu codigo debe estar debidamente
registrada en el archivo 'persistence.xml', de manera que JPA pueda gestionarla:

<class>es.davidmarco.tutorial.jpa.Descuento</class>

3.9 METODOS CALLBACK


Como hemos visto a lo largo de esta tercera parte del tutorial de introduccion a JPA, una
entidad puede estar en dos estados diferentes: gestionada y separada. Los metodos
callback son metodos que se ejecutan cuando ciertos eventos ocurren en el ciclo de vida
de una entidad. Estos eventos se clasifican en cuatro categorias:

- Eventos de persistencia (metodo callback asociado anotado con @PrePersists y


@PostPersist)
- Eventos de actualizacion (metodo callback asociado anotado con @PreUpdate y
@PostUpdate)
- Eventos de borrado (metodo callback asociado anotado con @PreRemove y
@PostRemove)
- Eventos de carga (metodo callback asociado anotado con @PostLoad)

Las anotaciones superiores pueden marcar un metodo de negocio, el cual sera ejecutado
cuando el evento correspondiente suceda:

@Entity
public class Pelicula {
...
@PrePersist
@PreUpdate
private void validar() {
// validar parametros antes de persistir y actualizar la

Pág. 22
entidad
}
}

Los metodos callback deben seguir algunas reglas para que el codigo funcione:

- No deben ser metodos estaticos ni finales


- Cada anotacion de ciclo de vida puede aparecer una y solo una vez en cada entidad
(no puede anotar dos o mas metodos)
- No pueden lanzar excepciones de tipo checked
- No pueden invocar metodos de las clases EntityManager y Query (esta ultima la
veremos en el cuarto capitulo)

Cuando existe herencia entre entidades, los metodos @Pre/PostXxx de la superclase son
invocados antes que los de la subclase. Asi mismo y como vimos antes, los eventos de
ciclo de vida se propagan en cascada cuando existen asociaciones.

3.10 CLASES LISTENER


Cuando necesitas aplicar un mismo metodo callback a varias entidades, es preferible
extraer este metodo de la entidad en una clase externa, que podra ser aplicada a varias
entidades, reduciendo asi la duplicacion de codigo. Estas clases son clases listener:

public class ValidadorListener {


@PrePersist
@PreUpdate
public void validar(Pelicula pelicula) {
// validar parametros antes de persistir y actualizar la
entidad
}
}

Una vez definida la clase listener, debemos indicar a cada entidad que clases listeners
puede utilizar:

@Entity
@EntityListeners(ValidadorListener.class)
public class Pelicula { ... }

En tiempo de ejecucion, JPA pasara una referencia de la entidad Pelicula al ejecutar el


metodo validar(Pelicula). Si deseamos que una clase listener pueda ser aplicada a
mas de un tipo de entidad podemos utilizar una superclase o interface comun a dichas
entidades, o una referencia a Object como parametro del metodo callback:

public class ValidadorListener {


@PrePersist
@PreUpdate

Pág. 23
public void validar(Object obj) {
// validar parametros antes de persistir y actualizar la
entidad
}
}

La anotacion @EntityListeners puede hacer referencia a varias clases listener, las


cuales deben ser incluidas entre llaves y separadas por comas. Cuando una entidad es
asociada con varias clases listener, los metodos callback de dichas clases que hagan
referencia a un mismo evento seran invocados en el orden en que fueron declaradas los
listeners. Asi mismo, los metodos listener seran invocados previamente a los metodos
callback que hagan referencia al mismo tipo de evento y que existan como metodos
propios (no externos) de la entidad:

@Entity
@EntityListeners({
ValidadorListener.class,
OtroListener.class})
public class Pelicula {
...
@PrePersist
@PreUpdate
private void validar() {
// validar parametros antes de persistir y actualizar la
entidad
}
}

En el ejemplo anterior, los metodos @PrePersist y @PreUpdate se ejecutaran en este


orden: ValidatorListener, OtroListener, Pelicula.

Pág. 24
4.1 JPQL BASICO
Como vimos en el capitulo anterior, con EntityManager estamos limitados a realizar
consultas en la base de datos proporcionando la identidad de la entidad que deseamos
obtener, y solo podemos obtener una entidad por cada consulta que realicemos. JPQL
nos permite realizar consultas en base a multitud de criterios, como por ejemplo
propiedades de la entidad almacenada o condiciones booleanas, y obtener mas de un
objeto por consulta. Veamos el ejemplo mas simple posible:

SELECT p FROM Pelicula p

La linea anterior obtiene todas las instancias de la clase Pelicula desde la base de
datos. La expresion puede parecer un poco extraña la primera vez que se ve, pero es
muy sencilla. Las palabras SELECT y FROM tienen un significado similar a las sentencias
homonimas de SQL, indicando que se quiere seleccionar (SELECT) cierta informacion
desde (FROM) cierto lugar. La segunda p es un alias para la clase Pelicula, y ese alias
es usado por la primera p (llamada 'expresion') para acceder a la clase a la que refiere el
alias o a sus propiedades. El siguiente ejemplo nos ayudara a comprender esto mejor:

SELECT p.titulo FROM Pelicula p

Facil, ¿verdad?. El alias p nos permite utilizar la expresion p.titulo para obtener los
titulos de todas las peliculas almacenadas en la base de datos. La expresiones JPQL
utilizan la notacion de puntos, convirtiendo tediosas consultas en algo realmente simple:

SELECT c.propiedad.subPropiedad.subSubPropiedad FROM Clase c

JPQL tambien nos permite obtener resultados referentes a mas de una propiedad:

SELECT p.titulo, p.duracion FROM Pelicula p

Todas las sentencias anteriores (que mas tarde veremos como ejecutar) devuelven o un
unico valor o un conjunto de ellos. Podemos eliminar los resultados duplicados
mediante la clasula DISTINCT:

SELECT DISCTINCT p.titulo FROM Pelicula p

Asi mismo, el resultado de una consulta puede ser el resultado de una funcion agregada
aplicada a la expresion:

SELECT COUNT(p) FROM Pelicula p

Pág. 25
En el ejemplo anterior, COUNT() es una funcion agregada de JPQL que cuenta el numero
de ocurrencias tras realizar la consulta. El valor devuelto por la funcion agregada es lo
obtenemos. Otras funciones agregadas son AVG para la media aritmetica, MAX para el
valor maximo, MIN para el valor minimo y SUM para la suma de todos los valores.

4.2 SENTENCIAS CONDICIONALES


Ahora que ya sabemos como realizar consultas basicas, vamos a introducir conceptos
mas complejos (pero aun simples). El primero de ellos es el de consulta condicional,
aplicado con la clausula WHERE, la cual restringe los resultados devueltos por una
consulta en base a ciertos criterios logicos (desde ahora la mayoria de los ejemplos
constaran de varias lineas pero ten en cuenta que son una unica sentencia JPQL, no
varias):

SELECT p FROM Pelicula p


WHERE p.duracion < 120

La sentencia anterior obtiene todas las instancias de Pelicula almacenadas en la base


de datos con una duracion inferior a 120 minutos. Las sentencias condicionales pueden
contener varias condiciones:

SELECT p FROM Pelicula p


WHERE p.duracion < 120 AND p.genero = 'Terror'

La sentencia anterior obtiene todas las instancias de Pelicula con una duracion inferior
a 120 minutos y cuya propiedad genero sea igual a Terror. La otra sentencia
condicional ademas de AND es OR (aplicado en el caso del ejemplo anterior obtendria
todas peliculas con una duracion inferior a 120 minutos o las del genero de Terror, solo
una de las dos condiciones seria suficiente). Veamos mas ejemplos:

SELECT p FROM Pelicula p


WHERE p.duracion BETWEEN 90 AND 150

La sentencia anterior obtiene todas las instancias de Pelicula con una duracion entre
(BETWEEN) 90 y (AND) 150 minutos. BETWEEN puede ser convertido en NOT BETWEEN en
cuyo caso se obtendrias todas las peliculas que una duracion que no se encuentre dentro
del margen 90-150 minutos. Otro operador de comparacion muy util es [NOT] LIKE
(NOT es opcional, como en el ejemplo anterior), el cual nos permite comparar una
cadena de texto con comodines con las propiedades de una entidad almacenada en la
base de datos. Veamos un ejemplo para comprenderlo:

SELECT p FROM Pelicula p

Pág. 26
WHERE p.titulo LIKE 'Ocean's%'

La sentencia anterior obtiene todas las instancias de Pelicula cuyo titulo sea como
(LIKE) 'Oceans...', osea la palabra 'Oceans' seguida de mas caracteres (entre cero y
varios). El comodin que representa 'cero o mas caracteres' es el simbolo de porcentaje
(%). El otro comodin aceptado por LIKE es el caracter de barra baja (_) el cual representa
un solo caracter. JPQL dispone de muchos operadores de comparacion, que por ser este
un tutorial introductorio no vamos a mostar; te invito a que busques informacion en
google y en la propia documentacion de JPA y 'juegues' con ellos.

4.3 PARAMETROS DINAMICOS


Podemos añadir parametros dinamicamente a nuestras sentencias JPQL de dos maneras:
por posicion y por nombre. La sentencia a continuacion indica parametros por posicion:

SELECT p FROM Pelicula p


WHERE p.titulo = ?1

Y la siguiente, parametros por nombre:

SELECT p FROM Pelicula p


WHERE p.titulo = :titulo

En el momento de realizar la consulta, podemos pasar los parametros a la sentencia


JPQL. Esto lo veremos en el punto 4.8.

4.4 ORDENAR LOS RESULTADOS


Cuando realizamos una consulta a la base de datos, podemos ordenar los resultados
mediante la clausula ORDER BY (ordenar por), la cual admite ordenamiento ascendente
mediante la clausula ASC (comportamiento por defecto) u en orden descendiente
mediante la clausula DESC:

SELECT p FROM Pelicula p


ORDER BY p.duracion DESC

La sentencia anterior podria tener una clausula WHERE entre SELECT y ORDER BY para
restringir los resultados devueltos. Ademas, puedes incluir multiples expresiones de
ordenacion:

SELECT p FROM Pelicula p


ORDER BY p.duracion DESC, p.titulo ASC

Pág. 27
4.5 OPERACIONES DE ACTUALIZACION
JPQL puede realizar operaciones de actualizacion en la base de datos mediante la
sentencia UPDATE:

UPDATE Articulo a
SET a.descuento = 15
WHERE a.precio > 50

La sentencia anterior actualiza (UPDATE todas las instancias de Articulo con un precio
mayor de 50 aplicandoles (SET un descuento de 15.

4.6 OPERACIONES DE BORRADO


De forma muy similar al punto 4.5, JPQL puede realizar operaciones de borrado en la
base de datos mediante la sentencia DELETE:

DELETE FROM Pelicula p


WHERE p.duracion > 190

La sentencia anterior elimina (DELETE) todas las instancias de Pelicula cuya duracion
sea mayor de 190 minutos.

4.7 EJECUCION DE SENTENCIAS JPQL


JPQL es integrado a traves de implementaciones de la interface Query. Dichas
implementaciones se obtienen a traves de nuestro querido amigo EntityManager
mediante diversos metodos de factoria. Los tres mas tipicamente usados (y los que
explicaremos aqui) son:

createQuery(String jpql)
createNamedQuery(String name)
createNativeQuery(String sql)

Veamos como crear un objeto Query y realizar una consulta a la base de datos:

package es.davidmarco.tutorial.jpa;

import java.util.List;

import javax.persistence.EntityManager;

Pág. 28
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class Main {

public static void main(String[] args) {


EntityManagerFactory emf = Persistence
.createEntityManagerFactory("introduccionJPA");
EntityManager em = ...
...

String jpql = "SELECT p FROM Pelicula p";


Query query = em.createQuery(jpql);
List<Pelicula> resultados = query.getResultList();
for(Pelicula p : resultados) {
...
}

em.close();
emf.close();
}
}

En el ejemplo anterior obtenemos una implementacion de Query mediante el metodo


createQuery(String) de EntityManager, al cual le pasamos una sentencia JPQL en
forma de cadena de texto. Con el objeto Query ya inicializado, podemos realizar la
consulta a la base de datos llamando a su metodo getResultList() el cual devuelve un
objeto List con todas las entidades alcanzadas por la sentencia JPQL. Esta sentencia es
una sentencia dinamica, ya que es generada cada vez que se ejecuta. Utilizando una
coleccion parametrizada (List<Pelicula>) evitamos tener que hacer ningun tipo de
casting dentro del bloque for-each.

4.8 EJECUCION DE SENTENCIAS CON PARAMETROS DINAMICOS


En el punto 4.3 vimos como escribir sentencias JPQL con parametros dinamicos. Ahora
que sabemos como funciona Query veamos como insertar dichos parametros:

String jpql = "SELECT p FROM Pelicula p WHERE p.duracion > ?1 AND


p.genero = ?2"
Query query = em.createQuery(jpql);
query.setParameter(1, 180);
query.setParameter(2, "Accion");
List<Pelicula> resultados = query.getResultList();

En el ejemplo anterior insertamos dinamicamente (mediante el metodo


setParameter(int, Object) los valores deseados para p.duracion y p.genero, que
en la sentencia JPQL se corresponden con los parametros por posicion ?1 y ?2,
respectivamente. El primer parametro de setParameter indica que parametro por
posicion debe ajustar (en el ejemplo anterior query.setParameter(2, "Accion")

Pág. 29
ajusta el parametro por posicion ?2. Si el valor que pasamos no se corresponde con el
valor esperado, la aplicacion lanzara una excepcion de tipo
IllegalArgumentException. Esto tambien ocurrira si ajustamos una posicion
inexistente.

Asi mismo, podemos utilizar parametros por nombre en nuestras sentencias JPQL:

String jpql = "SELECT p FROM Pelicula p WHERE p.duracion > :duracion


AND p.genero = :genero"
Query query = em.createQuery(jpql);
query.setParameter("duracion", 180);
query.setParameter("genero", "Accion");
List<Pelicula> resultados = query.getResultList();

Ahora en vez de utilizar ?1 y ?2 hemos utilizado :duracion y :genero. Al ajustar estos


valores mediante el metodo setParameter(String, Object) hemos incluido los
nombres de los parametros (sin los dos puntos iniciales) y sus valores correspondientes,
haciendo la aplicacion mas sencilla de leer y mantener.

4.9 CONSULTAS CON NOMBRE (ESTATICAS)


Las consultas con nombre son diferentes de las sentencias dinamicas que hemos visto
hasta ahora en el sentido en que no pueden cambiar: son leidas y transformadas en
sentencias SQL cuando el programa arranca en lugar de cada vez que son ejecutadas.
Este comportamiento estatico las hace mas eficientes y por tanto ofrecen un mejor
rendimiento. Las consultas con nombre son definidas mediante metadatos (recuerda que
los metadatos se definen mediante anotaciones o configuracion XML) definidos en las
propias entidades. Veamos un ejemplo:

@Entity
@NamedQuery(name="buscarTodos", query="SELECT p FROM Pelicula p")
public class Pelicula { ... }

El codigo anterior define una consulta con nombre a traves de la anotacion


@NamedQuery. Esta anotacion necesita dos atributos: name que define el nombre de la
consulta, y query que define la sentencia JPQL a ejecutar. El nombre de la consulta
debe ser unico dentro de su unidad de persistencia, de manera que no puede existir otra
entidad definiendo una consulta con nombre que se llame buscarTodos. Para evitar que
podamos modificar por error la sentencia, es una buena idea utilizar una constante
definida dentro de la propia entidad como nombre de la consulta:

@Entity
@NamedQuery(name=Pelicula.BUSCAR_TODOS, query="SELECT p FROM Pelicula
p")
public class Pelicula {
public static final String BUSCAR_TODOS = "Pelicula.buscarTodos";
...

Pág. 30
}

Tanto de la primera manera, como de esta ultima, una vez definida una consulta con
nombre podemos ejecutarla desde nuestra aplicacion mediante el segundo metodo de la
lista del punto 4.7: createNamedQuery():

Query query = em.createNamedQuery(Pelicula.BUSCAR_TODOS);


List<Pelicula> resultados = query.getResultList();

createNamedQuery() requiere un parametro de tipo String que contenga el nombre de


la consulta (el cual hemos definido en @NamedQuery(name=...). Una vez obtenida la
coleccion de resultados, podemos trabajar con ellos de la manera habitual.

4.10 CONSULTAS NATIVAS SQL


El tercer y ultimo tipo de consultas que vamos a ver requiere una sentencia SQL en
lugar de una JPQL:

String sql = "SELECT * FROM PELICULA";


Query query = em.createNativeQuery(sql);
...

Las consultas nativas SQL pueden ser definidas de manera estatica como las consultas
con nombre, obteniendo los mismos beneficios de eficiencia y rendimiento. Para ello,
necesitamos utilizar de nuevo metadatos:

@Entity
@NamedNativeQuery(name=Pelicula.BUSCAR_TODOS, query="SELECT * FROM
PELICULA")
public class Pelicula {
public static final String BUSCAR_TODOS = "Pelicula.buscarTodos";
...
}

Tanto las consultas con nombre como las consultas nativas SQL estaticas son muy utiles
para definir consultas inmutables (por ejemplo buscar todas las instancias de una
entidad en la base de datos, como hemos hecho en los ejemplos anteriores), aquellas que
se mantienen entre distintas ejecuciones del programa y que son usadas frecuentemente,
etc..

Pág. 31

También podría gustarte