Explora Libros electrónicos
Categorías
Explora Audiolibros
Categorías
Explora Revistas
Categorías
Explora Documentos
Categorías
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).
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:
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.
Pág. 2
@Entity
@Table(name = "TABLA_PELICULAS")
public class Pelicula {
@Id
@GeneratedValue
@Column(name = "ID_PELICULA")
private Long id;
...
}
@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.
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:
@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
}
@Transient
private int edad;
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.
@Embeddable
public class Direccion {
private String calle;
private int codigoPostal;
...
}
@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.
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;
}
@Entity
public class Entidad
@Id
@GeneratedValue
private Long id;
@Embedded
private Insertable insertable;
}
@Embeddable
@Access(AccessType.PROPERTY)
public class Insertable { ... }
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;
}
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.
@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;
@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;
@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;
}
@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.
@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:
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
}
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
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class SuperClass { ... }
@Entity
@Inheritance
@DiscriminatorColumn(name = "...", discriminatorType = CHAR)
@DiscriminatorValue("C")
public class SuperClase { ... }
<@Entity
@DiscriminatorValue("S")
public class SubClase extends SuperClase { ... }
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.
@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 { ... }
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
}
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:
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.
package es.davidmarco.tutorial.jpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
em.persist(pelicula);
tx.commit();
} catch(Exception e) {
tx.rollback()
}
em.close();
emf.close();
}
}
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);
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:
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.
tx.begin();
em.persist(pelicula);
tx.commit();
em.detach(pelicula);
// otras operaciones
em.merge(pelicula);
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");
@Entity
public class Pelicula {
...
@OneToOne(orphanRemoval = true)
private Descuento descuento;
// Getters y setters
}
@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
@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>
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:
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.
Una vez definida la clase listener, debemos indicar a cada entidad que clases listeners
puede utilizar:
@Entity
@EntityListeners(ValidadorListener.class)
public class Pelicula { ... }
Pág. 23
public void validar(Object obj) {
// validar parametros antes de persistir y actualizar 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
}
}
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:
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:
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:
JPQL tambien nos permite obtener resultados referentes a mas de una propiedad:
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:
Asi mismo, el resultado de una consulta puede ser el resultado de una funcion agregada
aplicada a la expresion:
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.
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:
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:
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.
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:
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.
La sentencia anterior elimina (DELETE) todas las instancias de Pelicula cuya duracion
sea mayor de 190 minutos.
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;
em.close();
emf.close();
}
}
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:
@Entity
@NamedQuery(name="buscarTodos", query="SELECT p FROM Pelicula p")
public class Pelicula { ... }
@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():
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