En este articulo , mo- delare un simple libro de direcciones (Address book ) para una compaa de msi- ca ficticia llamada Wa- termelon , para guardar las direcciones de los clientes en la base de datos . Watermelon vende y distribuye art- culos de msica as como instrumentos , amplificadores y li- bros . Voy a usar una incremental e iterativa aproximacin para des- arrollar y persistir el modelo del negocio.
Mapeo Objeto Relacio- nal (ORM) ,en otras palabras persistir los objetos Java en una base de datos relacio- nal - ha tenido su ma- yor cambio reciente- mente, gracias, en par- te a la proliferacin de mtodos avanzados que intentan hacer esta tarea mas fcil .
Entre estas tecnologas estn los Entity Beans 2.x , TopLink , Hiber- nate , JDO , y tambin JDBC con DAO . Con muchas alternativas incompatibles , el gru- po Java EE experto toma inspiracin de estos frameworks po- pulares 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 programacin POJO para la persistencia. A pesar de que este mo- delo esta incluido en la especificacin 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 ) Que es ORM ? CURSO JAVA DEVELOPER - INSTRUCTOR : LEONARDO TORRES ALTEZ 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 Hiberna- te , JPA usa anotacio- nes para mapear obje- tos a la base de datos relacional. Estos obje- tos , usualmente llama- dos entidades, no tie- nen nada en comn con los Entity Beans 2.x . Las entidades JPA son clases POJOs, no extienden de ninguna clase y no implemen- tan ninguna interface. Usted no necesita ar- chivos 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 mayora del conteni- do de el paquete ja- vax.persitence son anota- ciones. Con esto expli- cado , veremos el si- guiente ejemplo de cdigo : @Entity public class Customer {
especifica unidad de persistencia ( watermelonPU en este caso ) . Una unidad de persistencia es declarada en el ar- chivo persistence.xml y contiene informacin como la base de datos a usar y el driver JDBC .
<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"/> update , y remove son llamados entre transacion.begin() y transaction.commit (). Vea los mtodos CRUD
Que base de datos usamos ? , La res- puesta esta en Enti- tyManagerFactory, Esta toma un par- metro que se refiere a una Lo mnimo necesario ... Persistence.xml Customer ( usando el operador new como cual- quier otro objeto JAVA ) y le pasare algo de data co- mo el "id" , "last name" , "email" , etc. Usare el mto- do EntityManager.persist() para insertar este objeto en la base de datos. Yo puedo luego buscar este objeto por su identificador usando el mtodo EntityMan- ger.find() y actualizar el mail usando los mtodos "set" .
La interface EntityManger no tiene un mtodo upda- te . Los updates se hacen a travs de las propiedades "setters" . Luego borrare el objeto usando EntityMana- ger.remove() , notar que este cdigo usa transaccio- nes explicitas . Es por eso que los mtodos persist , EntityManager ... puede ser visto co- mo una clase DAO que nos provee un set de mtodos cl- sicos ( persist , re- move ) y buscado- res ( find ). Despus de crear el EntityManager usando un factory ( EntityManagerFac- tory ) , instanciare mi objeto
Esta parte de cdi- go que vimos ( constructores , getter y setter no son mostrados para hacer esto mas fcil de leer ) muestra una clase Customer sim- ple . Esta tiene un identifi- cador (id) , un nombre ( firstname) , apellido ( last- name) 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 :
# La clase tiene que ser identifi- cada como una entidad usan- do la anotacin @javax.persistence.Entity
# Una propiedad de la clase tiene que tener un identifica- dor anotado con @javax.persistence.Id
# Tiene que haber un construc- tor sin argumentos
El cdigo que sigue mues- tra lo mnimo requerido para definir un "persistence object" . Ahora vamos a manipular este objeto, deseo persistir mi objeto customer , actualizar algu- na de sus propiedades y borrarlo de la base de da- tos. Estas operaciones sern echas a travs de la interfa- ce javax.persistence.EntityManager de JPA.
Para esto, quienes estn familiarizados con el patrn DAO , el EntityManager Page 2 JAVA PERSISTENCE API @Entity public class Customer {
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", "john@lenon.com", 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("john@beatles.co.uk"); trans.commit();
// Deletes the customer trans.begin(); em.remove(customer); trans.commit();
// Closes the entity manager and the factory em.close(); emf.close(); } <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property na- me="toplink.jdbc.password" value=""/> <property name="toplink.target- database" value="MySQL4"/> <property name="toplink.ddl- generation" value="create-tables"/> </properties> </persistence-unit> </persistence>
En el cdigo arriba , hay solo una unidad de persis- tencia , llamada waterme- lonPU ( el archivo persis- tence.xml puede contener muchas unidades de persis- tencia ) . Usted puede pen- sar en una unidad de per- sistencia como un conjunto de entidades ( el elemento class ) que comparten pro- piedades comunes . En este caso , estas propiedades son : url de la base de da- tos , driver JDBC , creden- ciales. Bajo el elemento "properties" usted encontra- ra propiedades especificas para Top-link por ejemplo toplink.ddl-generation . Esta propiedad es usada por TopLink para generar las tablas automticamente si estas no existen aun . Esto significa que una vez ejecu- tado el cdigo TopLink a creado una tabla para guar- dar la informacin de customers. la tabla 1 muestra la informacin DDL ( data definition languaje ) de la tabla customer.
Este es el DDL que JPA gene- ra auto- mticamente de la clase anotada Customer . Gra- cias a la codificacin por defecto que gua JPA ( y en general Java EE 5 ) No primary Key en cuatro posi- bles 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 prximo primary key # TABLE requiere una tabla con dos columnas : el nombre de la secuencia y su valor ( esta es la estra- tegia por defecto de To- pLink ) # IDENTITY usa un En este punto yo quisiera mejorar algunas cosas. Pri- mero que todo, yo no quie- ro setear un identificador (primary key)del objeto pero en lugar de esto quie- ro que JPA lo incremente automticamente. Gracias a las anotaciones , esto es fcil de hacer.
Solo necesito anotar mi atributo identificador @javax.persitence.GeneratedValue . Esta anotacin genera 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 ) obli- gatorios. El mximo largo de un numero de telfono (telephone) tiene que ser 15 caracteres y la columna Unidad de Persistencia Aadiendo funcionalidades y customizando el mapeo lumna tiene el mismo nombre de las propie- dades . Los tipos de datos son tambin mapeados por defecto ( ejemplo String es mapeado a var- char(255) ). Customizando Usted solo necesita aadir cdigo customizado cuando el cdigo 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 co- Page 3 JAVA PERSISTENCE API una entidad es cargada , persistida , actualizada o removida . Una aplicacin puede ser notificada antes o despus de ocurridos estos eventos usando anotacio- nes . JPA tiene un conjunto de "callback annotatios" que pueden ser usadas en mto- dos y permiten al desarrolla- dor aadir cualquier regla de negocios que desee . JPA llamara al mtodo anotado antes o despus de estos eventos . La tabla 4 muestra las "callback anotations" Como uso las callback an- Ahora con el mapeo entre la clase Customer y la tabla t_customer quedo mejor gracias a que las anotacio- nes @Column y @Table tie- nen muchos atributos. Hay otras dos cosas que quisiera hacer . Primero ase- gurarme que todo telfono es ingresado usando cdigos internacio- nales . co- menzando con el sm- bolo '+' . 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 , notatios para mis necesida- des ? , Primero me encarga- re del formato del numero de telfono . Quiero verifi- car que el primer carcter del telfono es "+" , Yo pue- do hacer esto antes que la entidad sea persistida o ac- tualizada . solo tengo que crear un mtodo ( validateP- honeNumber en mi ejem- plo , pero el nombre es irre- levante ) con algo de lgica de negocios y con las anota- ciones @PrePersist y @PreUpdate, JPA hace el resto. El cdigo de ejemplo en la Anotaciones Callback email tiene que ser renombrada a e_mail con un "underscore" .
Todos estos pequeos cambios pueden ser hechos con las anotaciones. Para cambiar el nombre ( name ) de la tabla anote la clase con @javax.persistence.Table . La anotacin @javax.persistence.Column es usada para definir las columnas y tienen una serie de Una aplicacin puede ser notificada antes o despus de ocurridos estos eventos JPA usando "callback annotations" Page 4 JAVA PERSISTENCE API siguiente pagina.
Para la edad del cliente , hare algo similar, calculare la edad del cliente antes que la fecha de nacimiento sea in- sertada ( @PostPersist ) o actualizada ( @PostUpdate ) , y claro cada vez que el clien- te es cargado de la base de datos ( @PostLoad ).
TAMP ) . Entonces estoy en la capacidad de calcular la edad del cliente pero yo necesito persistir esa infor- macin ? , NO , conociendo que el valor cambia todos los aos . Para hacer que esta propiedad (age) edad no sea persistente , usare la anotacin @Transient ( La Para hacer que esto funcio- ne . necesito aadir un nue- vo atributo a la clase Custo- mer : date of birth . Para notificar a JPA que mapee este atributo a una fecha , uso la anotacin @Temporal con el atributo TemporalTy- pe.DATE ( las opciones son DATE , TIME , TIMES- tabla no tendr una colum- na edad mas ) ver tabla 5.
Anotaciones Callback .. aadir 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 el customer tambin remue- vo el address. Pero persistiendo y remo- viendo ambos objetos pare- ce que se hiciera mas traba- jo que el que necesito. Se- ria mejor si yo pudiera per- sistir o remover justo el objeto raz ( el Customer ) y permitir las dependencias que se persistan o remue- van automticamente ? , TopLink y la codificacin Como este cdigo muestra , usted tiene primero que instanciar un objeto Custo- mer y un Adress , Para lin- kear los dos ,uso un mto- do setter ( set HomeAd- dress ) y luego persistido cada objeto , cada uno en la misma transaccin. Por- que no tiene sentido tener un adress en el sistema que no este linkeado a un cus- tomer , cuando yo remuevo por defecto har las asocia- ciones opcionales y basa- das en mis requerimientos, es decir al persistir o remo- ver customer tambin se hace con su address.
Quiero que un Customer tenga exactamente un Ad- dress , significa que un va- lor null no es permitido . Puedo lograr todo eso usando la anotacin One to One Relationship One to One Relationship "customer's" uso el cdigo que sigue :
One to One Relationship Como tu pueden ver en la tabla 6 , la clase Address usa la anotacin @Entity para notificar a JPA que es una clase persistente y @Column para customizar el mapeo. Creando la rela- cin entre Customer y Ad- dress es simple . Yo simple- mente aado una propie- dad Address en la clase Customer . Para persistir la direccin de los Ahora que tengo mapeada mi clase Customer y tengo anotaciones callback para validar y calcular data , ne- cesito aadir una direccin. Un Customer tiene una y solo una address (direccin) enton- ces Watermelon pueden enviar al cliente un regalo por su cumpleaos . Lo representare como una cla- se separada, clase Address con un id , una calle (street) , una ciudad (city) , un c- Page 6 JAVA PERSISTENCE API al persistir o remover customer tambin se hace con su address public void createCustomerWithAddress() { // Instantiates a Customer and an Address objecy Customer customer = new Customer("John", "Lennon", "+441909", "john@lenon.com", 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(); }
@OneToOne es usada para anotar una relacin . Este tiene muchas propiedades incluyendo un cascade usado para "cascading" de cualquier tipo de accin. En este ejemplo , quiero "cascade" la accin de per- sistir y remover. De esta forma , cuando hago remo- ve o persist de un objeto customer este automtica- mente lleva acabo esta ac- cin para el "address" . El atributo fetch dice a JPA que poltica usar cuando cargamos una relacin. Esta puede ser una asocia- cin de carga tarda ( lazy loading LAZY) , o "eagerly" (EAGER) porque quiero cargar la direccin de la casa "homeAddress" tan pronto como el objeto Customer es cargado. La anotacin @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 ningn valor null ( nullable=false)
JPA entonces creara los siguientes DDLs con una constraint de integri- dad ,para la relacin 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 poltica usar cuando cargamos una relacin @Entity @Table(name = "t_customer") public class Customer {
@Id @GeneratedValue private Long id; (...) @Transient private Integer age;
// constuctors, getters, setters } 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 notacin , para obtener al atributo country del obje- Como usted puede ver en este query JPQL usa la no- tacin objeto . Usted tiene que hacer query no sobre una tabla, mas bien sobre un objeto. El carcter c ( el nombre es irrele- vante ) es el alias de un to address. SELECT c FROM Customer c WHERE c.homeAddress.country='US'; Ac hay un conjunto de querys que nosotros pode- mos hacer con JPQL . Ver tabla 8 Querying Objects JPQL Queries, ejemplos ... dos formas . De cualquiera de las dos formas el string "John" es buscado : puede ser parte del query JPQL o puede ser pasado como parmetro , en este ultimo caso necesito usar el mto- do setParameter. Querying Objects, sobre objetos ... Para hacer querys sobre los objetos se necesita Entity- Manager para crear un ob- jeto Query . Luego tengo el resultado del query llaman- do el getResultList o gen- SingleResult 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 Hasta ahora yo estoy usan- do 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 tambin permite que hagas querys sobre los objetos. Esto usa "Java Persistence Query Langua- ge" ( JPQL) , el cual es similar a SQL y es tambin independiente de la base de datos , Este es un lenguaje rico que nos per- mite hacer querys comple- jos sobre objetos ( asocia- ciones , herencia , clases abstractas ) Los querys usan las pala- bras SELECT , FROM y WHERE , mas un conjunto de operadores para filtrar la data ( IN , NOT IN , EXIST , LIKE , IS NULL , IS NOT NULL ) o para con- trolar las colecciones ( IS EMPTY , IS NOT EMPTY , MEMBER OF ) , Tambin hay funcio- nes para manejar Strings ( LOWER , UPPER , TRIM , CONCAT , LENGTH , SUBS- TRING ) , n- meros ( ABS , SQRT , MOD ) , o colecciones ( COUNT , MIN , MAX , SUM ). Co- mo SQL , tu tambin pue- des 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.