420 likes | 519 Views
Disseny de la persistència Hibernate i frameworks de persistència POJO. Toni Navarrete Enginyeria del Software II – UPF 200 7. Recordatori: problemes (i aventatges) d’altres solucions. Serialitzatió Avantantges: Estàndard en qualsevol ambient Java Fàcil d’utilitzar
E N D
Disseny de la persistència Hibernate i frameworks de persistència POJO Toni Navarrete Enginyeria del Software II – UPF 2007
Recordatori: problemes (i aventatges) d’altres solucions • Serialitzatió • Avantantges: • Estàndard en qualsevol ambient Java • Fàcil d’utilitzar • El model de dades és directament el de les classes (aprofita OO) • Inconvenients: • No té les característiques d’un gestor de BD robust • Per exemple, índexs, transaccions,... • No és escalable
Recordatori: problemes (i aventatges) d’altres solucions • JDBC • Permet treballar amb un Gestor de BD • Dóna accés a SQL • Usa el model relacional • No el model orientat a objectes de Java • Procedimental en lloc d’OO • El processament de dades es fa mitjançant SQL i no Java • Les operacions són expressades en relació a taules, files i columnes, referides a l’esquema de la BD • SQL no està realment estandaritzat, amb la qual cosa la portabilitat no sempre és total
Recordatori: problemes (i aventatges) d’altres solucions • Quan s’utilitza JDBC, el programador està obligat a treballar amb dos models de dades diferents, així com amb dos paradigmes i llenguatges d’accés a les dades distints • Si cal canviar l’esquema de la BD també cal canviar les classes • El resultat sol ser que s’escriu codi Java procedural per manipular les taules. No OO
Recordatori: problemes (i aventatges) d’altres solucions • EJB 2.1 i la persistència manegada pel contenidor (CMP) • Defineix un mecanisme de persistència transparent • Els mètodes dels EJB d’entitat són independents de l’esquema de dades • Llenguatge de consultes abstracte, independent de SQL • EJB és una arquitectura complexa i amb un alt overhead
Objectius dels frameworks de persistència POJO (Plain Old Java Object) • Treballar amb el model de classes i deixar al framework de persistència la traducció al model relacional • Normalment el mapping s’especifica mitjançant un fitxer XML • Operacions d’accés a la BD referides a: • Guardar objectes (inserts) • Modificar objectes (updates) • Eliminar objectes (deletes) • Recuperar col·leccions d’objectes (selects) • Independència entre el model de classes i el model físic de la BD mínima intrusió i màxima transparència
Hibernate • Hibernate és un d’aquests frameworks de persistència POJO • Codi obert i gratuït (llicència GNU LGPL) • Molt estès, gran comunitat de desenvolupadors i usuaris
Problemes dels frameworks de persistència • Adoptar un API no estandaritzada és sempre arriscat • A moltes implementacions: • Falta d’encapsulació causada per la necessitat de definir mètodes getter i setter públics per als atributs persistents • Extensibilitat limitada per la falta de soport de l’herència • Falta de soport de referències polimòrfiques • Dependència absoluta del framework
JDO • És una iniciativa de la comunitat Java per crear un framework estandaritzat per a persistència (de la mateixa forma que Swing ho és per a interfície gràfica) • JDO és només una especificació, de la qual els fabricants de software faran implementacions (a vegades lliures, a vegades no) • JPOX és la implementació de referència (i lliure), però no és prou estable • JDO pot suportar no només BD relacionals sinó qualsevol forma d’emmagatzemantge permanent (XML, fitxers,...) • És molt “transparent” (per exemple, persistència transitiva) • No ha acabat de consolidar-se com a estàndard • Actualment un projecte Apache: http://db.apache.org/jdo/
Java Persistence API (JPA) • Definida en EJB 3.0 (JavaEE 5) http://java.sun.com/javaee/technologies/persistence.jsp • La idea original era que JPA es fes a partir de JDO, però ha acabat essent una especificació diferent • http://db.apache.org/jdo/jdo_v_jpa.html • http://db.apache.org/jdo/jdo_v_jpa_orm.html • JPA substitueix als EJB d’entitat • Ja no cal definir les interfícies • No s’utilitza el deployment descriptor
Hibernate i els estàndards • Hibernate no té cap relació amb JDO • Té una API “EntityManager” per permetre compatibilitat amb Java Persistenca API
Nota: Hibernate i JDBC • Hibernate no substitueix JDBC • Són complementaris • Hibernate utilitza JDBC per sota per connectar-se a la BD • JDBC és útil per gestionar connexions amb la BD • JDBC és força sòlid • JDBC està soportat per la majoria de fabricants de SGBD
Exemple d’una petita aplicació amb Hibernate • Farem una classe que permet guardar a la BD instàncies d’una classe Persona • Una altra classe ens permetrà recuperar les instàncies de Persona a la BD
La classe Persona package persones; public class Persona { Long id; //a Hibernate cal definir un identificador d’instància, amb els seus mètodes get i set String nom, cognom; int anyNaixement = 0; public Persona() {} // a Hibernate és obligatori que hi hagi un constructor sense arguments public Persona(String nom, String cognom, int anyNaixement) { this.nom = nom; this.cognom = cognom; this. anyNaixement = anyNaixement; } public Long getId() { return id; } private void setId(Long id) { this.id = id; } public void setNom(String nom) { this.nom = nom; } public String getNom() { return this.nom; } public void setCognom(String cognom) { this.cognom = cognom; } public String getCognom() { return this.cognom; } public void setAnyNaixement(int anyNaixement) { this. anyNaixement = anyNaixement; } public int getAnyNaixement() { return this.anyNaixement; } }
Mapeig entre classe i taula(es) de la BD • Fitxer persona.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="persones"> <class name="Persona" table="PERSONA"> <id name="id" column="PERSONA_ID"> <generator class="native"/> </id> <property name="nom"/> <property name="cognom"/> <property name="anyNaixement"/> </class> </hibernate-mapping>
Configuració del gestor de BD (i altres propietats) <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Propietats de la BD --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/hibernate_db1 </property> <property name="connection.username">usuari</property> <property name="connection.password">password</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> • Fitxer hibernate.cfg.xml
Configuració del gestor de BD (i altres propietats) • Fitxer hibernate.cfg.xml <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Esborra i torna a crear l’esquema de la BD a cada execució (només per proves) --> <!-- Cal comentar-ho quan volguem fer consultes!!!--> <property name="hbm2ddl.auto">create</property> <!-- Referència al(s) fitxer(s) de mapping --> <mapping resource="persones/Persona.hbm.xml"/> </session-factory> </hibernate-configuration>
package persones; import org.hibernate.*; import org.hibernate.cfg.*; public class Insercions { static Session session; static SessionFactory sessionFactory; public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //crea les instàncies que calgui i les guarda Persona p1 = new Persona("pep", "garcia", 1970); Persona p2 = new Persona("josep", "fernandez", 1965); session.save(p1); session.save(p2); //fa el commit de la transacció session.getTransaction().commit(); //tanca el sessionFactory i allibera els recursos associats (caches, pools,…) sessionFactory.close(); } catch (RuntimeException e) { if (session!=null) session.getTransaction().rollback(); e.printStackTrace(); } } } Inserció d’instàncies
package persones; import org.hibernate.*; import org.hibernate.cfg.*; public class Consulta { static Session session; static SessionFactory sessionFactory; public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //recupera les instàncies de la classe Persona i escriu els seus atributs per pantalla java.util.Iterator it = session.createQuery("from Persona").list().iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); System.out.println("Nom: "+p.getNom()); System.out.println("Cognom: "+p.getCognom()); System.out.println("Any naixement: "+p.getAnyNaixement()); } //fa el commit de la transacció session.getTransaction().commit(); // tanca el sessionFactory i allibera els recursos associats (caches, pools,…) // sessionFactory.close(); } catch (RuntimeException e) { if (session!=null) session.getTransaction().rollback(); e.printStackTrace(); } } } Consulta d’instàncies
package persones; import org.hibernate.*; import org.hibernate.cfg.*; public class Modificacio { static Session session; static SessionFactory sessionFactory; public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //recupera les instàncies de la classe Persona java.util.Iterator it = session.createQuery("from Persona").list().iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); if (p.getNom().equals("pep")) p.setNom("josep"); } //fa el commit de la transacció session.getTransaction().commit(); // tanca el sessionFactory i allibera els recursos associats (caches, pools,…) // sessionFactory.close(); } catch (RuntimeException e) { if (session!=null) session.getTransaction().rollback(); e.printStackTrace(); } } } Modificació d’instàncies NOTA: si l’objecte que modifiquem no s’ha guardat o recuperat en la mateixa transacció, cal afegir session.update(p); En l’exemple això no és necessari
package persones; import org.hibernate.*; import org.hibernate.cfg.*; public class Esborrat { static Session session; static SessionFactory sessionFactory; public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //recupera les instàncies de la classe Persona i escriu els seus atributs per pantalla java.util.Iterator it = session.createQuery("from Persona").list().iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); if (p.getAnyNaixement()<1970) session.delete(p); } //fa el commit de la transacció session.getTransaction().commit(); // tanca el sessionFactory i allibera els recursos associats (caches, pools,…) // sessionFactory.close(); } catch (RuntimeException e) { if (session!=null) session.getTransaction().rollback(); e.printStackTrace(); } } } Esborrat d’instàncies
Més sobre consultes • Les consultes s’expressen en HQL • Sempre fent referència al model de classes, no al de la BD • Exemple: persones nascudes abans d’un cert any: public static void imprimeixLlistatAbansAny(int any) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); java.util.Iterator it = session.createQuery( "from Persona p where p.anyNaixement < ?") .setInteger(0, any) .list().iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); System.out.println("Nom: "+p.getNom()); System.out.println("Cognom: "+p.getCognom()); System.out.println("Any naixement: "+p.getAnyNaixement()); } session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); }
Altres exemples de consultes • Paginació Query q = session.createQuery("from Persona"); q.setFirstResult(20); q.setMaxResults(10); List persones = q.list(); • Ordenació Query q = session.createQuery( "from Persona p order by p.cognom"); • Agregacions Query q = session.createQuery( "select avg(p.pes), sum(p.pes), max(p.pes), count(p) from Persona p");
Recuperar objecte per ID • L’objecte de sessió té un mètode load que permet recuperar un objecte persistent si sabem el seu identificador: Persona p = (Persona) session.load( Persona.class, new Long(1234) );
Estats d’una instància • transient • La instància no està, i no ha estat mai, associada a un context de persistència (sessió). No té un id persistent (valor de clau primària) • persistent • La instància està associada a un context de persistència (sessió). Té un id persistent i normalment està associada a una fila en la BD. Hibernate garantitza que l’estat de la instància és equivalent a l’estat persistent, mentre segueixi associat a la transacció • detached • La instància va estar associada amb un context de persistència (sessió), però aquest es va tancar. Té un id persistent i normalment està associada a una fila en la BD, però els canvis en l’estat de la instància no es veuen reflectits a la BD • Notes: • Totes les comunicacions amb la BD (lectures i escriptures) es fan dins d’una transacció • En els nostres exemples, una sessió (un context de persistència) està emmarcada per una transacció. Més endavant veurem que aquest patró es pot canviar
Flush d’una sessió • Totes les instàncies amb estat “persistent” que es modifiquen dins d’una sessió es poden actualitzar a la BD directament cridant el mètode flush (sense haver de fer-ho un a un) Persona p1 = (Persona) session.load(Persona.class, new Long(1) ); Persona p2 = (Persona) session.load(Persona.class, new Long(2) ); p1.setNom(“joan”); p2.setNom(“jordi”); session.flush();
Millorant l’estructura package util; import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError(e); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
package persones; import org.hibernate.*; import org.hibernate.cfg.*; import util.HibernateUtil; public class Persona { Long id; String nom, cognom; int anyNaixement = 0; public Persona() {} //Constructors, gets i sets i altres mètodes public void guarda() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.save(this); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); }
public void esborra() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.delete(this); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); } public void actualitza() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.update(this); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); } public static java.util.List getLlistaPersones() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); java.util.List persones = session.createQuery("from Persona").list(); session.getTransaction().commit(); HibernateUtil.getSessionFactory().close(); return persones; } }
package clients; import org.hibernate.*; import org.hibernate.cfg.*; import persones.*; public class Client { public static void main(String[] args) { //crea les instàncies que calgui i les guarda Persona p1 = new Persona("pep", "garcia", 1970); Persona p2 = new Persona("josep", "fernandez", 1965); p1.guarda(); p2.guarda(); java.util.List persones = Persona.getLlistaPersones(); java.util.Iterator it = persones.iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); if (p.getNom().equals("pep")) p.esborra(); else { p.setNom(p.getNom().toUpperCase()); p.setCognom(p.getCognom().toUpperCase()); p.actualitza(); System.out.println("Nom: "+p.getNom()); System.out.println("Cognom: "+p.getCognom()); System.out.println("Any naixement: "+p.getAnyNaixement()); } } } }
Un exemple més complet (amb herència i associacions) Grup Persona 0..* 0..* Empleat
Fitxers de mapping • Un per classe o un per tot el paquet • Herència • Cal definir entre les tres formes d’implementar l’herència • Si és el cas, cal definir quin atribut s’utilitza com a discriminador, i quin valor pren cada subclasse • Associacions • 1-a-1, 1-a-molts o molts-a-molts • Unidireccionals o bidireccionals • Amb o sense taula de join
<hibernate-mapping package="persones"> <class name="Persona" table="PERSONA"> <id name="id" column="PERSONA_ID"> <generator class="native"/> </id> <discriminator column="TIPUS" type="string"/> <property name="nom"/> <property name="cognom"/> <property name="anyNaixement"/> <subclass name="Empleat" extends="Persona" discriminator-value="persones.Empleat"> <property name="salari"/> </subclass> </class> <class name="Grup" table="GRUP"> <id name="id" column="GRUP_ID"> <generator class="native"/> </id> <property name="nom"/> <set name="persones" table="GRUP_PERSONA"> <key column="GRUP_ID"/> <many-to-many column="PERSONA_ID" class="Persona"/> </set> </class> </hibernate-mapping>
package persones; import java.util.*; import org.hibernate.*; import org.hibernate.cfg.*; public class Insercions { public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //crea les instàncies que calgui i les guarda Persona toni = new Persona("toni","navarrete",1973); session.save(toni); Persona dani = new Persona("dani","soto",1973); session.save(dani); Empleat pep = new Empleat("pep","garcia",1977,30000); session.save(pep); Grup g = new Grup("grupo 1"); Set s = new HashSet(); s.add(toni); s.add(pep); g.setPersones(s); session.save(g); //fa el commit de la transacció session.getTransaction().commit(); sessionFactory.close(); } catch (Exception e) { e.printStackTrace(); } } }
package persones; import java.util.*; import org.hibernate.*; import org.hibernate.cfg.*; public class Consultes { public static void main(String[] args) { try { // Crea la Session de hibernate a partir del fitxer hibernate.cfg.xml SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.getCurrentSession(); //comença la transacció session.beginTransaction(); //recupera les instàncies de la classe Grup java.util.List grups = session.createQuery("from Grup").list(); System.out.println("Nombre de grups: "+grups.size()); for (int i=0;i<grups.size();i++) { Grup g = (Grup)grups.get(i); System.out.println(" Nom: "+g.getNom()); System.out.println(" Número de membres: "+g.getPersones().size()); java.util.Iterator it = g.getPersones().iterator(); while (it.hasNext()) { Persona p = (Persona)it.next(); System.out.println(" Nom: "+p.getNom()+" "+p.getCognom()); if (p instanceof Empleat) System.out.println(" Salari: "+((Empleat)p).getSalari()); } } //fa el commit de la transacció session.getTransaction().commit(); sessionFactory.close(); } catch (Exception e) { e.printStackTrace(); } }
Consultes amb joins • Un empleat pertany a 1 departament, un departament té molts empleats • Empleat té un atribut departament • Departament té un atribut col·lecció empleats • Empleats d’un departament: • “from Empleat e where e.departament.nom=‘Informàtica’ ” • “select e from Empleat e join e.departament d where d.nom=‘Informàtica’ ”
Aspectes a tenir en compte sobre les referències i col·leccions • Persistència transitiva • Quan guardem una instància, cal també guardar expressament cada una de les instàncies referenciades per ella (no com amb serialització o JDO) • Per evitar això: es pot configurar al fitxer de mapping: • <one-to-many name="persones" cascade="persist"/> • <one-to-many name="persones" cascade="persist,delete,lock"/> • Quan recuperem una instància, podem recuperar les seves referències a altres instàncies sense haver de definir una altra consulta
Aspectes a tenir en compte sobre les referències i col·leccions • Lazy associations • Quan recuperem una instànica (grup), la col·lecció d’instàncies referenciades (conjunt de persones) no es carrega en memòria fins que no s’hi accedeix • Aquest comportament per defecte es pot modificar: diverses formes de fetching i caches • Instàncies detached • Quan recuperem una instància dins d’una transacció, un cop es tanca la transacció, no podem accedir a les seves referències
Sessions i transaccions • El patró més habitual és el de session-per-request (no necessàriament session-per-operation) • Converses més llargues • No és viable mantenir oberta una sessió entre diferents peticions o per una aplicació • session-per-request-with-detached-objects • session-per-conversation (la sessió es pot desconnectar de la capa de JDBC després d’un commit, i tornar a connectar quan rep una altra petició del mateix client)