Este documento proporciona una introducción a Java Persistence API (JPA). Explica brevemente qué es ORM y cómo JPA usa anotaciones para mapear objetos Java a tablas de base de datos. Luego presenta un ejemplo simple de una clase Customer anotada como una entidad y describe los pasos básicos para crear, leer, actualizar y eliminar instancias de Customer usando JPA. Finalmente, cubre algunas customizaciones avanzadas como cambiar el mapeo de tabla y columna y agregar funcionalidad de validación mediante anotaciones de de
Una introducción a los conceptos y las características básicas de la API de persistencia para la plataforma Java, desde un punto de vista neutral (sin una preferencia por algún proveedor de persistencia en particular) comenzando desde el planteo de la necesidad de un ORM, y pasando luego por conceptos básicos como las relaciones entre entidades, ciclo de vida de las mismas, JPQL, criteria API.
Introducción a Doctrine 2 ORM.
Una introducción y uso básico de Doctrine 2 ORM en PHP sin utilizar frameworks, a través de un proyecto sencillo usado como ejemplo.
El codigo PHP del proyecto se puede descargar de su repositorio de Github: (https://github.com/gonfert/cine.git)
Presentación realizada para la X Symfony Zaragoza
Una introducción a los conceptos y las características básicas de la API de persistencia para la plataforma Java, desde un punto de vista neutral (sin una preferencia por algún proveedor de persistencia en particular) comenzando desde el planteo de la necesidad de un ORM, y pasando luego por conceptos básicos como las relaciones entre entidades, ciclo de vida de las mismas, JPQL, criteria API.
Introducción a Doctrine 2 ORM.
Una introducción y uso básico de Doctrine 2 ORM en PHP sin utilizar frameworks, a través de un proyecto sencillo usado como ejemplo.
El codigo PHP del proyecto se puede descargar de su repositorio de Github: (https://github.com/gonfert/cine.git)
Presentación realizada para la X Symfony Zaragoza
1. CURSO JAVA DEVELOPER - INSTRUCTOR : LEONARDO TORRES ALTEZ
Java Persistence Api
Que es ORM ?
JDBC con DAO . Con LINKS DE INTERES :
JPA
muchas alternativas ejecutándose en una
incompatibles , el gru- aplicación Java SE. ♦ EJB3 : http://www.jcp.org/
po Java EE experto en/jsr/detail?id=220
toma inspiración de
estos frameworks po- En este articulo , mo-
♦ JPA API : http://
pulares y creo el api de delare un simple libro
persistencia de Java de direcciones java.sun.com/javaee/5/
Mapeo Objeto Relacio- (JPA) , el cual se puede (Address book ) para docs/api/javax/
nal (ORM) ,en otras usar desde aplicaciones una compañía de músi- persistence/package-
palabras persistir los Java EE o SE. ca ficticia llamada Wa- summary.html
objetos Java en una termelon , para guardar
base de datos relacio- En pocas palabras las direcciones de los ♦ DAO: http://java.sun.com/
nal - ha tenido su ma- JPA , usa el modelo de clientes en la base de blueprints/
yor cambio reciente- programación POJO datos . Watermelon corej2eepatterns/Patterns/
mente, gracias, en par- para la persistencia. A vende y distribuye artí-
DataAccessObject.html
te a la proliferación de pesar de que este mo- culos de música así
métodos avanzados delo esta incluido en la como instrumentos ,
que intentan hacer esta especificación EJB 3 , amplificadores y li-
tarea mas fácil . JPA puede ser usado bros . Voy a usar una
fuera de un contenedor incremental e iterativa
Entre estas tecnologías EJB , y esta es la forma aproximación para des-
están los Entity Beans que será usada en este arrollar y persistir el
2.x , TopLink , Hiber- articulo . “Plain Old modelo del negocio.
nate , JDO , y también Java Objects” ( POJO )
Como trabaja JPA ?
clase y no implemen-
tan ninguna interface.
Usted no necesita ar- @Entity
chivos descriptores public class Customer {
XML para hacer los
mapeos . Si uno se fija @Id
Inspirado en los frame- en el API ( java doc ) private Long id;
works como Hiberna- uno observara que esta private String firstname;
te , JPA usa anotacio- compuesto de pocas private String lastname;
nes para mapear obje- clases e interfaces. private String telephone;
tos a la base de datos private String email;
relacional. Estos obje- La mayoría del conteni- private Integer age;
tos , usualmente llama- do de el paquete ja- // constuctors, getters,
vax.persitence son anota-
dos entidades, no tie- setters
ciones. Con esto expli-
nen nada en común }
con los Entity Beans cado , veremos el si-
2.x . Las entidades JPA guiente ejemplo de
son clases POJOs, no código :
extienden de ninguna
2.
3. JAVA PERSISTENCE API Page 2
Lo mínimo necesario ...
una clase “Customer” sim-
ple . Esta tiene un identifi-
cador (id) , un nombre El código que sigue mues-
( firstname) , apellido ( last- tra lo mínimo requerido
name) un numero de telé- para definir un quot;persistence
fono ( telephone ) , mail objectquot; . Ahora vamos a
( email ) y edad del cliente manipular este objeto,
( age ) . Para ser persistente deseo persistir mi objeto
esta clase tiene que seguir “customer” , actualizar algu-
algunos reglas JPA simples : na de sus propiedades y
borrarlo de la base de da-
• La clase tiene que ser identifi- tos. Estas operaciones serán
cada como una entidad usan- echas a través de la interfa-
@Entity do la anotación
ce
public class Customer { Esta parte de códi- @javax.persistence.Entity
go que vimos javax.persistence.EntityManager de
( constructores , • Una propiedad de la clase JPA.
@Id tiene que tener un identifica-
getter y setter no
private Long id; son mostrados para
dor anotado con
@javax.persistence.Id Para esto, quienes estén
private String firstname; hacer esto mas fácil familiarizados con el patrón
private String lastname; de leer ) muestra • Tiene que haber un construc- DAO , el EntityManager
private String telephone; tor sin argumentos
private String email;
private Integer age;
// constuctors, EntityManager ...
//getters, setters
} puede ser visto co- “Customer” ( usando el mail usando los métodos
mo una clase DAO operador new como cual- quot;setquot; .
que nos provee un quier otro objeto JAVA ) y
set de métodos clá- le pasare algo de data co- La interface EntityManger
// métodos CRUD sicos ( persist , re- mo el quot;idquot; , quot;last namequot; , no tiene un método upda-
move ) y buscado- quot;emailquot; , etc. Usare el méto- te . Los updates se hacen a
public void createCustomer() {
// Gets an entity manager
res ( find ). do EntityManager.persist() través de las propiedades
EntityManagerFactory emf = Persisten- Después de crear el para insertar este objeto en quot;settersquot; . Luego borrare el
ce.createEntityManagerFactory(quot;watermelonPUquot;);
EntityManager em = emf.createEntityManager(); EntityManager la base de datos. Yo puedo objeto usando EntityMana-
EntityTransaction trans = em.getTransaction usando un factory luego buscar este objeto ger.remove() , notar que
();
( EntityManagerFac- por su identificador usando este código usa transaccio-
// Instantiates a customer object
Customer customer = new Customer(1L, quot;Johnquot;,
tory ) , instanciare el método EntityMan- nes explicitas . Es por eso
quot;Lennonquot;, quot;441909quot;, quot;john@lenon.comquot;, 66); mi objeto ger.find() y actualizar el que los métodos persist ,
// Persists the customer
trans.begin();
em.persist(customer);
trans.commit();
// Finds the customer by primary key
Persistence.xml
customer = em.find(Customer.class, 1L);
System.out.println(customer.getEmail()); update , y remove especifica unidad de persistencia ( watermelonPU en este
// Updates the customer email address son llamados entre caso ) . Una unidad de persistencia es declarada en el ar-
trans.begin(); transacion.begin() y chivo “persistence.xml” y contiene información como la
customer.setEmail(quot;john@beatles.co.ukquot;);
trans.commit(); transaction.commit base de datos a usar y el driver JDBC .
// Deletes the customer (). Vea los métodos
trans.begin(); CRUD
em.remove(customer); <persistence version=quot;1.0quot; xmlns=quot;http://java.sun.com/xml/ns/persistencequot;>
trans.commit();
Que base de datos <persistence-unit name=quot;watermelonPUquot; transaction-type=quot;RESOURCE_LOCALquot;>
// Closes the entity manager and the factory <provider>
em.close(); usamos ? , La res- oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
emf.close();
} puesta esta en Enti- </provider>
<class>entity.Customer</class>
tyManagerFactory, <properties>
Esta toma un pará- <property name=quot;toplink.jdbc.urlquot;
metro que se refiere a una value=quot;jdbc:mysql://localhost:3306/watermelonDBquot;/>
<property name=quot;toplink.jdbc.userquot; value=quot;rootquot;/>
4.
5. Page 3 JAVA PERSISTENCE API
Unidad de Persistencia
<property name=quot;toplink.jdbc.driverquot; piedades comunes . En este de la tabla “customer”.
value=quot;com.mysql.jdbc.Driverquot;/>
<property na- caso , estas propiedades
me=quot;toplink.jdbc.passwordquot; value=quot;quot;/> son : url de la base de da- Este es el DDL que JPA
<property name=quot;toplink.target-
databasequot; value=quot;MySQL4quot;/> tos , driver JDBC , creden- gene-
<property name=quot;toplink.ddl- ciales. Bajo el elemento ra
generationquot; value=quot;create-tablesquot;/>
</properties>
quot;propertiesquot; usted encontra- auto-
</persistence-unit> ra propiedades especificas
</persistence>
para Top-link por ejemplo
toplink.ddl-generation . Esta
En el código arriba , hay propiedad es usada por
solo una unidad de persis- TopLink para generar las
tencia , llamada waterme- tablas automáticamente si
lonPU ( el archivo persis- estas no existen aun . Esto
tence.xml puede contener significa que una vez ejecu-
muchas unidades de persis- tado el código TopLink a
tencia ) . Usted puede pen- creado una tabla para guar- máticamente de la clase
sar en una unidad de per- dar la información de anotada “Customer” . Gra-
sistencia como un conjunto “customers”. la tabla 1 cias a la “codificación por
de entidades ( el elemento muestra la información DDL defecto” que guía JPA ( y
class ) que comparten pro- ( data definition languaje ) en general Java EE 5 ) No
Customizando
Usted solo necesita añadir lumna tiene String es mapeado a var-
código customizado cuando el mismo char(255) ).
el código por defecto es nombre de
inadecuado . En mi caso, las propie-
porque yo nunca especifico dades . Los
el nombre de la tabla o tipos de
columnas en la clase datos son
“Customer” JPA asume que también
el nombre de la tablas es mapeados
igual al nombre de la clase por defecto
y que el nombre de la co- ( ejemplo
Añadiendo funcionalidades y customizando el mapeo
En este punto yo quisiera primary Key en cuatro posi- quot;identity generatorquot; ,
ej :una columna definida
mejorar algunas cosas. Pri- bles modos: como auto_increment en
mero que todo, yo no quie- MySQL.
ro setear un identificador • AUTO (default) permite al
(primary key)del objeto proveedor de persistencia
pero en lugar de esto quie- (TopLink en mi caso) Ahora quiero mejorar mi
ro que JPA lo incremente decidir cual de las tres mapeo . Primero cambiar el
posibilidades usar. nombre de la tabla a
automáticamente. Gracias a
• SEQUENCE usar un SQL quot;t_customerquot; en lugar de
las anotaciones , esto es sequence para obtener el
fácil de hacer. próximo primary key
quot;customerquot;. Luego hare el
nombre ( first name ) y el
• TABLE requiere una tabla
apellido ( last name ) obli-
Solo necesito anotar mi con dos columnas : el
atributo identificador nombre de la secuencia y gatorios. El máximo largo
@javax.persitence.GeneratedValue
su valor ( esta es la estra- de un numero de teléfono
tegia por defecto de To- (telephone) tiene que ser
. pLink )
Esta anotación genera un 15 caracteres y la columna
• IDENTITY usa un
6.
7. JAVA PERSISTENCE API Page 4
email tiene que ser renombrada a “e_mail” con un quot;underscorequot; .
Todos estos pequeños cambios pueden ser hechos con las anotaciones. Para cambiar el
Una aplicación
puede ser
notificada antes
nombre ( name ) de la tabla anote la clase con @javax.persistence.Table . La anotación
o después de @javax.persistence.Column es usada para definir las columnas y tienen una serie de
ocurridos estos
eventos JPA
Anotaciones Callback
usando
quot;callback Ahora con el mapeo entre la una entidad es cargada , notatios” para mis necesida-
clase Customer y la tabla persistida , actualizada o des ? , Primero me encarga-
t_customer quedo mejor removida . Una aplicación re del formato del numero
annotationsquot;
gracias a que las anotacio- puede ser notificada antes o de teléfono . Quiero verifi-
nes @Column y @Table tie- después de ocurridos estos car que el primer carácter
del teléfono es quot;+quot; , Yo pue-
nen muchos atributos. eventos usando anotacio- do hacer esto antes que la
nes . JPA tiene un conjunto entidad sea persistida o ac-
Hay otras dos cosas que
de quot;callback annotatiosquot; que tualizada . solo tengo que
quisiera hacer . Primero ase-
pueden ser usadas en méto-
gurarme
que todo
teléfono es
ingresado
usando
códigos
internacio-
nales . co-
menzando
con el sím-
bolo '+' .
dos y permiten al desarrolla- crear un método ( validateP-
Segundo , calcular la edad honeNumber en mi ejem-
dor añadir cualquier regla
del “customer” a partir de plo , pero el nombre es irre-
de negocios que desee . JPA
la fecha de nacimiento . levante ) con algo de lógica
llamara al método anotado
Tengo muchas alternativas de negocios y con las anota-
antes o después de estos
de como hacer estas tareas , ciones @PrePersist y
eventos . La tabla 4 muestra @PreUpdate, JPA hace el
pero voy a usar quot;callback
las quot;callback anotationsquot; resto.
annotationsquot;
Durante su ciclo de vida , Como uso las “callback an- El código de ejemplo en la
8.
9. siguiente pagina.
@PrePersist
@PreUpdate
Page 5
private void validatePhoneNumber() {
if (telephone.charAt(0) != '+')
Para la edad del cliente , throw new IllegalArgumentException
hare algo similar, calculare la (quot;Invalid phone numberquot;);
edad del cliente antes que la }
fecha de nacimiento sea in- }
sertada ( @PostPersist ) o
actualizada ( @PostUpdate ) ,
y claro cada vez que el clien- @PostLoad
te es cargado de la base de @PostPersist
datos ( @PostLoad ). @PostUpdate
public void calculateAge() {
Calendar birth = new GregorianCalendar();
birth.setTime(dateOfBirth);
Calendar now = new GregorianCalendar();
now.setTime(new Date());
int adjust = 0;
if (now.get(Calendar.DAY_OF_YEAR) - birth.get(Calendar.DAY_OF_YEAR) < 0)
{
adjust = -1;
}
age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + adjust;
}
Anotaciones Callback .. añadir anotaciones
Para hacer que esto funcio- TAMP ) . Entonces estoy en tabla no tendrá una colum-
ne . necesito añadir un nue- la capacidad de calcular la na edad mas ) ver tabla 5.
vo atributo a la clase Custo- edad del cliente pero yo
mer : “date of birth” . Para necesito persistir esa infor-
notificar a JPA que mapee mación ? , NO , conociendo
este atributo a una fecha , que el valor cambia todos
uso la anotación @Temporal los años . Para hacer que
con el atributo TemporalTy- esta propiedad (age) edad
pe.DATE ( las opciones son no sea persistente , usare la
DATE , TIME , TIMES- anotación @Transient ( La
10.
11. JAVA PERSISTENCE API Page 6
One to One Relationship
Ahora que tengo mapeada Un Customer tiene una y por su cumpleaños . Lo
mi clase Customer y tengo solo una representare como una cla-
anotaciones callback para “address“ (dirección) enton- se separada, clase Address
validar y calcular data , ne- ces Watermelon pueden con un id , una calle
cesito añadir una dirección. enviar al cliente un regalo (street) , una ciudad (city) ,
un
có-
al persistir o
remover
“customer”
One to One Relationship public void createCustomerWithAddress() {
también se hace // Instantiates a Customer and an Address objecy
Customer customer = new Customer(quot;Johnquot;, quot;Lennonquot;,
Como tu pueden ver en la quot;customer'squot; uso el código
quot;+441909quot;, quot;john@lenon.comquot;, dateOfBirth);
con su “address” tabla 6 , la clase Address que sigue : homeAddress = new Address(quot;Abbey Roadquot;,
Address
quot;Londonquot;, quot;SW14quot;, quot;UKquot;);
usa la anotación @Entity customer.setHomeAddress(homeAddress);
para notificar a JPA que es
una clase persistente y // Persists the customer with its address
@Column para customizar trans.begin();
el mapeo. Creando la rela- em.persist(homeAddress);
ción entre Customer y Ad- em.persist(customer);
trans.commit();
dress es simple . Yo simple-
mente añado una propie- // Deletes the customer and the address
dad Address en la clase trans.begin();
Customer . Para persistir la em.remove(customer);
dirección de los em.remove(homeAddress);
trans.commit();
}
One to One Relationship
el customer también remue- por defecto hará las asocia-
Como este código muestra ,
vo el address. ciones opcionales y basa-
usted tiene primero que
das en mis requerimientos,
instanciar un objeto Custo- Pero persistiendo y remo-
es decir al persistir o remo-
mer y un Adress , Para lin- viendo ambos objetos pare-
ver customer también se
kear los dos ,uso un méto- ce que se hiciera mas traba-
hace con su address.
do setter ( set HomeAd- jo que el que necesito. Se-
dress ) y luego persistido ria mejor si yo pudiera per-
Quiero que un Customer
cada objeto , cada uno en sistir o remover justo el
tenga exactamente un Ad-
la misma transacción. Por- objeto raíz ( el Customer ) y
dress , significa que un va-
que no tiene sentido tener permitir las dependencias
lor null no es permitido .
un adress en el sistema que que se persistan o remue-
Puedo lograr todo eso
no este linkeado a un cus- van automáticamente ? ,
usando la anotación
tomer , cuando yo remuevo TopLink y la codificación
12.
13. Page 7 JAVA PERSISTENCE API
One to One Relationship
loading LAZY) , o constraint de integri-
@OneToOne es usada para
quot;eagerlyquot; (EAGER) porque dad ,para la relación entre
anotar una relación . Este
quiero cargar la dirección tablas t_customer y t_adress
tiene muchas propiedades
de la casa quot;homeAddressquot; ( ver tabla 7 )
incluyendo un cascade
tan pronto como el objeto
usado para quot;cascadingquot; de
Customer es cargado.
cualquier tipo de acción. En
este ejemplo , quiero La anotación @JoinColumn
quot;cascadequot; la acción de per- tiene los mismos atributos
sistir y remover. De esta como @Column , excepto
forma , cuando hago remo- que es usado para asociar
ve o persist de un objeto atributos , En este ejemplo ,
customer este automática- Yo renombro la llave forá-
mente lleva acabo esta ac- nea en un address_fk y no
ción para el quot;addressquot; . El permito ningún valor null
atributo fetch dice a JPA ( nullable=false)
que política usar cuando
cargamos una relación.
Esta puede ser una asocia- JPA entonces creara los El atributo
ción de carga tardía ( lazy siguientes DDLs con una
fetch dice a JPA
@Entity
que política usar
@Table(name = quot;t_customerquot;)
public class Customer { cuando
@Id cargamos una
@GeneratedValue
private Long id;
(...) relación
@Transient
private Integer age;
@OneToOne(fetch = FetchType.EAGER, cascade =
{CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = quot;address_fkquot;, nullable = false)
private Address homeAddress;
// constuctors, getters, setters
}
14.
15. JAVA PERSISTENCE API Page 8
Querying Objects
mite hacer querys comple- de operadores para filtrar la
Hasta ahora yo estoy usan- jos sobre objetos ( asocia- data ( IN , NOT IN , EXIST , LIKE , IS
do JPA para mapear mis ciones , herencia , clases NULL , IS NOT NULL ) o para con-
objetos a una base de datos abstractas ) trolar las colecciones ( IS
relacional y usar el entity EMPTY , IS NOT EMPTY , MEMBER
manager para hacer algunas Los querys usan las pala- OF ) , También hay funcio-
operaciones CRUD (Create, bras SELECT , FROM y nes para manejar Strings
( LOWER , UPPER , TRIM , CONCAT ,
read, update and delete ) , WHERE , mas un conjunto LENGTH , SUBS-
Pero JPA también TRING ) , nú-
permite que hagas // Finds the customers who are called John
meros ( ABS ,
querys sobre los SQRT , MOD ) , o
objetos. Esto usa Query query = em.createQuery(quot;SELECT c
colecciones
quot;Java Persistence FROM Customer c WHERE ( COUNT , MIN ,
Query Langua- c.firstname='John'quot;); MAX , SUM ). Co-
gequot; ( JPQL) , el List<Customer> customers = que- mo SQL , tu
cual es similar a ry.getResultList(); también pue-
SQL y es también des ordenar
Usted tiene que independiente de // Same query but using a parameter los resultados
la base de datos , //Query query = em.createQuery(quot;SELECT c FROM ( ORDER BY) o
hacer querys no Este es un lenguaje //Customer c WHERE c.firstname=:paramquot;); agruparlos
//query.setParameter(quot;:paramquot;, quot;Johnquot;); (GROUP BY)
rico que nos per-
sobre una tabla,
mas bien sobre
Querying Objects, sobre objetos ...
un objeto. Para hacer querys sobre los dos formas . De cualquiera
objetos se necesita Entity- de las dos formas el string
Manager para crear un ob- quot;Johnquot; es buscado : puede
jeto Query . Luego tengo el ser parte del query JPQL o
resultado del query llaman- puede ser pasado como
do el getResultList o gen- parámetro , en este ultimo
SingleResult cuando hay caso necesito usar el méto-
solo un objeto retornado . do setParameter.
En el ejemplo que sigue ,
quiero buscar todos los
“Customers” quienes tienen
el primer nombre quot;Johnquot; ,
Yo puedo hacer esto de
JPQL Queries, ejemplos ...
objeto “customer” y el to address. SELECT c FROM
Como usted puede ver en Customer c WHERE
este query JPQL usa la no- “c.firstname” es la primera
c.homeAddress.country='US';
tación objeto . Usted tiene propiedad del objeto
que hacer query no sobre “customer” . Si ustedes Acá hay un conjunto de
una tabla, mas bien sobre quieren buscar todos los querys que nosotros pode-
un objeto. El carácter “customers” que viven en mos hacer con JPQL . Ver
“c” ( el nombre es irrele- U.S. , ustedes pueden usar tabla 8
vante ) es el alias de un esta notación , para obtener
al atributo country del obje-