====== 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 extends T> 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 extends T> 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 extends T> clazz) {
return listCollection(clazz, "");
}
public static Collection> listCollection(Class extends T> 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 extends T> 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
==== 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 |}}