El framework Hibernate es una herramienta de Mapeo objeto-relacional (ORM) para la plataforma Java ( NHibernate para la plataforma .Net) empleando para ello JPA (Java Persistence API). Esta herramienta nos permite manipular los datos del DBMS operando sobre objetos de nuestra aplicación, siendo éstos persistentes y cumpliendo las características de la OOP.
Para utilizar dicho framework, lo primero que hemos de hacer es descargarnos obviamente las API´s correspondientes (
Descargar núcleo de Hibernate). Los ficheros de configuración estas basados en
XML , pero si queremos deshacernos de ello y usar
anotaciones, además, hemos de descargarnos la siguiente API:
Descargar anotaciones de Hibernate.
Descomprimimos el fichero, y comprobamos que por defecto, este ya trae un ejemplo ya hecho. Lo único que nos interesa son los .jars y los ficheros de configuración.
Creamos un nuevo proyecto java en el eclipse y una vez creado, creamos dos sources folders:
src en donde colocaremos nuestros códigos fuentes.
test para realizar las pruebas, ya que aplicaremos TDD.
A continuación, creamos un nuevo directorio llamado lib, en donde colocaremos nuestras librerías, siendo en este caso los .jars de Hibernate (estos son los contenidos en el directorio lib del paquete extraido y el .jar del directorio raíz de éste). También hemos de incluir obligatoriamente en lib el driver JDBC según la base de datos con la que vayamos a trabajar (MySQL según ejemplo). Opcionalmente, pero recomendable, pondremos las librerías EasyMock, TestNG... Luego refrecamos nuestro proyecto y añadimos todas las librerías al build path.
En cuanto a los ficheros de configuración, copiamos los ficheros hibernate.cfg.xml y log4j.properties ubicados en doc\tutorial\src y lo pondremos en nuestro source folder src. Por mayor comodidad, usaremos ant en nuestro proyecto, así que copiaremos build.xml del directorio doc\tutorial y lo pondremos en el raiz de nuestro proyecto.Configuración de Hibernate (hibernate.cfg.xml) para MySQL :
<?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>
<!-- API´s del driver JDBC--> <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- URL de la base de datos, junto parámetros correspondientes --> <property name="connection.url"> jdbc:mysql://localhost/myhibernate?createDatabaseIfNotExist=true </property>
<!-- Usuario y contraseña de la base de datos --> <property name="connection.username">root</property> <property name="connection.password">root</property>
<!-- Carga de transacciones, relacionado con el rendimiento de la BB.DD. --> <property name="connection.pool_size">1</property>
<!-- Versión de la BB.DD. En este ejemplo,versión 5 de MySQL--> <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- Gestión automática de las sesiones de Hibernate a través de hilos--> <property name="current_session_context_class">thread</property>
<!-- Caché de los datos ya extraidos de la BB.DD. --> <property name="cache.provider_class"> org.hibernate.cache.NoCacheProvider </property>
<!-- Muestra sentencias SQL por la consola --> <property name="show_sql">true</property>
<!-- Relacionado con el arranque. También podría ser create-drop(borra antes el catálogo) --> <property name="hbm2ddl.auto">create</property>
<!-- Mapeo de objetos persistentes --> <mapping resource="Ruta/Fichero.hbm.xml"/>
</session-factory>
</hibernate-configuration> |
Si tienes algún problema a la hora de configurar el driver JDBC , tal vez te sirva de ayuda este fichero (No abrir con bloc de notas).
En el fichero log4j.xml, podemos configurar el tipo de salida que muestre por la consola (recordamos que los principales son, de mayor prioridad a la de menos: DEBUG, INFO, WARN y ERROR).
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%p - %C{1}.%M(%L)%m%n"/> </layout>
</appender>
<logger name="net.sf.ehcache"> <level value="ERROR"/> </logger> <logger name="org.apache"> <level value="WARN"/> </logger>
<logger name="org.hibernate"> <level value="INFO"/> </logger>
<!-- Suppress warnings from Commons Validator --> <logger name="org.apache.commons.validator.ValidatorResources"> <level value="ERROR"/> </logger> <!-- Suppress invalid warning messages from JSF --> <logger name="org.apache.myfaces.shared_impl.renderkit.html"> <level value="ERROR"/> </logger>
<logger name="nombre.paquete"> <level value="INFO"/> </logger>
<root> <level value="WARN"/> <appender-ref ref="CONSOLE"/> </root> </log4j:configuration> |
Todo lo comentado, ha sido relacionado con la instalación y configuración de hibernate, ya va siendo hora de ir al grano ....
Toda aplicación, necesita un único objeto perteneciente a la clase SessionFactory, que como su nombre indica se trata de una fábrica de sesiones. Luego, le pediremos sesiones a éste, mediante los cuales, podremos hacer transacciones con la base de datos. Veamos un ejemplo de como se crea :
package es.nuwi.hotel.util; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil {
public static SessionFactory getSessionFactory() {
SessionFactory sessionFactory; try {
// Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure() .buildSessionFactory();
} catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Error al crear la factoría de sesiones." + ex); throw new ExceptionInInitializerError(ex); } return sessionFactory; }
}
|
El ejemplo que usaremos tendrá la siguientes asociaciones-relaciones:
Como observamos un hotel va a tener n empleados y n habitaciones. Cada empleado puede trabajar en más de un hotel. Una habitación pertenece a un hotel y suponemos que en una habitacion sólo va a ver un único cliente. El cliente solo podrá alquilar una única habitación. En nuestro ejemplo, usaremos asociaciones bidireccionales en vez de unidireccionales.
Creamos los POJOS, los cuales serán objetos persistentes. Éstos han de llevar obligatoriamente una propiedad de tipo Long :
Fichero Hotel.java :
package es.nuwi.hotel.objpersistentes;
import java.util.HashSet; import java.util.Set;
public class Hotel {
private Long id; private String nombreHotel; private Set<Empleados> empleados=new HashSet<Empleados>(); private Set<Habitacion> habitaciones=new HashSet<Habitacion>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNombreHotel() { return nombreHotel; }
public void setNombreHotel(String nombre) { this.nombreHotel = nombre; }
public Set<Empleados> getEmpleados() { return empleados; }
public void setEmpleados(Set<Empleados> empleados) { this.empleados = empleados; }
public Set<Habitacion> getHabitaciones() { return habitaciones; }
public void setHabitaciones(Set<Habitacion> habitaciones) { this.habitaciones = habitaciones; }
}
|
Fichero Empleado.java :
package es.nuwi.hotel.objpersistentes; import java.util.Set;
public class Empleado {
private Long id; private String dniEmpleado; private Set<Hotel> hoteles; public String getDniEmpleado() { return dniEmpleado; }
public void setDniEmpleado(String dniEmpleado) { this.dniEmpleado = dniEmpleado; } public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNombreEmpleado() { return dniEmpleado; }
public void setNombreEmpleado(String nombreEmpleado) { this.dniEmpleado = nombreEmpleado; }
public Set<Hotel> getHoteles() { return hoteles; }
public void setHoteles(Set<Hotel> hoteles) { this.hoteles = hoteles; }
}
|
Fichero Habitacion.java :
package es.nuwi.hotel.objpersistentes; public class Habitacion {
private Long id; private Hotel hotel; private CLiente cliente;
public CLiente getCliente() { return cliente; }
public void setCliente(CLiente cliente) { this.cliente = cliente; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Hotel getHotel() { return hotel; }
public void setHotel(Hotel hotel) { this.hotel = hotel; }
}
|
Fichero Cliente.java :
package es.nuwi.hotel.objpersistentes; import java.util.Calendar;
public class CLiente {
private Long id; private String dniCliente; Calendar fechaAlquiler; Habitacion habitacion; public Habitacion getHabitacion() { return habitacion;
} public void setHabitacion(Habitacion habitacion) { this.habitacion = habitacion;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getDniCliente() { return dniCliente; }
public void setDniCliente(String dniCliente) { this.dniCliente = dniCliente; }
public Calendar getFechaAlquiler() { return fechaAlquiler; }
public void setFechaAlquiler(Calendar fechaAlquiler) { this.fechaAlquiler = fechaAlquiler; } }
|
Configuramos las asociaciones antes mencionadas. Para tener una cierta concordancia, los nombres de estos ficheros coinciden con los de la clase y cuya extensión es .xbm.xml, aunque no tiene siempre porque ya que en un mismo fichero pueden asociarse varias clases y en cuanto a la extensión, ésta puede ser cambiada. Para que no de ningún error, la propiedad tipo Long ha de ser la primera :
Fichero Hotel.hbm.xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping> <class name="es.nuwi.hotel.objpersistentes.Hotel" table="Hotel"> <id name="id" column="Hotel_id" unsaved-value="null"> <generator class="native" /> </id> <property name="nombreHotel" column="nombreHotel" not-null="true"/> <!-- ONE-TO-MANY --> <set name="habitaciones" cascade="all" > <key column="hotel_id"/> <one-to-many class="es.nuwi.hotel.objpersistentes.Habitacion"/> </set> <!-- MANY-TO-MANY. SE CREA UNA TABLA INTERMEDIA --> <set name="empleados" cascade="all" table="empleados_hoteles"> <key column="Hotel_id"/> <many-to-many class="es.nuwi.hotel.objpersistentes.Empleado" column="Empleado_id" /> </set> </class> </hibernate-mapping> |
Fichero Empleado.hbm.xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="es.nuwi.hotel.objpersistentes.Empleado" table="Empleados"> <id name="id" column="Empleado_id" unsaved-value="null"> <generator class="native" /> </id> <property name="dniEmpleado" column="dniEmpleado" not-null="true"/> <!-- MANY-TO-MANY. SE CREA UNA TABLA INTERMEDIA, ES LA MISMA QUE LA DE ANTES. --> <set name="hoteles" cascade="all" table="empleados_hoteles"> <key column="Empleado_id"/> <many-to-many class="es.nuwi.hotel.objpersistentes.Hotel" column="Hotel_id" /> </set> </class> </hibernate-mapping> |
Fichero Habitacion.hbm.xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping> <class name="es.nuwi.hotel.objpersistentes.Habitacion" table="Habitacion"> <id name="id" column="Habitacion_id" unsaved-value="null"> <generator class="native" /> </id> <!-- MANY-TO-ONE --> <many-to-one name="hotel" column="hotel_id" class="es.nuwi.hotel.objpersistentes.Hotel" /> <!-- ONE-TO-ONE --> <one-to-one name="cliente" property-ref="habitacion" /> </class> </hibernate-mapping> |
Fichero Cliente.hbm.xml :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping> <class name="es.nuwi.hotel.objpersistentes.Cliente" table="Clientes"> <id name="id" column="Cliente_id" unsaved-value="null"> <generator class="native" /> </id> <property name="dniCliente" column="dniCliente" not-null="true" /> <property name="fechaAlquiler" type="java.util.Date" column="fechaAlquiler" not-null="true" /> <!-- MANY-TO-ONE, AUNQUE REALMENTE VIENE DE UN ONE-TO-ONE --> <many-to-one name="habitacion" column="HabitacionCliente" unique="true" not-null="true"/> </class> </hibernate-mapping> |
Estas configuraciones han sido sacadas de la siguiente URL: http://www.hibernate.org/hib_docs/reference/en/html/associations.html.Veamos el reflejo del ejemplo sobre el DBMS:
Al crear una relación MANY-TO-MANY se ha creado una nueva tabla ( empleados-hoteles ) con dos campos primarios (hotel_id y empleado_id) los cuales son además claves ajenas (foreign key) de las tablas hotel y empleado respectivamente. Recordamos que una clave ajena apunta a los campos primarios de otra tabla.
Al hacer uso de un ONE-TO-MANY y MANY-TO-ONE, de este segundo (many-to-one, es decir, de la tabla habitacion) se crea una columna sienda esta clave ajena de la clave primaria de la primera ( one-to-many, es decir,de la tabla hotel ) .
En cuanto a ONE-TO-ONE , es exactamente igual igual que el anterior, con la única diferencia de que la clave ajena es además unique (no admite valores repetidos para esa columna). Según el ejemplo, se crea un nuevo campo de la tabla cliente la cual referencia a la clave primaria de la tabla habitacion siendo además único para esta columna.
Por último, lo único que nos queda es mapear los objetos persistentes. Para ello, añadimos las siguientes entradas en el fichero hibernate.cfg.xml, siendo estas entradas la ruta más el nombre en la que se encuentran los ficheros .hbm.xml de nuestro proyecto :
<mapping resource="es/nuwi/hotel/objpersistentes/Hotel.hbm.xml"/> <mapping resource="es/nuwi/hotel/objpersistentes/Empleado.hbm.xml"/> <mapping resource="es/nuwi/hotel/objpersistentes/Cliente.hbm.xml"/> <mapping resource="es/ " / " /objpersistentes/Habitacion.hbm.xml"/> |
Otra característica de este framework, es que podemos crear un fichero llamado import.sql ( ha de llamarse así obligatoriamente ) el cual contiene sentencias SQL (DDL,LMD,...). Esto es de gran utilidad ya que podemos insertar tuplas en nuestro DBMS y hacer pruebas para ver el funcionamiento de nuestra aplicación. En nuestro ejemplo, insertaremos nuevas tuplas :
insert into Hotel(Hotel_id,nombreHotel) values (1,'Hotel LG') insert into Hotel(Hotel_id,nombreHotel) values (2,'Hotel Nuwi') insert into Empleados (Empleado_id,dniEmpleado) values (1,'Mawi') insert into Empleados (Empleado_id,dniEmpleado) values (2,'Ana') insert into Empleados (Empleado_id,dniEmpleado) values (3, 'María') insert into Empleados_Hoteles (Hotel_id,Empleado_id) values (1,1) insert into Empleados_Hoteles (Hotel_id,Empleado_id) values (1,2) insert into Empleados_Hoteles (Hotel_id,Empleado_id) values (2,3) insert into Habitacion (Habitacion_id,Hotel_id) values (1,1) insert into Habitacion (Habitacion_id,Hotel_id) values (2,1) insert into Habitacion (Habitacion_id,Hotel_id) values (3,2) insert into Clientes (Cliente_id,dniCliente,fechaAlquiler,HabitacionCliente) values (1,'28765342H','2008-1-19 11:15',1) insert into Clientes (Cliente_id,dniCliente,fechaAlquiler,HabitacionCliente) values (2,'49382718L','2008-2-21 14:45',2) insert into Clientes (Cliente_id,dniCliente,fechaAlquiler,HabitacionCliente) values (3,'27272129P','2008-4-19 19:30',3) |
Hibenate soporta 3 tipos de herencia:
- Tabla para clase padre e hijas (se admiten valores nulos).
- Tabla por cada subclase (no se admiten valores nulos).
- Tabla por cada clase concreta (No abstracta), tanto padre como hijas.(no valores nulos).
He aquí un ejemplo del primer caso: La clases Cliente tendrá dos subclases, los que pagen en efectivo, donde guardaremos el importe y los que pagen por tarjeta de crédito, donde guardaremos el número de dicha tarjeta :
Creamos los POJO´s de las dos subclases:
Fichero ClienteEfectivo :
package es.nuwi.hotel.objpersistentes;
public class ClienteEfectivo extends Cliente{ private int cantidadEfectivo;
public int getCantidadEfectivo() { return cantidadEfectivo; }
public void setCantidadEfectivo(int efectivo) { this.cantidadEfectivo = efectivo; } } |
Fichero ClienteTarjeta:
package es.nuwi.hotel.objpersistentes;
public class ClienteTarjeta extends Cliente{ private String numeroTarjeta;
public String getNumeroTarjeta() { return numeroTarjeta; }
public void setNumeroTarjeta(String numeroTarjeta) { this.numeroTarjeta = numeroTarjeta; } } |
Modificamos el fichero Cliente.hbm.xml. Para ello, es necesario crear una nueva columna en la tabla Cliente (discriminador tipoCliente, éste no esta reflejado en ningún campo de la clase), situada obligatoriamente como segunda posición para que no de ningún error (recordamos que la primera es el Primary Key de tipo Long). Dicho discriminador ha de ser de tipos String y no puede contener valores nulos. Luego se especifican los nuevos campos de las subclases pudiendo éstos contener valores nulos y el valor del discriminador que van a usar. El fichero quedaría de la siguiente manera :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping> <class name="es.nuwi.hotel.objpersistentes.Cliente" table="Clientes"> <id name="id" column="Cliente_id" unsaved-value="null"> <generator class="native" /> </id> <!-- DISCRIMINADOR--> <discriminator column="tipocliente" not-null="true" type="string"/> <!--Resto de propiedades de la clase madre --> <property name="dniCliente" column="dniCliente" not-null="true"/> <property name="fechaAlquiler" type="java.util.Date" column="fechaAlquiler" not-ull="true"/> <!-- MANY-TO-ONE, AUNQUE REALMENTE VIENE DE UN ONE-TO-ONE --> <many-to-one name="habitacion" column="HabitacionCliente" unique="true" not-null="true"/> <!-- ESPECIFICAMOS LAS SUBCLASES --> <subclass name="es.nuwi.hotel.objpersistentes.ClienteEfectivo" discriminator-value="Efectivo"> <property name="cantidadEfectivo" column="cantidadEfectivo" not-null="false"/> </subclass> <subclass name="es.nuwi.hotel.objpersistentes.ClienteTarjeta" discriminator-value="Tarjeta"> <property name="numeroTarjeta" column="numeroTarjeta" not-null="false"/> </subclass> </class> </hibernate-mapping>
|
Como habrás observado, no hemos creado ningún .hbm.xml para cada subclase ya que lo hemos especificado en el de la clase madre. Será este el que esté mapeado en hibernate.cfg.xml.
Si tienes dudas, tal vez este foro te sirva de ayuda.
No hay comentarios:
Publicar un comentario