Taula de continguts

Hibernate

, , , ,

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 <mapping> per cada classe que s’ha de mapejar, segons si són amb format .hbm.xml (format antic) o JPA (format nou)

Exemple:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
     <mapping resource="Employee.hbm.xml"/>
     <mapping class="Contact"/>
  </session-factory>
</hibernate-configuration>

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»

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
  <class name="Employee" table="employee">
     <meta attribute="class-description">
        This class contains the employee detail.
     </meta>
     <id name="id" type="int" column="id">
        <generator class="native"/>
     </id>
     <property name="firstName" column="first_name" type="string"/>
     <property name="lastName" column="last_name" type="string"/>
     <property name="salary" column="salary" type="int"/>
  </class>
</hibernate-mapping>

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> 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<Employee> 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 <T> 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 <T> Collection<?> listCollection(Class<? extends T> clazz) {
        return listCollection(clazz, "");
    }
 
    public static <T> 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 <T> 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<Object[]> queryTable (String queryString) {
        Session session = factory.openSession();
        Transaction tx = null;
        List<Object[]> result = null;
        try {
            tx = session.beginTransaction();
            SQLQuery query = session.createSQLQuery(queryString);
            @SuppressWarnings("unchecked")
            List<Object[]> 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<Object[]> 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<Employee> 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<Employee> getEmployees () {
		return this.employees;
	}
 
	public void setEmployees (Set<Employee> employees) {
		this.employees=employees;
	}
 
	public List<Object[]> 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

<hibernate-mapping>
 <class name="Cart" table="Cart">
  <meta attribute="class-description">CD</meta>
  <id name="cartId" type="long" column="cartId">
    <generator class="native"/>
  </id>
  <property name="type" column="type" type="string"/>
    <set name="items" cascade="all">
      <key column="cartId"/>
      <one-to-many class="Item"/>
    </set>
  </class>
</hibernate-mapping>

Cart.java

public class Cart implements Serializable {
 
     private long cartId;
     private String type; 
     private Set<Item> items;
 
     public Cart() {}
     public Cart(String type) { this.type=type; }
 
     // Getters i Setters
}

Item.hbm.xml

<hibernate-mapping>
  <class name="Item" table="Item">
    <meta attribute="class-description">AB</meta>
    <id name="itemId" type="long" column="id">
    <generator class="native"/>
  </id>
  <property name="name" column="name" type="string"/>
  <many-to-one name="cart" class="Cart" fetch="select">
   <column name="cartId" not-null="false" />
  </many-to-one>  
 </class>
</hibernate-mapping>

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<Item> 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

<hibernate-mapping>
 <class name="Contact" table="Contact">
  <meta attribute="class-description">AB</meta>
   <id name="contactId" type="long" column="id">
     <generator class="identity"/>
   </id>
   <property name="name" column="name" type="string"/>
   <property name="email" column="email" type="string"/>
    <set name="employees" table="Employee_Contact" cascade="save-update">
     <key column="contacts_id"/>
     <many-to-many column="employees_id" class="Employee"/>
    </set>
  </class>
</hibernate-mapping>

Contact.java

public class Contact implements Serializable {
   private long contactId;
   private String name;
   private String email;
   private Set<Employee> employees; 
 
   public Contact() { }
   public Contact(String name, String email) {
       this.name=name;
       this.email=email;
   }
     // Getters i Setters
}

Employee.hbm.xml

<hibernate-mapping>
 <class name="Employee" table="Employee">
  <meta attribute="class-description">AB</meta>
   <id name="employeeId" type="long" column="id">
     <generator class="identity"/>
   </id>
   <property name="firstName" column="firstName" type="string"/>
    <property name="lastName" column="lastName" type="string"/>
    <property name="salary" column="salary" type="int"/>
    <set name="contacts" table="Employee_Contact" inverse="true">
      <key column="employees_id"/>
      <many-to-many column="contacts_id" class="Contact" />
     </set>
  </class>
</hibernate-mapping>

Employee.java

public class Employee implements Serializable {
     private long employeeId;
     private String firstName;
     private String lastName;  
     private int salary; 
     private Set<Contact> 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<Employee> 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<Contact> 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