====== Hibernate ====== {{tag> #FpInfor #Dam #DamMp06 #DamMp06Uf2 #DamMp06Uf02}} ---- ==== ORMs i JPA ==== ORM (Object Relational Mapping) és una tècnica per compartir dades entre bases de dades i objectes programmatics. JPA (Java Persistence API) són unes llibreries de Java EE per gestionar dades allotjades en bases de dades relacionals desde aplicacions JAVA ==== Hibernate ==== Hibernate és una eina de mapeig ORM per JAVA que utilitza JPA Hibernate “facilita” la feina de treballar amb bases de dades des de Java, fent transparent la relació entre taules de la base de dades i objectes Java Un cop definits els objectes i com es relacionen amb la base de dades, Hibernate pot crear les taules automàticament i gestionar l'actualització de les dades a la base de dades sense que s'hagi d'escriure codi SQL. ==== Configuració de Hibernate, arxiu "hibernate.properties" ==== Hibernate es configura a través de l'arxiu "hibernate.properties", aquest arxiu conté configuracions de hibernate que no estan relacionades amb el mapeig de dades, tals com: - Tipus de connexió amb la base de dades. - Usuari/Contrasenya si la base de dades ho requereix - URI de la base de dades == La configuració de "hibernate.properties" per MySQL: == (Cal canviar DB_USER i DB_PASSWORD pels valors corresponents a la connexió ...) hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.connection.driver_class=com.mysql.jdbc.Driver hibernate.connection.url=jdbc:mysql://localhost:3306/hibernate hibernate.connection.username=DB_USER hibernate.connection.password=DB_PASSWORD hibernate.show_sql=false hibernate.hbm2ddl.auto=update == La configuració de "hibernate.properties" per SQLite: == (Amb SQLite s’especifica l’arxiu a utilitzar, en aquest cas "database.db") hibernate.dialect=com.enigmabridge.hibernate.dialect.SQLiteDialect hibernate.connection.driver_class=org.sqlite.JDBC hibernate.connection.url=jdbc:sqlite:database.db hibernate.show_sql=false hibernate.hbm2ddl.auto=update ==== Configuració de Hibernate, arxiu "hibernate.cfg.xml" ==== L'arxiu "hibernate.cfg.xml" conté configuracións relacionades amb el mapeig de dades. **Important**: En aquest arxiu s’ha de posar un element per cada classe que s’ha de mapejar, segons si són amb format .hbm.xml (format antic) o JPA (format nou) Exemple: En aquest exemple s'indica: - Cal mapejar les classes "Employee" i "Contact" - La classe "Employee" fa servir el format antic (hbm.xml) i configura les relacions amb la base de dades a través de l'arxiu "Employee.hbm.xml" - La classe "Contact" fa servir el format JPA modern i configura les relacions directament a través de "Contact.java" ==== Exemple d'arxiu de mapeig amb format ".hbm.xml" ==== Antigament el mapeig entre Hibernate i la base de dades es feia a través d'arxius ".hbm.xml" This class contains the employee detail. En aquest exemple es veu com: - Relaciona l'objecte "Employee" amb la taula "employee" - El camp 'id' és de tipus enter i es genera automàticament - Els camps 'firstName" i "lastName" corresponen a les columnes de la base de dades "first_name" i "last_name" i són de tipus String ==== Exemple d'arxiu de mapeig amb anotacions JPA al codi ==== En aquest exemple es fan servir notacions JPA per definir amb quina taula es relaciona l'objecte i les propietats de les columnes a la base de dades import javax.persistence.*; @Entity @Table(name="contact") public class Contact { @Id @Column(name="id") @GeneratedValue(strategy=GenerationType.AUTO) // L’id es genera automàticament private long id; @Column(name="name") private String name; ==== Hibernate, Manager.java ==== És una bona pràctica tenir un objecte "Manager" que s'encarregui de definir totes les operacions que es poden fer amb la base de dades, habitualment són operacions basades en CRUD: - Create - Read - Update - Delete Exemple d'arxiu Manajer.java import java.io.Serializable; import java.util.Collection; import java.util.List; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class Manager { private static SessionFactory factory; public static void createSessionFactory() { try { Configuration configuration = new Configuration(); configuration.configure(); StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings( configuration.getProperties()).build(); factory = configuration.buildSessionFactory(serviceRegistry); } catch (Throwable ex) { System.err.println("Failed to create sessionFactory object." + ex); throw new ExceptionInInitializerError(ex); } } public static void close () { factory.close(); } public static Employee addEmployee(String firstName, String lastName, int salary){ Session session = factory.openSession(); Transaction tx = null; Employee result = null; try { tx = session.beginTransaction(); result = new Employee(firstName, lastName, salary); session.save(result); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); result = null; } finally { session.close(); } return result; } public static Contact addContact(String lname, String lmail){ Session session = factory.openSession(); Transaction tx = null; Contact result = null; try { tx = session.beginTransaction(); result = new Contact(lname, lmail); session.save(result); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); result = null; } finally { session.close(); } return result; } public static T getById(Class clazz, long id){ Session session = factory.openSession(); Transaction tx = null; T obj = null; try { tx = session.beginTransaction(); obj = clazz.cast(session.get(clazz, id)); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } return obj; } public static void updateContact(long contactId, String name, String email, Set employees){ Session session = factory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); Contact obj = (Contact) session.get(Contact.class, contactId); obj.setName(name); obj.setEmail(email); obj.setEmployees(employees); session.update(obj); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } } public static void updateEmployee(long employeeId, String firstName, String lastName, int salary){ Session session = factory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); Employee obj = (Employee) session.get(Employee.class, employeeId); obj.setFirstName(firstName); obj.setLastName(lastName); obj.setSalary(salary); session.update(obj); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } } public static void delete(Class clazz, Serializable id){ Session session = factory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); T obj = clazz.cast(session.get(clazz, id)); session.delete(obj); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } } public static Collection listCollection(Class clazz) { return listCollection(clazz, ""); } public static Collection listCollection(Class clazz, String where){ Session session = factory.openSession(); Transaction tx = null; Collection result = null; try { tx = session.beginTransaction(); if (where.length() == 0) { result = session.createQuery("FROM " + clazz.getName()).list(); } else { result = session.createQuery("FROM " + clazz.getName() + " WHERE " + where).list(); } tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } return result; } public static String collectionToString(Class clazz, Collection collection){ String txt = ""; for(Object obj: collection) { T cObj = clazz.cast(obj); txt += "\n" + cObj.toString(); } if (txt.substring(0, 1).compareTo("\n") == 0) { txt = txt.substring(1); } return txt; } public static void queryUpdate (String queryString) { Session session = factory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); SQLQuery query = session.createSQLQuery(queryString); query.executeUpdate(); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } } public static List queryTable (String queryString) { Session session = factory.openSession(); Transaction tx = null; List result = null; try { tx = session.beginTransaction(); SQLQuery query = session.createSQLQuery(queryString); @SuppressWarnings("unchecked") List rows = query.list(); result = rows; tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); } finally { session.close(); } return result; } public static String tableToString (List rows) { String txt = ""; for (Object[] row : rows) { for (Object cell : row) { txt += cell.toString() + ", "; } if (txt.length() >= 2 && txt.substring(txt.length() - 2).compareTo(", ") == 0) { txt = txt.substring(0, txt.length() - 2); } txt += "\n"; } if (txt.length() >= 2) { txt = txt.substring(0, txt.length() - 2); } return txt; } } ==== Hibernate, transactions ==== Les transaccions són aquelles operacions que fem amb la base de dades, es diuen així perquè és important que cada transacció obri i tanqui una sessió per mantenir la integritat de les dades. A més: - Les transaccions poden resultar fallides, i no modificar dades a la base de dades - Algunes transaccions s'han de personalitzar per cada objecte (operacions d'afegir, actualitzar, ...) - Altres transaccions són genèriques per tots els objectes (esborrar, llistar, consutar, ...) Exemple de la transacció "addEmployee", afegir un "Employee" a la taula "employee": public static Employee addEmployee(String firstName, String lastName, int salary){ Session session=factory.openSession(); Transaction tx=null; Employee result=null; try { tx=session.beginTransaction(); result=new Employee(firstName, lastName, salary); session.save(result); tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); e.printStackTrace(); result=null; } finally { session.close(); } return result; } **Important**: Les dades no es guarden fins que s'ha fet el "**tx.commit()**", i fins i tot en aquest moment la transacció pot resultar fallida ==== Hibernate, getters i setters ==== Hibernate fa ús dels getters i setters dels objectes, així que són obligatoris per tots aquells valors que estan mapejats cap a la base de dades Igualment, és una bona pràctica sobreescriure la funció "toString()" per obtenir les dades de l'objecte en qüestió. Exemple complert amb getters i setters: import java.io.Serializable; import java.util.List; import java.util.Set; import javax.persistence.*; @Entity @Table(name="Contact", uniqueConstraints={@UniqueConstraint(columnNames="id")}) public class Contact implements Serializable { @Id @Column(name="id", unique=true, nullable=false) @GeneratedValue(strategy=GenerationType.IDENTITY) // L'id es genera automàticament private long contactId; @Column(name="name") private String name; @Column(name="email") private String email; @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable(name="Employee_Contact", joinColumns={@JoinColumn(referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(referencedColumnName="id")}) private Set employees; // Ha de tenir getter i setter perquè s'encarrega de la taula relacional N:N public Contact() { } public Contact(String name, String email) { this.name=name; this.email=email; } public long getContactId() { return this.contactId; } public void setContactId(long id) { this.contactId=id; } public String getName() { return this.name; } public void setName(String name) { this.name=name; } public String getEmail() { return email; } public void setEmail(String email) { this.email=email; } public Set getEmployees () { return this.employees; } public void setEmployees (Set employees) { this.employees=employees; } public List queryEmployees () { long id=this.getContactId(); return Manager.queryTable("SELECT DISTINCT e.* FROM Employee_Contact ec, Employee e WHERE e.id=ec.employees_id AND ec.contacts_id=" + id); } @Override public String toString () { String str=Manager.tableToString(queryEmployees()); return this.getContactId() + ": " + this.getName() + ", " + this.getEmail() + ", Employees: [" + str + "]"; } } ==== Tipus de relacions ==== Per mapejar les relacions entre taules, Hibernate ofereix: - 1:1 one-to-one - 1:N one-to-many - N:M many-to-many Hi ha dues maneres: - Amb arxius .hbm.xml (la manera antiga) - Amb anotacions JPA directament al codi Java (la manera actual) ==== Tipus de relacions 1:N OneToMany ==== Són aquelles que creen una relació entre un element d’una taula i múltiples d’altres taules. Per exemple: - Un carret de la compra té diversos productes (però cada producte està en un carret) - Una escola té diversos alumnes (però cada alumne va a una escola) **NOTA**: Per simplicitat no veurem les relacions ‘one-to-one’ ==== Exemple 1:N OneToMany amb ".hbm.xml" ==== Cart.hbm.xml CD Cart.java public class Cart implements Serializable { private long cartId; private String type; private Set items; public Cart() {} public Cart(String type) { this.type=type; } // Getters i Setters } Item.hbm.xml AB Item.java public class Item implements Serializable { private long itemId; private String name; private Cart cart; public Item() { } public Item(String name) { this.name=name; } // Getters i Setters } ==== Exemple 1:N OneToMany amb JPA ==== Cart.java @Entity @Table(name="Cart") public class Cart implements Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="cartId", unique=true, nullable=false) private long cartId; @Column(name="type") private String type; @OneToMany @JoinColumn(name="cartId") private Set items; public Cart() {} public Cart(String type) { this.type=type; } // Getters, Setters i toString } Item.java @Entity @Table(name="Item") public class Item implements Serializable { @Id @Column(name="id", unique=true, nullable=false) @GeneratedValue(strategy=GenerationType.AUTO) // L'id es genera automàticament private long itemId; @Column(name="name") private String name; @ManyToOne @JoinColumn(name="cartId", insertable=false, updatable=false) private Cart cart; public Item() { } public Item(String name) { this.name=name; } // Getters, Setters i toString } ==== Tipus de relacions N:N ManyToMany ==== Són aquelles que creen una relació entre múltiples elements de múltiples d’altres taules. Necessiten una taula extra per mantenir la relació. Per exemple: - Una supermercat té N treballadors i alguns d'aquests són N clients - Múltiples comandes es relacionen amb múltiples clients ==== Exemple N:N ManyToMany amb ".hbm.xml" ==== Contact.hbm.xml AB Contact.java public class Contact implements Serializable { private long contactId; private String name; private String email; private Set employees; public Contact() { } public Contact(String name, String email) { this.name=name; this.email=email; } // Getters i Setters } Employee.hbm.xml AB Employee.java public class Employee implements Serializable { private long employeeId; private String firstName; private String lastName; private int salary; private Set contacts; // No té getter i setter public Employee() {} // Getters i Setters } ==== Exemple N:N ManyToMany amb JPA ==== Contact.java @Entity @Table(name="Contact", uniqueConstraints={@UniqueConstraint(columnNames="id")}) public class Contact implements Serializable { @Id @Column(name="id", unique=true, nullable=false) @GeneratedValue(strategy=GenerationType.IDENTITY) private long contactId; @Column(name="name") private String name; @Column(name="email") private String email; @ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable(name="Employee_Contact", joinColumns={@JoinColumn(referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(referencedColumnName="id")}) private Set employees; // Constructors, Getters, Setters i toString } Employee.java @Entity @Table(name="Employee", uniqueConstraints={@UniqueConstraint(columnNames="id")}) public class Employee implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id", unique=true, nullable=false) private long employeeId; @Column(name="firstName") private String firstName; @Column(name="lastName") private String lastName; @Column(name="salary") private int salary; @ManyToMany(mappedBy="employees") private Set contacts; // Constructors, Getters, Setters i toString } ==== Exemples ==== Els arxius ./run.sh i .\run.bat tenen el codi per fer anar els exemples des de la línia de comandes amb versions modernes de Java (per sistemes Unix o Windows respectivament) Als exemples, cada vegada s'esborra i crea la base de dades com a nova Fan servir “@hibernate.argfile” per configurar els paràmetres de .java que permeten compilar i fer anar Hibernate {{ :hibernate-exempleintroduccio.zip |}} {{ ::hibernate-exemplejpa_onetomany.zip |}} {{ ::hibernate-exemplejpa_manytomany.zip |}} {{ ::hibernate-exemplexml_onetomany.zip |}} {{ ::hibernate-exemplexml_manytomany.zip |}} {{ ::hibernate-exemplemysql.zip |}}