8

Click here to load reader

13137252-Jpa-Java-Developer-Leonardo-Torres

Embed Size (px)

Citation preview

Page 1: 13137252-Jpa-Java-Developer-Leonardo-Torres

En este articulo , modela-re un simple libro de di-recciones (Address book ) para una compañía de música ficticia llamada Watermelon , para guar-dar las direcciones de los clientes en la base de da-tos . Watermelon vende y distribuye artículos de música así como instru-mentos , amplificadores y libros . Voy a usar una incremental e iterativa aproximación para des-arrollar y persistir el mo-delo del negocio.

Mapeo Objeto Relacional (ORM) ,en otras palabras persistir los objetos Java en una base de datos rela-cional - ha tenido su ma-yor cambio recientemen-te, 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 , Hiberna-te , JDO , y también JDBC con DAO . Con muchas alternativas in-

compatibles , el grupo Java EE experto toma inspiración de estos fra-meworks populares y creo el api de persistencia de Java (JPA) , el cual se puede usar desde aplica-ciones Java EE o SE. En pocas palabras JPA , usa el modelo de progra-mación POJO para la persistencia. A pesar de que este modelo esta in-cluido en la especificación EJB 3 , JPA puede ser usado fuera de un conte-nedor EJB , y esta es la forma que será usada en este articulo . “Plain Old Java Objects” ( POJO ) ejecutándose en una apli-cación Java SE.

Que es ORM ?

CURSO JAVA DEVELOPER

Java Persistence Api

LINKS DE INTERES :

♦ 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 frame-works como Hibernate , JPA usa anotaciones para mapear objetos a la base de datos relacional. Estos objetos , usualmente lla-mados entidades, no tie-nen nada en común con los Entity Beans 2.x . Las entidades JPA son clases POJOs, no extienden de ninguna clase y no imple-mentan ninguna interface. Usted no necesita archi-vos 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ódi-go :

@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 }

JPAJPA

Page 2: 13137252-Jpa-Java-Developer-Leonardo-Torres

<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="watermelonPU" transaction-type="RESOURCE_LOCAL"> <provider> oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider </provider> <class>entity.Customer</class> <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"/> </properties> </persistence-unit> </persistence>

especifica unidad de persistencia ( water-melonPU en este ca-so ) . Una unidad de persistencia es decla-rada en el archivo “persistence.xml” y contiene información como la base de datos a usar y el driver JDBC .

Lo mínimo necesario ...

Persistence.xml

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 transac-tion.commit(). Vea los méto-dos CRUD Que base de datos usamos ? , La respuesta esta en EntityMa-nagerFactory, Esta toma un parámetro que se refiere a una

EntityManager ... Después de crear el EntityManager usan-do un factory ( Entity-ManagerFactory ) , 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-

Esta parte de código que vimos ( construc-tores , getter y setter no son mostrados para hacer esto mas fácil de leer ) muestra una clase “Customer”

simple . Esta tiene un identifi-cador (id) , un nombre ( first-name) , apellido ( lastname) un numero de teléfono ( telep-hone ) , mail ( email ) y edad del cliente ( age ) . Para ser persistente esta clase tiene que seguir algunos reglas JPA sim-ples : • 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 anota-do 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 ope-raciones serán echas a través de la interface javax.persistence.EntityManager de JPA. Para esto, quienes estén fami-liarizados 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 ).

Page 2 JAVA PERSISTENCE API

@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 = Persisten-ce.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(); }

Page 3: 13137252-Jpa-Java-Developer-Leonardo-Torres

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 persis-tencia 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 , cre-denciales. Bajo el elemento "properties" usted encontrara propiedades especificas para Top-link por ejemplo to-plink.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”. Este es el DDL que JPA gene-ra 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-

fecto hace mas fácil la progra-mación para el desarrollador .

• 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.

En este punto yo quisiera me-jorar algunas cosas. Primero que todo, yo no quiero setear un identificador (primary key)del objeto pero en lugar de esto quiero que JPA lo incre-mente automáticamente. Gra-cias a las anotaciones , esto es fácil de hacer. Solo necesito anotar mi atribu-to identificador @javax.persitence.GeneratedValue . Esta anotación genera un pri-mary Key en cuatro posibles modos:

Ahora quiero mejorar mi ma-peo . Primero cambiar el nom-bre de la tabla a "t_customer" en lugar de "customer". Luego hare el nombre ( first name ) y el apellido ( last name ) obliga-torios. 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" .

Unidad de Persistencia

Añadiendo funcionalidades y customizando el mapeo

dades . Los tipos de da-tos son tam-bién mapea-dos por de-fecto ( ejem-plo String es mapeado a varchar(255) ).

Customizando Usted solo necesita añadir código customizado cuando el código por defecto es inade-cuado . 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-

Page 3 JAVA PERSISTENCE API

Page 4: 13137252-Jpa-Java-Developer-Leonardo-Torres

actualizada o removida . Una aplicación puede ser notificada antes o después de ocurridos estos eventos usando anotacio-nes . JPA tiene un conjunto de "callback annotatios" que pue-den ser usadas en métodos y permiten al desarrollador añadir

cualquier regla de negocios que desee . JPA llamara al método anotado antes o después de estos eventos . La tabla 4 mues-tra las "callback anotations"

Como uso las “callback annota-tios” para mis necesidades ? , Primero me encargare del for-mato del numero de teléfono .

Ahora con el mapeo entre la clase Customer y la tabla t_customer quedo mejor gra-cias a que las anotaciones @Column y @Table tienen muchos atributos.

Hay otras dos cosas que quisie-ra hacer . Primero asegurarme que todo teléfono es ingresado usando códi-gos interna-cionales . comenzando con el símbolo '+' . Segun-do , 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 ,

Quiero verificar que el primer carácter del teléfono es "+" , Yo puedo hacer esto antes que la entidad sea persistida o actua-lizada . solo tengo que crear un método ( validatePhoneNum-ber en mi ejemplo , pero el nombre es irrelevante ) con algo de lógica de negocios y con

las anotaciones @PrePersist y @PreUpdate, JPA hace el re-sto.

El código de ejemplo en la si-guiente pagina.

Anotaciones Callback

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 lista-dos en la Tabla 3

Una aplicación

puede ser

notificada antes

o después de

ocurridos estos

eventos JPA

usando

"callback

annotations"

Page 4 JAVA PERSISTENCE API

Page 5: 13137252-Jpa-Java-Developer-Leonardo-Torres

Para la edad del cliente , hare algo similar, calculare la edad del cliente antes que la fecha de na-cimiento sea insertada ( @PostPersist ) o actualizada ( @PostUpdate ) , y claro cada vez que el cliente es cargado de la base de datos ( @PostLoad ).

estoy en la capacidad de calcu-lar la edad del cliente pero yo necesito persistir esa informa-ció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 colum-na edad mas ) ver tabla 5.

Para hacer que esto funcione . necesito añadir un nuevo atri-buto 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

Anotaciones Callback .. añadir anotaciones

@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; }

@PrePersist @PreUpdate private void validatePhoneNumber() { if (telephone.charAt(0) != '+') throw new IllegalArgumentException("Invalid phone number"); } }

Page 5

Page 6: 13137252-Jpa-Java-Developer-Leonardo-Torres

también remuevo el address.

Pero persistiendo y removien-do 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 Custo-mer ) y permitir las dependen-cias que se persistan o remue-van automáticamente ? , To-pLink y la codificación por defecto hará las asociaciones

Como este código muestra , usted tiene primero que instan-ciar un objeto Customer y un Adress , Para linkear los dos ,uso un método setter ( set HomeAddress ) y luego persis-tido 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 , cuan-do yo remuevo el customer

opcionales y basadas en mis requerimientos, es decir al per-sistir o remover customer tam-bién se hace con su address. Quiero que un Customer tenga exactamente un Address , sig-nifica que un valor null no es permitido . Puedo lograr todo eso usando la anotación @OneToOne junto con @JoinColumn.

One to One Relationship

One to One Relationship

One to One Relationship

Como tu pueden ver en la tabla 6 , la clase Address usa la ano-tación @Entity para notificar a JPA que es una clase persisten-te 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 :

Ahora que tengo mapeada mi clase Customer y tengo anota-ciones 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 cum-pleaños . Lo representare como

una clase separada, clase Ad-dress con un id , una calle (street) , una ciudad (city) , un código postal ( zip code ), y un país (country).

Page 6 JAVA PERSISTENCE API

al persistir o

remover

“customer”

también se hace

con su “address”

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(); }

Page 7: 13137252-Jpa-Java-Developer-Leonardo-Torres

@OneToOne es usada para anotar una relación . Este tiene muchas propiedades incluyen-do 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 custo-mer 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 co-mo @Column , excepto que es usado para asociar atributos , En este ejemplo , Yo renom-bro la llave foránea en un ad-dress_fk y no permito ningún valor null ( nullable=false)

JPA entonces creara los si-guientes DDLs con una cons-traint de integridad ,para la relación entre tablas t_customer y t_adress ( ver tabla 7 )

One to One Relationship

Page 7 JAVA PERSISTENCE API

El atributo

fetch dice a JPA

que política usar

cuando

cargamos una

relación

@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 }

Page 8: 13137252-Jpa-Java-Developer-Leonardo-Torres

es la primera propiedad del objeto “customer” . Si ustedes quieren buscar todos los “customers” que viven en U.S. , ustedes pueden usar esta no-tación , para obtener al atributo country del objeto address. SELECT c FROM Customer c WHERE c.homeAddress.country='US';

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ác-ter “c” ( el nombre es irrele-vante ) es el alias de un objeto “customer” y el “c.firstname”

Acá hay un conjunto de querys que nosotros podemos hacer con JPQL . Ver tabla 8

Querying Objects

JPQL Queries, ejemplos ...

formas el string "John" es bus-cado : puede ser parte del que-ry JPQL o puede ser pasado como parámetro , en este ulti-mo caso necesito usar el méto-do setParameter.

Querying Objects, sobre objetos ... Para hacer querys sobre los objetos se necesita EntityMa-nager para crear un objeto Query . Luego tengo el resulta-do del query llamando el ge-tResultList 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 for-mas . De cualquiera de las dos

Hasta ahora yo estoy usando JPA para mapear mis objetos a una base de datos relacional y usar el entity manager para hacer algunas operaciones CRUD (Create, read, update and delete ) , Pero JPA también permite que hagas querys sobre los objetos. Esto usa "Java Persis-tence Query Langua-ge" ( JPQL) , el cual es similar a SQL y es también independien-te de la base de da-tos , Este es un len-guaje rico que nos permite hacer querys

complejos sobre objetos ( aso-ciaciones , herencia , clases abstractas )

Los querys usan las palabras SELECT , FROM y WHE-RE , mas un conjunto de ope-

radores para filtrar la data ( IN , NOT IN , EXIST , LIKE , IS NULL , IS NOT NULL ) o para controlar las colecciones ( IS EMPTY , IS NOT EMPTY , MEMBER OF ) , También hay funciones para manejar Strings ( LOWER , UPPER , TRIM , CONCAT , LENGTH , SUBSTRING ) ,

números ( ABS , SQRT , MOD ) , o colecciones ( COUNT , MIN , MAX , SUM ). Co-mo SQL , tu también puedes ordenar los resultados ( ORDER BY) o agruparlos (GROUP BY)

Page 8 JAVA PERSISTENCE API

// Finds the customers who are called John Query query = em.createQuery("SELECT c FROM Customer c WHERE c.firstname='John'"); List<Customer> customers = que-ry.getResultList(); // Same query but using a parameter //Query query = em.createQuery("SELECT c FROM //Customer c WHERE c.firstname=:param"); //query.setParameter(":param", "John");

Usted tiene que

hacer querys no

sobre una tabla,

mas bien sobre

un objeto.