CURSO JAVA DEVELOPER
Java Persistence Api Que es ORM ?
JPA
Mapeo Objeto Relacional (ORM) ,en otras palabras persistir los objetos Java en una base de datos relacional - ha tenido su mayor cambio recientemente, gracias, en parte a la proliferación de métodos avanzados que intentan hacer esta tarea mas fácil . Entre estas tecnologías están los Entity Beans 2.x , TopLink , Hibernate , JDO , y también JDBC con DAO . Con muchas alternativas in-
compatibles , el grupo Java EE experto toma inspiración de estos frameworks populares y creo el api de persistencia de Java (JPA) , el cual se puede usar desde aplicaciones Java EE o SE. En pocas palabras JPA , usa el modelo de programación POJO para la persistencia. A pesar de que este modelo esta incluido en la especificación EJB 3 , JPA puede ser usado fuera de un contenedor EJB , y esta es la forma que será usada en este articulo . “Plain Old Java Objects” ( POJO ) ejecutándose en una aplicación Java SE.
LINKS DE INTERES :
En este articulo , modelare un simple libro de direcciones (Address book ) para una compañía de música ficticia llamada Watermelon , para guardar las direcciones de los clientes en la base de datos . Watermelon vende y distribuye artículos de música así como instrumentos , amplificadores y libros . Voy a usar una incremental e iterativa aproximación para desarrollar y persistir el modelo del negocio.
♦ EJB3 : http://www.jcp.org/ en/jsr/detail?id=220 ♦ JPA API : http:// java.sun.com/javaee/5/ docs/api/javax/persistence/ package-summary.html ♦ DAO: http://java.sun.com/ blueprints/ corej2eepatterns/Patterns/ DataAccessObject.html
Como trabaja JPA ? Inspirado en los frameworks como Hibernate , JPA usa anotaciones para mapear objetos a la base de datos relacional. Estos objetos , usualmente llamados entidades, no tienen nada en común con los Entity Beans 2.x . Las entidades JPA son clases POJOs, no extienden de ninguna clase y no implementan ninguna interface. Usted no necesita archivos descriptores XML para hacer los mapeos . Si uno se fija en el API ( java doc ) uno observara que esta compuesto de pocas
clases e interfaces. La mayoría del contenido de el paquete javax.persitence son anotaciones. Con esto explicado , veremos el siguiente ejemplo de código :
@Entity public class Customer { @Id private Long id; private String firstname; private String lastname; private String telephone; private String email; private Integer age; // constuctors, getters, setters }
JAVA PERSISTENCE API
Page 2
Lo mínimo necesario ... simple . Esta tiene un identificador (id) , un nombre ( firstname) , apellido ( lastname) un numero de teléfono ( telephone ) , mail ( email ) y edad del cliente ( age ) . Para ser persistente esta clase tiene que seguir algunos reglas JPA simples : • @Entity public class Customer {
}
@Id private Long id; private String firstname; private String lastname; private String telephone; private String email; private Integer age; // constuctors, //getters, setters
// métodos CRUD public void createCustomer() { // Gets an entity manager EntityManagerFactory emf = Persistence.createEntityManagerFactory("watermelonPU"); EntityManager em = emf.createEntityManager(); EntityTransaction trans = em.getTransaction (); // Instantiates a customer object Customer customer = new Customer(1L, "John", "Lennon", "441909", "
[email protected]", 66); // Persists the customer trans.begin(); em.persist(customer); trans.commit(); // Finds the customer by primary key customer = em.find(Customer.class, 1L); System.out.println(customer.getEmail()); // Updates the customer email address trans.begin(); customer.setEmail("
[email protected]"); trans.commit(); // Deletes the customer trans.begin(); em.remove(customer); trans.commit();
}
// Closes the entity manager and the factory em.close(); emf.close();
Esta parte de código que vimos ( constructores , getter y setter no son mostrados para hacer esto mas fácil de leer ) muestra una clase “Customer”
• •
La clase tiene que ser identificada como una entidad usando la anotación @javax.persistence.Entity Una propiedad de la clase tiene que tener un identificador anotado con @javax.persistence.Id Tiene que haber un constructor sin argumentos
El código que sigue muestra lo mínimo requerido para definir un "persistence object" . Ahora vamos a manipular este objeto, deseo persistir mi objeto “customer” , actualizar alguna de sus propiedades y borrarlo de la base de datos. Estas operaciones serán echas a través de la interface javax.persistence.EntityManager de JPA. Para esto, quienes estén familiarizados con el patrón DAO , el EntityManager puede ser visto como una clase DAO que nos provee un set de métodos clásicos ( persist , remove ) y buscadores ( find ).
EntityManager ... Después de crear el EntityManager usando un factory ( EntityManagerFactory ) , instanciare mi objeto “Customer” ( usando el operador new como cualquier otro objeto JAVA ) y le pasare algo de data como el "id" , "last name" , "email" , etc. Usare el método EntityMana-
ger.persist() para insertar este objeto en la base de datos. Yo puedo luego buscar este objeto por su identificador usando el método EntityManger.find() y actualizar el mail usando los métodos "set" . La interface EntityManger no tiene un método update . Los updates se hacen a través de las propiedades "setters" . Luego borrare el objeto usando Enti-
tyManager.remove() , notar que este código usa transacciones explicitas . Es por eso que los métodos persist , update , y remove son llamados entre transacion.begin() y transaction.commit(). Vea los métodos CRUD Que base de datos usamos ? , La respuesta esta en EntityManagerFactory, Esta toma un parámetro que se refiere a una
Persistence.xml especifica unidad de persistencia ( watermelonPU en este caso ) . Una unidad de persistencia es declarada en el archivo “persistence.xml” y contiene información como la base de datos a usar y el driver JDBC .
<provider> oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider entity.Customer <properties> <property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/watermelonDB"/> <property name="toplink.jdbc.user" value="root"/> <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="toplink.jdbc.password" value=""/> <property name="toplink.target-database" value="MySQL4"/> <property name="toplink.ddl-generation" value="create-tables"/>
JAVA PERSISTENCE API
Page 3
Unidad de Persistencia En el código arriba , hay solo una unidad de persistencia , llamada watermelonPU ( el archivo persistence.xml puede contener muchas unidades de persistencia ) . Usted puede pensar en una unidad de persistencia como un conjunto de entidades ( el elemento class ) que comparten propiedades comunes . En este caso , estas propiedades son : url de la base de datos , driver JDBC , credenciales. Bajo el elemento "properties" usted encontrara propiedades especificas para Top-link por ejemplo toplink.ddl-generation . Esta propiedad es usada por To-
pLink para generar las tablas automáticamente si estas no existen aun . Esto significa que una vez ejecutado el código TopLink a creado una tabla para guardar la información de “customers”. la tabla 1 muestra la información DDL ( data definition languaje ) de la tabla “customer”.
fecto hace mas fácil la programación para el desarrollador .
Este es el DDL que JPA genera automáticamente de la clase anotada “Customer” . Gracias a la “codificación por defecto” que guía JPA ( y en general Java EE 5 ) No tengo que hacer mucho para generar este DDL , la codificación por de-
Customizando Usted solo necesita añadir código customizado cuando el código por defecto es inadecuado . En mi caso, porque yo nunca especifico el nombre de la tabla o columnas en la clase “Customer” JPA asume que el nombre de la tablas es igual al nombre de la clase y que el nombre de la columna tiene el mismo nombre de las propie-
dades . Los tipos de datos son también mapeados por defecto ( ejemplo String es mapeado a varchar (255) ).
Añadiendo funcionalidades y customizando el mapeo En este punto yo quisiera mejorar algunas cosas. Primero que todo, yo no quiero setear un identificador (primary key) del objeto pero en lugar de esto quiero que JPA lo incremente automáticamente. Gracias a las anotaciones , esto es fácil de hacer. Solo necesito anotar mi atributo identificador @javax.persitence.GeneratedValue . Esta anotación genera un primary Key en cuatro posibles modos:
•
AUTO (default) permite al proveedor de persistencia (TopLink en mi caso) decidir cual de las tres posibilidades usar.
•
SEQUENCE usar un SQL sequence para obtener el próximo primary key
•
TABLE requiere una tabla con dos columnas : el nombre de la secuencia y su valor ( esta es la estrategia por defecto de TopLink )
•
IDENTITY usa un "identity generator" , ej :una columna definida como auto_increment en MySQL.
Ahora quiero mejorar mi mapeo . Primero cambiar el nombre de la tabla a "t_customer" en lugar de "customer". Luego hare el nombre ( first name ) y el apellido ( last name ) obligatorios. El máximo largo de un numero de teléfono (telephone) tiene que ser 15 caracteres y la columna email tiene que ser renombrada a “e_mail” con un "underscore" .
JAVA PERSISTENCE API
Page 4
Todos estos pequeños cambios pueden ser hechos con las anotaciones. Para cambiar el nombre ( name ) de la tabla anote la clase con @javax.persistence.Table . La anotación @javax.persistence.Column es usada para definir las columnas y tienen una serie de atributos listados en la Tabla 3
Una aplicación puede ser notificada antes o después de ocurridos estos eventos JPA usando "callback annotations"
Anotaciones Callback Ahora con el mapeo entre la clase Customer y la tabla t_customer quedo mejor gracias a que las anotaciones @Column y @Table tienen muchos atributos. Hay otras dos cosas que quisiera hacer . Primero asegurarme que todo teléfono es ingresado usando códigos internacionales . comenzando con el símbolo '+' . Segundo , calcular la edad del “customer” a partir de la fecha de nacimiento . Tengo muchas alternativas de como hacer estas tareas , pero voy a usar "callback annotations" Durante su ciclo de vida , una entidad es cargada , persistida ,
actualizada o removida . Una aplicación puede ser notificada antes o después de ocurridos estos eventos usando anotaciones . JPA tiene un conjunto de "callback annotatios" que pueden ser usadas en métodos y permiten al desarrollador añadir
Quiero verificar que el primer carácter del teléfono es "+" , Yo puedo hacer esto antes que la entidad sea persistida o actualizada . solo tengo que crear un método ( validatePhoneNumber en mi ejemplo , pero el nombre es irrelevante ) con algo de lógica de negocios y con
cualquier regla de negocios que desee . JPA llamara al método anotado antes o después de estos eventos . La tabla 4 muestra las "callback anotations"
las anotaciones @PrePersist y @PreUpdate, JPA hace el resto.
Como uso las “callback annotatios” para mis necesidades ? , Primero me encargare del formato del numero de teléfono .
El código de ejemplo en la siguiente pagina.
Page 5
Para la edad del cliente , hare algo similar, calculare la edad del cliente antes que la fecha de nacimiento sea insertada ( @PostPersist ) o actualizada ( @PostUpdate ) , y claro cada vez que el cliente es cargado de la base de datos ( @PostLoad ).
@PrePersist @PreUpdate private void validatePhoneNumber() { if (telephone.charAt(0) != '+') throw new IllegalArgumentException ("Invalid phone number"); } }
@PostLoad @PostPersist @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 funcione . necesito añadir un nuevo atributo a la clase Customer : “date of birth” . Para notificar a JPA que mapee este atributo a una fecha , uso la anotación @Temporal con el atributo TemporalType.DATE ( las opciones son DATE , TIME , TIMESTAMP ) . Entonces
estoy en la capacidad de calcular la edad del cliente pero yo necesito persistir esa información ? , NO , conociendo que el valor cambia todos los años . Para hacer que esta propiedad (age) edad no sea persistente , usare la anotación @Transient ( La tabla no tendrá una columna edad mas ) ver tabla 5.
JAVA PERSISTENCE API
Page 6
One to One Relationship Ahora que tengo mapeada mi clase Customer y tengo anotaciones callback para validar y calcular data , necesito añadir una dirección. Un Customer
tiene una y solo una “address“ (dirección) entonces Watermelon pueden enviar al cliente un regalo por su cumpleaños . Lo representare como
una clase separada, clase Address con un id , una calle (street) , una ciudad (city) , un código postal ( zip code ), y un país (country).
al persistir o remover “customer” también se hace con su “address”
One to One Relationship Como tu pueden ver en la tabla 6 , la clase Address usa la anotación @Entity para notificar a JPA que es una clase persistente y @Column para customizar el mapeo. Creando la relación entre Customer y Address es simple . Yo simplemente añado una propiedad Address en la clase Customer . Para persistir la dirección de los "customer's" uso el código que sigue :
public void createCustomerWithAddress() { // Instantiates a Customer and an Address objecy Customer customer = new Customer("John", "Lennon", "+441909", "
[email protected]", dateOfBirth); Address homeAddress = new Address("Abbey Road", "London", "SW14", "UK"); customer.setHomeAddress(homeAddress); // Persists the customer with its address trans.begin(); em.persist(homeAddress); em.persist(customer); trans.commit(); // Deletes the customer and the address trans.begin(); em.remove(customer); em.remove(homeAddress); trans.commit(); }
One to One Relationship Como este código muestra , usted tiene primero que instanciar un objeto Customer y un Adress , Para linkear los dos ,uso un método setter ( set HomeAddress ) y luego persistido cada objeto , cada uno en la misma transacción. Porque no tiene sentido tener un adress en el sistema que no este linkeado a un customer , cuando yo remuevo el customer
también remuevo el address. Pero persistiendo y removiendo ambos objetos parece que se hiciera mas trabajo que el que necesito. Seria mejor si yo pudiera persistir o remover justo el objeto raíz ( el Customer ) y permitir las dependencias que se persistan o remuevan automáticamente ? , TopLink y la codificación por defecto hará las asociaciones
opcionales y basadas en mis requerimientos, es decir al persistir o remover customer también se hace con su address. Quiero que un Customer tenga exactamente un Address , significa que un valor null no es permitido . Puedo lograr todo eso usando la anotación @OneToOne junto con @JoinColumn.
JAVA PERSISTENCE API
Page 7
One to One Relationship @OneToOne es usada para anotar una relación . Este tiene muchas propiedades incluyendo un cascade usado para "cascading" de cualquier tipo de acción. En este ejemplo , quiero "cascade" la acción de persistir y remover. De esta forma , cuando hago remove o persist de un objeto customer este automáticamente lleva acabo esta acción para el "address" . El atributo fetch dice a JPA que política usar cuando cargamos una relación. Esta puede ser una asociación de carga tardía ( lazy loading LAZY) , o "eagerly" (EAGER) porque quiero cargar la direc-
ción de la casa "homeAddress" tan pronto como el objeto Customer es cargado. La anotación @JoinColumn tiene los mismos atributos como @Column , excepto que es usado para asociar atributos , En este ejemplo , Yo renombro la llave foránea en un address_fk y no permito ningún valor null ( nullable=false) JPA entonces creara los siguientes DDLs con una constraint de integridad ,para la relación entre tablas t_customer y t_adress ( ver tabla 7 )
@Entity @Table(name = "t_customer") public class Customer { @Id @GeneratedValue private Long id; (...) @Transient private Integer age; @OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) @JoinColumn(name = "address_fk", nullable = false) private Address homeAddress; // constuctors, getters, setters }
El atributo fetch dice a JPA que política usar cuando cargamos una relación
JAVA PERSISTENCE API
Page 8
Querying Objects
Usted tiene que hacer querys no sobre una tabla, mas bien sobre un objeto.
radores para filtrar la data ( IN , complejos sobre objetos ( asoNOT IN , EXIST , LIKE , IS NULL , IS Hasta ahora yo estoy usando ciaciones , herencia , clases NOT NULL ) o para controlar las JPA para mapear mis objetos a abstractas ) colecciones ( IS EMPTY , IS NOT una base de datos relacional y EMPTY , MEMBER OF ) , También usar el entity manager para Los querys usan las palabras hay funciones para manejar hacer algunas operaciones SELECT , FROM y WHEStrings ( LOWER , UPPER , TRIM , CRUD (Create, read, update RE , mas un conjunto de opeCONCAT , LENGTH , SUBSTRING ) , and delete ) , Pero números ( ABS , JPA también permite // Finds the customers who are called John SQRT , MOD ) , o que hagas querys colecciones sobre los objetos. Query query = em.createQuery("SELECT c ( COUNT , MIN , Esto usa "Java Persis- FROM Customer c WHERE MAX , SUM ). Cotence Query Langua- c.firstname='John'"); mo SQL , tu ge" ( JPQL) , el cual List
customers = quetambién puedes es similar a SQL y es ry.getResultList(); ordenar los también independienresultados te de la base de da// Same query but using a parameter ( ORDER BY) o tos , Este es un len- //Query query = em.createQuery("SELECT c FROM agruparlos guaje rico que nos //Customer c WHERE c.firstname=:param"); (GROUP BY) permite hacer querys //query.setParameter(":param", "John");
Querying Objects, sobre objetos ... Para hacer querys sobre los objetos se necesita EntityManager para crear un objeto Query . Luego tengo el resultado del query llamando el getResultList o genSingleResult cuando hay solo un objeto retornado . En el ejemplo que sigue , quiero buscar todos los “Customers” quienes tienen el primer nombre "John" , Yo puedo hacer esto de dos formas . De cualquiera de las dos
formas el string "John" es buscado : puede ser parte del query JPQL o puede ser pasado como parámetro , en este ultimo caso necesito usar el método setParameter.
JPQL Queries, ejemplos ... Como usted puede ver en este query JPQL usa la notación objeto . Usted tiene que hacer query no sobre una tabla, mas bien sobre un objeto. El carácter “c” ( el nombre es irrelevante ) es el alias de un objeto “customer” y el “c.firstname”
es la primera propiedad del objeto “customer” . Si ustedes quieren buscar todos los “customers” que viven en U.S. , ustedes pueden usar esta notación , para obtener al atributo country del objeto address. SELECT c FROM Customer c WHERE c.homeAddress.country='US';
Acá hay un conjunto de querys que nosotros podemos hacer con JPQL . Ver tabla 8