Upload
zaka3000
View
3.851
Download
1
Embed Size (px)
DESCRIPTION
Citation preview
HIBERNATE
[2]
HIBERNATE
ÍNDICE
1 INTRODUCCIÓN .................................................................................................................... 3
1.1 Object-Relational Mapping.......................................................................................... 3
1.2 Hibernate y MVC en entornos web ............................................................................. 5
2 MI PRIMERA APLICACIÓN CON HIBERNATE ......................................................................... 7
2.1 Pasos para crear una aplicación Hibernate ................................................................. 7
2.2 Desarrollo paso a paso................................................................................................. 8
3 FRAMEWORK HIBERNATE .................................................................................................. 38
3.1 Introducción............................................................................................................... 38
3.2 Instalación.................................................................................................................. 39
3.3 Configuración............................................................................................................. 39
3.4 El interfaz Session ...................................................................................................... 58
4 ASOCIACIONES.................................................................................................................... 62
4.1 Relaciones en Bases de datos .................................................................................... 63
4.2 Many-to-one .............................................................................................................. 65
4.3 Many-to-many ........................................................................................................... 67
4.4 One-to-one................................................................................................................. 69
5 RECUPERACIÓN DE DATOS ................................................................................................. 71
5.1 HQL ............................................................................................................................ 73
5.2 Criteria ....................................................................................................................... 83
5.3 SQL ............................................................................................................................. 89
[3]
HIBERNATE
1. INTRODUCCIÓN
1.1. OBJECT-RELATIONAL MAPPING
Hibernate es una implementación de mapeo Objeto-Relacional (ORM – Object-Relational
Mapping). El objetivo de un ORM es hacer corresponder el modelo de datos de la aplicación
con el modelo de clases de la misma, los cuales no son tan distintos como se pueda pensar.
Para ello, Hibernate realiza el mapeo de elementos (tablas, columnas) entre una Base de datos
relacional y objetos de la aplicación en cuestión:
Base de Datos Relacional
Juan
Ana
Francisco
Belén
López
Hurtado
Soria
Jiménez
666111122
959121212
889121212
789456123
Nombre Apellido Teléfono
TABLA Persona
Persona
-String nombre
-String apellido
-String teléfono
Modelo de Objetos
instance: Persona
nombre = Juan
apellido = López
teléfono = 666111122
[4]
HIBERNATE
Este tipo de sistema facilita todas las operaciones de persistencia y acceso a los datos desde la
aplicación al presentar las siguientes características principales:
� Independiente de SQL: Hibernate incorpora funcionalidades para las operaciones
simples de recuperación y actualización de datos para las cuales no será necesario
utilizar las sentencias SELECT, INSERT, UPDATE o DELETE habituales, siendo sustituidas
por llamadas a métodos (list, save, update, delete, …). Hibernate se encarga de
generar la sentencia SQL que se encargará de realizar tales operaciones. De cualquier
modo, se pueden especificar consultas SQL en caso necesario o incluso consultas en el
dialecto de Hibernate, el HQL, con sintaxis similar a SQL pero que establece una serie
de elementos propios del sistema ORM que gestiona.
� Independiente del SGBD: al aislar las funciones de manipulación de datos del lenguaje
SQL, se consigue que la aplicación pueda comunicarse con cualquier SGBD ya que no
existirán dependencias o particularidades en las sentencias de consulta o actualización
de datos que harían a la aplicación dependiente de un SGBD en cuestión. El encargado
de realizar la traducción final entre las operaciones Objeto-relacionales y las
sentencias SQL es Hibernate, con lo cual el problema de la compatibilidad queda
solventado. Incorpora soporte para la mayoría de los sistemas de bases de datos
existentes.
� Independiente de JDBC: Hibernate contiene una API completa que aísla a la aplicación,
no solamente de las operaciones con lenguaje SQL, sino también la utilización de
objetos propios de la gestión de Bases de datos JDBC (Statement, ResultSet, …). Este
tipo de objetos habituales en la programación Java de acceso a Bases de datos, se
sustituye por el uso de objetos mucho más sencillos como colecciones de clases tipo
JavaBeans que contendrán los datos de los almacenes de datos.
[5]
HIBERNATE
1.2. HIBERNATE Y MVC EN ENTORNOS WEB
En una arquitectura MVC genérica como la siguiente, Hibernate juega un papel primordial
como capa de persistencia de la aplicación. Como aparece en el esquema, los JavaBeans
encargados de ejecutar la lógica de la aplicación interacturán con datos habitualmente
almacenados en una Base de datos. Hibernate se introduce en este contexto para dar soporte
de acceso y gestión de los datos a la capa de Modelo.
El patrón MVC se utiliza de forma amplia en el desarrollo de aplicaciones web.
Fuente: http://java.sun.com/blueprints/patterns/MVC-detailed.html
[6]
HIBERNATE
Controller (Controlador):
� Servlet central que recibe las peticiones, procesa la URL recibida y delega el
procesamiento a los JavaBeans.
� Servlet que almacena el resultado del procesamiento realizado por los JavaBeans en el
contexto de la petición, la sesión o la aplicación.
� Servlet que transfiere el control a un JSP que lleva a cabo la presentación de
resultados.
Model (Modelo):
� JavaBeans (o EJBs para aplicaciones más escalables) que desempeñan el rol de modelo:
� Algunos beans ejecutan lógica.
� Otros guardan datos.
� Normalmente:
� El Servlet Controller invoca un método en un bean de lógica y éste devuelve un
bean de datos.
� El programador del JSP tiene acceso a beans de datos.
View (Vista):
� Rol ejecutado por JSPs.
� El Servlet Controller transfiere el control al JSP después de guardar en un contexto el
resultado en forma de un bean de datos.
� JSP usa jsp:useBean y jsp:getProperty para recuperar datos y formatear respuesta en
HTML o XML.
En resumen:
� Los JavaBeans o EJBs ejecutan la lógica de negocio y guardan los resultados.
� Los JSPs proporcionan la información formateada.
� Los servlets coordinan/controlan la ejecución de los beans y los JSPs.
[7]
HIBERNATE
2. MI PRIMERA APLICACIÓN CON HIBERNATE
Esta parte del manual es una guía paso a paso para realizar una aplicación sencilla, es tal vez la
mejor manera de introducirse con Hibernate y es recomendable realizarla antes de leer el
resto del manual ya que mejorará la comprensión de éste.
Esta aplicación será desarrollada haciendo uso del IDE Netbeans 6.7. Se puede descargar de
http://netbeans.org/.
2.2. PASOS PARA CREAR UNA APLICACIÓN HIBERNATE
Estos son los pasos mínimos que se deberán seguir para desarrollar la aplicación
satisfactoriamente.
1. Determinar el modelo de datos de la aplicación.
2. Añadir las librerías Java de Hibernate.
3. Definir las clases de persistencia.
4. Crear los ficheros de mapeo.
5. Configurar Hibernate.
6. Crear clases de ayuda.
7. Cargar y guardar objetos.
8. Ejecutar la primera versión.
9. Asociar clases.
10. Ejecutar la aplicación.
[8]
HIBERNATE
2.3. DESARROLLO PASO A PASO
1. Determinar el modelo de datos de la aplicación.
Este ejemplo se trata de una sencilla introducción donde únicamente se dispone de
dos clases Event y Person tanto en la base de datos del sistema como en el modelo de
objetos de la aplicación. También se usarán clases auxiliares para la gestión del acceso
y operaciones con los datos.
En primer lugar, es necesario crear la Base de datos en MySQL. Su nombre será
FirstApp pero no se van a crear tablas en la misma ya que se configurará una opción en
el fichero de configuración de Hibernate (hibernate.cfg.xml) para que genere el
esquema de la Base de datos al desplegar la aplicación.
Como se ha comentado, la aplicación se creará a través de Netbeans. Este entorno de
desarrollo dispone de herramientas para la configuración del acceso a la Base de
datos.
En primer lugar es posible que sea necesario configurar el driver de la base de datos
que se va a utilizar en la aplicación, en este caso MySQL. Para ello es necesario
seleccionar la opción New Connection… del menú del botón derecho del ratón sobre la
opción Drivers.
A continuación se selecciona el fichero .jar que contiene el driver de MySQL y se dejan
el resto de campos con los valores que aparecen al seleccionarlo. Todo lo relacionado
[9]
HIBERNATE
con la gestión de Drivers NO es propio de Hibernate, se está configurando el entorno
de Netbeans para la utilización de los Asistentes existentes.
Ahora hay que crear la conexión desde la pestaña Services seleccionando la opción
New Connection… del menú del botón derecho del ratón sobre la opción Databases.
[10]
HIBERNATE
Los datos que se completan son los correspondientes a la conexión con la base de
datos que se encuentra en el servidor MySQL.
2. Añadir las librerías Java de Hibernate.
Netbeans dispone de un asistente para la creación de aplicaciones Web donde se
puede indicar que la aplicación empleará Hibernate. De esta forma, Netbeans realiza
una serie de acciones de forma automática que ayudarán al desarrollo mediante este
Framework.
Se deberá crear un nuevo proyecto web con Netbeans, para ello dirigirse al menú: File
� New Project � Web � Web Application. No se debe olvidar añadir el Framework,
como muestran las imágenes siguientes.
[11]
HIBERNATE
Al crear el proyecto, Netbeans crea un fichero hibernate.cfg con los valores para conectar
con la Base de datos que se ha establecido al crear el proyecto. Dicho archivo se encuentra
en la carpeta Source Packages\default package de la aplicación.
[12]
HIBERNATE
3. Definir las clases de persistencia.
El paso siguiente es añadir clases para representar a los elementos de la Base de datos.
La clase Event (paquete events) es una clase tipo JavaBean sencilla, cuyas propiedades
coinciden con los campos de la tabla Events que existirá en la Base de datos.
package events;
import java.util.Date;
public class Event { private Long id; private String title; private Date date;
public Event() { }
public Long getId() {
return id; }
private void setId(Long id) {
this.id = id; }
public Date getDate() {
return date; }
public void setDate(Date date) {
this.date = date; }
public String getTitle() {
return title; }
public void setTitle(String title) {
this.title = title; }
}
La propiedad id representa el identificador único para un evento. Todas las clases de
persistencia deben tener un identificador si se quiere utilizar el conjunto completo de
[13]
HIBERNATE
funcionalidades de Hibernate. El constructor sin argumentos también es un requisito
para todas las clases persistentes.
Se recomienda en la mayoría de las ocasiones utilizar clases para representar los tipos
de los atributos en lugar de utilizar tipos básicos, debido a que para aquellos campos
que se permitan valores nulos en la Base de datos, la representación del valor del
campo será natural en la representación Java (null), mientras que con tipos básicos
podría resultar en un error.
En el paquete events también se introducirá la clase Person, la cual se representará de
forma similar por el momento:
package events; public class Person {
private Long id; private int age; private String firstname; private String lastname;
public Person() { }
// getters y setters para todas las propiedades
}
4. Crear los ficheros de mapeo.
Para cada una de las clases de persistencia tiene que existir un fichero de mapeo que se
encarga de definir la relación entre las tablas y columnas de éstas en la Base de datos, y las
clases y sus propiedades en el modelo de objetos.
En Netbeans existe una utilidad para mapear las clases con las tablas de la Base de datos
de forma automática. Para ello, seleccionar la opción New � Other � Hibernate �
Hibernate Mapping File
[14]
HIBERNATE
El nombre del fichero (Event.hbm.xml) coincidirá con el nombre de la clase que mapea
y se guardará habitualmente en el mismo paquete donde se encuentra dicha clase.
[15]
HIBERNATE
Solamente queda seleccionar la clase y la tabla a mapear. En este caso no se puede
especificar ninguna tabla ya que la Base de datos se encuentra vacía.
Al pulsar Finish se genera un archivo de mapeo, al cual es necesario añadir el nombre
de la tabla que está mapeando, y completar con los datos del mapeo de las columnas.
<?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="events.Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping>
[16]
HIBERNATE
Algunos detalles sobre este archivo:
<class name="events.Event" table="EVENTS">
Aunque inicialmente el archive se crea con una serie de propiedades para el
mapeo (dynamic-insert="false" dynamic-update="false" …) se han eliminado
para simplificar el ejemplo. Realmente las únicas propiedades imprescindibles
son name (nombre de la clase) y table (nombre de la tabla).
<id name="id" column="EVENT_ID">
En primer lugar se declara el identificador. Esta definición se destaca porque se
identifica con la etiqueta id. La propiedad name representa el nombre del
atributo en la clase, y la propiedad column el nombre del campo en la tabla.
<generator class="native"/>
Dentro del identificador se declara cómo se gestiona la asignación de ésta. En
este caso se ha elegido el valor native, que delega la gestión de la clave en el
SGBD utilizado, es decir, que no es necesario preocuparse de la
gestión/asignación de claves de forma manual. Existen otras posibilidades de
generadores de clave que se estudiarán más adelante en este documento.
[17]
HIBERNATE
<property name="date" type="timestamp" column="EVENT_DATE"/>
La siguiente propiedad en declararse es date. En este caso también name
representa el nombre del atributo en la clase, y column el nombre del campo
en la tabla. La propiedad type representa el tipo de datos que se va a utilizar,
ya que aunque Hibernate es capaz de detectar que este campo se refiere a un
valor de tipo Date, es necesario especificar si esa fecha se gestionará como
time, date o timestamp. Esta propiedad type sirve para realizar conversiones
de tipos si es necesario en la aplicación, de forma que un campo que en la base
de datos es numérico se trate como String, o Boolean, …
<property name="title"/>
Por último se declara el campo title. En este caso solamente se especifica el
nombre del atributo de la clase porque el nombre coincide con el nombre de la
columna de la tabla de Base de datos, si no sería necesario especificar el
nombre de dicha columna.
El fichero de mapeo para la clase Person quedará de la siguiente forma:
<?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="events.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
</hibernate-mapping>
[18]
HIBERNATE
5. Configurar Hibernate.
Hasta el momento se han creado las clases para representar los objetos de la Base de
datos y los ficheros de mapeo para las mismas. Para que la aplicación funcione de
forma correcta, es necesario configurar Hibernate a través de su fichero de
configuración hibernate.cfg.xml.
En este fichero se establecerá al menos la conexión con la base de datos, y se
especificarán cuáles son los ficheros de mapeo que se están empleando en la
aplicación, es decir, que si un fichero de mapeo no aparece en hibernate.cfg el mapeo
no funcionará.
El fichero hibernate.cfg se encuentra en el paquete por defecto de la aplicación. Si se
abre este fichero con Netbeans, en primer lugar aparece la vista Diseño. Al seleccionar
la vista XML (esquina superior izquierda de la ventana) aparecerá el código que
Netbeans introdujo por defecto al crear la aplicación y especificar la conexión a la Base
de datos que se iba a emplear en la misma:
<?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 name="session1">
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/firstapp
</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>
</session-factory>
</hibernate-configuration>
[19]
HIBERNATE
<session-factory name="session1">
Indica el session factory utilizado en la aplicación. Si la aplicación accede a más de una
Base de datos es necesario declarar más de un session-factory, cada uno de ellos
identificado por un nombre distinto.
<property name="hibernate.dialect">
La propiedad dialect indica la variante de SQL que Hibernate genera a la hora de
acceder a la Base de datos.
Además de los datos de la conexión a la Base de datos y los ficheros de mapeo de la
aplicación (que Netbeans ha introducido de forma automática al crear los propios ficheros
hbm), en hibernate.cfg deben aparecer propiedades importantes que definen los
siguientes aspectos:
� current_session_context_class: Hibernate funciona con sesiones para acceder a los
datos. Es necesario definir una clase de gestión de sesiones para poder realizar
operaciones con la Base de datos.
� cache.provider_class: especifica el comportamiento de la Caché de 2º nivel.
� show_sql: mostrar en los logs las sentencias SQL que Hibernate lanza cada vez que
realiza una operación con la Base de datos.
� hbm2ddl.auto: crear el esquema de la Base de datos de forma automática al desplegar
la aplicación.
Los valores a añadir a hibernate.cfg para el ejemplo son los siguientes:
<!-- 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>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
[20]
HIBERNATE
6. Crear clases de ayuda.
Una vez que se ha configurado Hibernate, ya se puede escribir el código Java para el
acceso a los datos.
Como se ha comentado anteriormente, Hibernate utiliza el concepto de sesión para
acceder a los datos, de forma que cualquier operación de consulta o manipulación de
los datos debe estar enmarcada en una sesión.
Para que la gestión de sesiones quede aislada del resto de operaciones de acceso a
datos, y para evitar la duplicidad de código, es habitual generar una clase de ayuda que
se encarga de la inicialización de sesiones.
Netbeans permite la generación de esta clase a través del menú New� Other �
Hibernate � HibernateUtil. En este caso la clase HibernateUtil se incorporará al
paquete util.
[21]
HIBERNATE
package util;
import org.hibernate.cfg.Configuration;
import org.hibernate.SessionFactory;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory from hibernate.cfg.xml
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Log the exception.
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
[22]
HIBERNATE
Cuando una clase de la aplicación quiera acceder a la sesión actual, simplemente
invocará al método estático getSessionFactory() (HibernateUtil.getSessionFactory()).
7. Cargar y guardar objetos.
Para manipular los datos de la Base de datos se va a crear una clase EventManager en
el paquete event para el almacenamiento y recuperación de eventos:
public class EventManager {
public void createAndStoreEvent(String title, Date theDate) {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Crear el evento
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
//Almacenarlo
session.save(theEvent);
//Confirmar transacción
session.getTransaction().commit();
}
public List listEvents() {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Obtener la lista de eventos
List result = session.createQuery("from Event").list();
//Confirmar transacción
session.getTransaction().commit();
return result;
}
}
[23]
HIBERNATE
Los métodos que se han definido son los siguientes, los cuales se describen para
comprobar la sencillez de las acciones que realizan:
� createAndStoreEvent: crea y almacena un evento realizando los siguientes pasos:
1. Acceder a la sesión actual: a través del método estático getSessionFactory() de la
clase HibernateUtil creada anteriormente en el proyecto.
2. Abrir una transacción: utilizando la sesión actual.
3. Crear el objeto: de la forma habitual en Java.
4. Almacenar el objeto: simplemente invocando al método save del interfaz Session
(sesión actual).
5. Confirmar la transacción: utilizando también la sesión actual.
En la siguiente imagen se muestra un esquema del funcionamiento de Hibernate
basado en sesiones:
� listEvents: obtiene una lista de todos los eventos de la Base de datos realizando los
siguientes pasos:
1. Acceder a la sesión actual: a través del método estático getSessionFactory() de la
clase HibernateUtil creada anteriormente en el proyecto.
2. Abrir una transacción: utilizando la sesión actual. Las consultas a bases de datos
también requieren de una transacción en Hibernate.
[24]
HIBERNATE
3. Obtener el conjunto de resultados: se obtiene una colección (List) de instancias de la
clase Event cuyos datos coincidirán con los datos de los registros existentes en la
Base de datos. Existen varias formas de obtener datos de una tabla en Hibernate, en
este caso se ha utilizado un Query HQL. Posteriormente se tratarán en profundidad
los aspectos relacionados con los distintos tipos de consultas en Hibernate.
4. Confirmar la transacción: utilizando también la sesión actual.
8. Ejecutar la primera versión.
Para probar lo que se ha implementado hasta el momento se va a crear una sencilla
aplicación Web.
Para ello en primer lugar se modificará la página index.jsp del proyecto para que
funcione como formulario para introducir los datos:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>JSP Page</title>
</head>
<body>
<form action="Servlet">
<table>
<tr>
<td>title</td><td><input type="text" name="title"></input></td>
</tr>
<tr>
<td>date</td><td><input type="text" name="date"></input></td>
</tr>
<tr>
<td><input type="submit"/></td>
</tr>
</table>
</form>
</body>
</html>
[25]
HIBERNATE
A continuación se crea el Servlet que se encargará de la lógica de la aplicación. Para
crear un Servlet Netbeans también dispone de un Asistente a través de la opción New
� Other � Web � Servlet
Su nombre será Servlet y se almacenará en el paquete servlet. En la siguiente pantalla
se dejan por defecto los parámetros que se añadirán al fichero web.xml.
[26]
HIBERNATE
Para ejecutar la aplicación simplemente usar la opción Run � Run Main Project (F6).
public class Servlet extends HttpServlet { protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try {
EventManager mng = new EventManager(); //Parsear la fecha Date date = new SimpleDateFormat("dd/MM/yyyy").
parse(request.getParameter("date")); mng.createAndStoreEvent(request.getParameter("title"), date);
//Mostrar los datos almacenados this.mostrarTabla(out, mng.listEvents());
} catch (Exception ex) { out.println(ex);
} finally { out.close();
} }
private void mostrarTabla(PrintWriter out, List lista) {
out.println("<table>"); Iterator it = lista.iterator(); //Iterar sobre todos los eventos while (it.hasNext()) {
Event event = (Event) it.next(); out.println("<tr>"); out.println("<td>" + event.getTitle() + "</td>"); out.println("<td>" + event.getDate() + "</td>"); out.println("</tr>");
} out.println("</table>");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response); }
}
[27]
HIBERNATE
9. Asociar clases.
La asociación entre clases es uno de los aspectos más importantes de Hibernate, así
como la asociación entre tablas es la base del modelo relacional de Bases de datos.
Este aspecto puede resultar complejo en algunas ocasiones. Posteriormente se
tratarán en profundidad los aspectos relacionados con los distintos tipos de
asociaciones en Hibernate.
En los pasos 3 y 4 se definieron las clases Event y Person a través de código Java y
ficheros de mapeo. Ahora es necesario modelar la relación que existe entre ambas a
través de una asociación Hibernate.
La relación que existe entre ambas tablas es una relación M a M, de forma que a un
Evento acudirán varias Personas, y una Persona puede acudir a diversos Eventos. A
nivel de Bases de datos, esta relación dará lugar a una tabla intermedia
PERSON_EVENT. Esta relación en Hibernate se puede modelar de diversas maneras, y
además se puede reflejar de distintas formas en las clases implicadas.
En primer lugar, la relación más sencilla que se puede establecer es la relación
Unidireccional entre 2 tablas, en este caso entre las tablas Person y Person_Event. Para
el ejemplo, el tipo de relación escogido es algo complejo, denominado en Hibernate
como relación con tabla intermedia (join table). En el capítulo 4 se explicarán los tipos
de relaciones más generales, y la forma de eludir las relaciones complejas, pero es
bueno presentar en el ejemplo otro tipos de relaciones que pueden encontrarse en
aplicaciones Hibernate.
Las relaciones se modelan con colecciones, normalmente conjuntos (Set) de forma que
se agregan los fragmentos de código que aparecen a continuación. En la clase
simplemente se crea el atributo de la clase Set y se generan los métodos get/set para
el mismo.
[28]
HIBERNATE
private Set events = new HashSet();
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="events.Event"/>
</set>
En Person.hbm.xml se genera una nueva propiedad para la tabla de tipo conjunto. Los
atributos de esa propiedad son los siguientes:
� name: nombre de la propiedad de la clase a la que hace referencia.
� table: tabla intermedia de la base de datos.
� key: clave de la tabla intermedia (PERSON_EVENT) que aporta la clase actual.
La propiedad columna es el nombre de la columna de la tabla de Base de
datos. Es decir, esta etiqueta significa que el nombre del campo que relaciona
a la tabla que modela la clase Person (PERSON) y a la tabla intermedia
(PERSON_EVENT) es PERSON_ID, el cual estará relacionado con el campo id de
la clase Person (en este caso también se denomina PERSON_ID).
� many-to-many: indica para la tabla intermedia (PERSON_EVENT) cuál es la otra
clase que está implicada en la relación (la clase Event) y cuál es la clave
foránea que esta clase aporta a la relación.
Con todo esto únicamente se ha posibilitado que a través de una persona concreta, es
decir, una instancia de la clase Person, se pueda conocer de forma directa los eventos
a los cuales está asociada. En Hibernate esto se traduce en acceder simplemente a los
atributos de dicha clase. Por ejemplo, el método getEvents devuelve un Set con los
eventos asociados a una persona:
[29]
HIBERNATE
Person aPerson = (Person) session.load(Person.class, personId);
aPerson.getEvents();
Esta utilidad es suficiente, pero se pueden realizar cosas más avanzadas, como
manipular objetos persistentes y luego transformarlos en acciones contra la base de
datos. Por ejemplo, el siguiente método asigna un evento a una persona de forma
sencilla, sin gestionar claves ni relaciones entre tablas, simplemente a través de la
relación normal entre conjuntos:
public void addPersonToEvent(Long personId, Long eventId) {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Cargar la persona por clave
Person aPerson = (Person) session.load(Person.class, personId);
//Cargar el evento por clave
Event anEvent = (Event) session.load(Event.class, eventId);
//Agregar el evento a la persona
aPerson.getEvents().add(anEvent);
//Confirmar transacción
session.getTransaction().commit();
}
La línea aPerson.getEvents().add(anEvent) es donde se crea la relación entre ambos
elementos, y al realizar la confirmación en la Base de datos se crean los registros
necesarios para satisfacer dicha relación.
A continuación se va a modelar la relación inversa. Una relación en Hibernate se puede
modelar de forma unidireccional o bidireccional. Para el segundo de los casos, se
establecen en los ficheros de configuración 2 relaciones, por una parte la relación
directa (que corresponde con la que ya se ha generado en el ejemplo), y por la otra
parte se modela la relación inversa. Es decir, si en el ejemplo la relación directa servía
para conocer cuáles eran los eventos a los que estaba asociada una persona, la
relación inversa proporciona información sobre cuáles son las personas que participan
en un determinado evento.
[30]
HIBERNATE
Para ello, en la clase Event se establece también una propiedad de tipo Set que
almacenará los datos de las personas asociadas a un evento concreto:
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}
El mapeo es muy similar al que se estableció para la clase Person, teniendo en cuenta que
hay que incluir la propiedad inverse (con valor true):
� name: nombre de la propiedad de la clase a la que hace referencia.
� table: tabla intermedia de la base de datos.
� inverse: en toda relación bidireccional uno de los 2 extremos debe ser
inverse=true, lo cual es utilizado por Hibernate a la hora de construir las
sentencias SQL para interactuar con la Base de datos.
� key: el nombre del campo que relaciona a la tabla que modela la clase Event
(EVENT) y a la tabla intermedia (PERSON_EVENT) es EVENT_ID, el cual estará
relacionado con el campo id de la clase Event (EVENT_ID).
� many-to-many: indica para la tabla intermedia (PERSON_EVENT) cuál es la otra
clase que está implicada en la relación (la clase Person) y cuál es la clave
foránea que esta clase aporta a la relación (PERSON_ID).
<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="events.Person"/>
</set>
[31]
HIBERNATE
Gracias a esta nueva relación se pueden conocer las personas que pertenecen a un
determinado evento. La pregunta es, ¿funcionará correctamente un método como el
siguiente?
public void addPersonToEvent(Long personId, Long eventId) {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Cargar la persona por clave
Person aPerson = (Person) session.load(Person.class, personId);
//Cargar el evento por clave
Event anEvent = (Event) session.load(Event.class, eventId);
//Agregar la persona al evento
anEvent.getParticipants().add(aPerson);
//Confirmar transacción
session.getTransaction().commit();
}
10. Ejecutar la aplicación.
Para probar todos los conceptos vistos hasta el momento, se crea un nuevo Servlet de
la misma forma especificada en el paso 8, donde se realizarán las siguientes pruebas:
� Cargar eventos por clave (emng.loadEvent(new Long(1))), para comprobar si
existe un evento con identificador igual a 1.
� Almacenar personas (mng.createAndStorePerson(…): almacena una persona
de prueba.
� Agregar personas a eventos (mng.addPersonToEvent(new Long(1), e.getId())):
agrega la persona creada anteriormente al evento.
� Listar participantes de un evento (e.getParticipants()): obtiene la lista de
participantes al evento para mostrar sus nombres y la lista de eventos.
[32]
HIBERNATE
public class ServletP extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
PersonManager mng = new PersonManager();
//Comprobar que existe el evento con id=1
EventManager emng = new EventManager();
Event e = emng.loadEvent(new Long(1));
if (e == null) { //No existe el evento
out.println("Debe crear un evento");
} else {
//Almacenar
mng.createAndStorePerson(new Integer(30), "José", "Guardiola");
//Agregar un evento como prueba (id=1)
mng.addPersonToEvent(new Long(1), e.getId());
//Refrescar los datos
e = emng.loadEvent(new Long(1));
//Mostrar los datos almacenados en el evento
this.mostrarTabla(out, e.getParticipants());
}
} catch (Exception ex) {
out.println(ex);
} finally {
out.close();
}
}
[33]
HIBERNATE
private void mostrarTabla(PrintWriter out, Set lista) {
out.println("<table>");
Iterator it = lista.iterator();
//Iterar sobre todos los eventos
while (it.hasNext()) {
Person p = (Person) it.next();
out.println("<tr>");
out.println("<td>" + p.getAge() + "</td>");
out.println("<td>" + p.getFirstname() + "</td>");
out.println("<td>" + p.getLastname() + "</td>");
out.println("<td>" + p.getEvents() + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}
Ejemplo:
1. En primer lugar se crea un evento.
2. A continuación se pulsa el enlace Prueba relaciones.
3. Tras la ejecución del código del Servlet se muestra la lista de participantes en el evento
inicial. Para el ejemplo, la persona que se almacena como prueba estará al menos en el
evento creado inicialmente.
[34]
HIBERNATE
El código del Servlet resulta sencillo ya que toda la lógica de interacción con Hibernate se
encuentra en las clases PersonManager y EventManager, destacando la forma en que se
han realizado las operaciones basadas en relaciones:
� Agregar personas a eventos (mng.addPersonToEvent(new Long(1), e.getId())): la
persona se agrega al evento a través del conjunto de eventos en los que participa
una persona (ver PersonManager).
� Listar participantes de un evento (e.getParticipants()): los participantes a un
evento se obtienen a través del conjunto de personas que participa en un evento.
Para ello se carga el evento, se obtienen sus participantes, y para cada uno de ellos
se muestra su nombre y la lista de eventos en los cuales participa.
El método loadPerson carga una persona a través del valor de su campo clave (id). Para ello
se utiliza el método load del interfaz Session. El procedimiento de recuperación de una
instancia también sigue el esquema de sesiones y transacciones explicado con anterioridad.
El método listPerson devuelve un listado de todas las personas a través de un Criteria. Esta
forma de recuperar datos se explica en el capítulo 5.2 con detenimiento, pero por el
momento destacar que el método setFetchMode posibilita la recuperación de datos
relacionados, es decir, Hibernate por defecto no cargará los datos de los conjuntos que
estén relacionados con una instancia (por ejemplo, los eventos a los que está asociada una
persona) para no sobrecargar innecesariamente al sistema, así que en caso de querer
recuperarlos hay que notificarlo de esta forma, entre otras.
[35]
HIBERNATE
public class EventManager {
/**
* Almacenar un evento
* @param title título
* @param theDate fecha
*/
public void createAndStoreEvent(String title, Date theDate) {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Crear el evento
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
//Almacenarlo
session.save(theEvent);
//Confirmar transacción
session.getTransaction().commit();
}
/**
* Cargar un evento
* @return evento
*/
public Event loadEvent(Long eventId) {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Cargar el evento por clave
[36]
HIBERNATE
El método loadEvent carga un evento a través del valor de su campo clave (id). Para
ello se utiliza un Criteria al cual se le añade una restricción de igualdad
(Restrictions.eq) para filtrar por el campo clave (id). Además se utilizan los métodos
necesarios para que se carguen los participantes de un evento
Event anEvent = (Event) session.createCriteria(Event.class).
setFetchMode("participants", FetchMode.JOIN).
setFetchMode("participants.events", FetchMode.JOIN).
add(Restrictions.eq("id", eventId)).
uniqueResult();
//Confirmar transacción
session.getTransaction().commit();
return anEvent;
}
/**
* Listar los eventos
* @return lista de eventos
*/
public List listEvents() {
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
//Comenzar la transacción
session.beginTransaction();
//Obtener la lista de personas
List result = session.createCriteria(Event.class).
setFetchMode("participants", FetchMode.JOIN).
list();
//Confirmar transacción
session.getTransaction().commit();
return result;
}
}
[37]
HIBERNATE
(setFetchMode("participants", FetchMode.JOIN)) y para cada uno de dichos
participantes, que en su propiedad events (ver clase Person) se carguen los datos de
sus eventos (setFetchMode("participants", FetchMode.JOIN)). De esta forma, para
cada evento se podrían conocer los datos completos de las personas asociadas (edad,
nombre, apellidos y eventos a los que se encuentra asociada). El método uniqueResult
indica que la consulta deberá devolver un único valor, el cual es una instancia de la
clase solicitada.
Para estas 2 clases, los ficheros de mapeo quedan configurados según los cambios que
se han comentado al introducir las relaciones entre ambas. Para ejecutar el proyecto
simplemente queda modificar la página JSP de inicio, para que incluya la opción
Prueba relaciones:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>JSP Page</title>
</head>
<body>
<form action="Servlet">
<table>
<tr>
<td>title</td><td><input type="text" name="title"></input></td>
</tr>
<tr>
<td>date</td><td><input type="text" name="date"></input></td>
</tr>
<tr>
<td><input type="submit"/></td>
</tr>
</table>
</form>
<a href="ServletP">Prueba relaciones</a>
</body>
</html>
[38]
HIBERNATE
3. FRAMEWORK HIBERNATE
3.1. INTRODUCCIÓN
En el desarrollo de una aplicación J2EE se trabaja continuamente con objetos. Sin embargo, las
bases de datos relacionales no trabajan con ellos, sino con conjuntos y relaciones entre
conjuntos. Entonces ¿Cómo se almacena un objeto en una base de datos? ¿Y cómo se
mantienen las relaciones cuando se convierte un elemento de una base de datos en un objeto
Java? La causa de esta disparidad es la llamada diferencia Objeto-Relacional.
El problema se agrava cuanto más complejo sea el material a trasladar de un paradigma a otro.
En estos casos se hace necesario un puente entre ambos conceptos. Es lo que se llama un
mapeador objeto-relacional, también conocido por sus siglas en inglés ORM (Object-Relational
Mapping).
Un ORM permite al programador aislarse de los detalles básicos. Basta definir cómo se deben
trasladar los datos entre ambos modelos y el mapeador se encargará de aplicarlo en cada
ocasión.
Hibernate es un potente servicio de consulta y persistencia objeto/relacional con el cual es
posible desarrollar clases persistentes mediante lenguajes orientados a objetos manteniendo
sus características básicas (incluyendo herencia, polimorfismo, colecciones, etc...). Permite
expresar consultas tanto en SQL como en su propia extensión de este lenguaje, denominada
HSQL.
Hibernate es un proyecto de software libre con código abierto, por lo que su desarrollo se
encuentra en continua evolución. La página del proyecto:
http://www.hibernate.org/
[39]
HIBERNATE
3.2. INSTALACIÓN
IMPORTANTE:
Este apartado sirve como guía de ayuda de instalación manual de Hibernate para su uso dentro de una aplicación.
No es recomendable realizar la instalación de Hibernate “a mano”. Normalmente, se utilizan entornos de desarrollo integrados como Netbeans, con los que se puede elegir durante la creación del proyecto que añada los frameworks deseados, entre ellos Hibernate, de forma que el entorno los configurará adecuadamente de forma automática.
En caso de que se descargue la última versión, se recomienda seguir la ayuda oficial ya que estas instrucciones pueden estar obsoletas.
1. Descargar la distribución de Hibernate Core de:
https://www.hibernate.org/344.html
2. Descomprimir el fichero
3. Copiar los archivos jar necesarios a la carpeta lib de nuestra aplicación:
a. hibernate3.jar
b. \lib\required\*.jar
c. \lib\optional\*.jar
4. Crear el fichero hibernate.cfg.xml en el paquete por defecto de la aplicación.
3.3. CONFIGURACIÓN
Hibernate se configura a través hibernate.cfg.xml. En el paso 5 del ejemplo anterior se
explicaron algunos de los elementos de configuración que se pueden encontrar en este
fichero:
[40]
HIBERNATE
<?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 name="session1">
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/firstapp
</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 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>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>
</session-factory>
</hibernate-configuration>
[41]
HIBERNATE
Este archivo es leído por el framework cuando arranca y contiene los siguientes elementos:
� Sesiones: session factory utilizados en la aplicación. Si la aplicación accede a más de
una Base de datos es necesario declarar más de un session-factory, cada uno de ellos
identificado por un nombre distinto.
� Propiedades: dentro de cada sesión se declaran una serie propiedades para la
conexión y configuración del acceso a la Base de datos:
� Datos de la conexión: datos para conectar con la Base de datos.
hibernate.connection.driver_class Driver JDBC de la Base de datos
hibernate.connection.url URL de conexión a la Base de datos
hibernate.connection.username Usuario
hibernate.connection.password Clave
hibernate.connection.pool_size Máximo número de conexiones del pool
� Propiedades de configuración: parámetros de configuración generales.
hibernate.max_fetch_depth Profundidad máxima de joins para relaciones simples
hibernate.default_batch_fetch_size Tamaño por defecto para el fetching de asociaciones
hibernate.default_entity_mode Modo por defecto para la representación de entidades
dynamic-map
dom4j
pojo
hibernate.order_updates Ordenar las actualizaciones por clave primaria
true
false
hibernate.generate_statistics Generar estadísticas de rendimiento
true
false
hibernate.use_identifier_rollback Resetea el identificador asociado en caso de eliminación
true
false
hibernate.use_sql_comments Genera comentarios en las sentencias SQL
true
false
[42]
HIBERNATE
� Propiedades de conexión: otras propiedades de la conexión.
hibernate.jdbc.fetch_size Tamaño de búsqueda
hibernate.jdbc.batch_size Actualización batch de JDBC2
hibernate.jdbc.batch_versioned_data Utilizar el rowcount que devuelve executeBatch()
true
false
hibernate.jdbc.factory_class Seleccionar un org.hibernate.jdbc.Batcher propio
hibernate.jdbc.use_scrollable_resultset Uso de resultsets scrollables de JDBC2
true
false
hibernate.jdbc.use_streams_for_binary Uso de streams para la escritura de tipos de datos serializables
true
false
hibernate.jdbc.use_get_generated_keys Uso de getGeneratedKeys() de JDBC3
true
false
hibernate.connection.provider_class Uso de un ConnectionProvider propio
hibernate.connection.autocommit Habilita el Autocommit true
false
hibernate.connection.release_mode Modo de Liberación de conexiones JDBC
auto
on_close
after_transaction
after_statement
hibernate.connection.<propertyName> Propiedad específica de JDBC para la conexión
hibernate.jndi.<propertyName> Propiedad específica de JNDI para la conexión
hibernate.connection.isolation Nivel de aislamiento de transacciones
[43]
HIBERNATE
� Propiedades de caché: gestión de la caché de segundo nivel.
hibernate.cache.provider_class Nombre de CacheProvider propio.
hibernate.cache.use_minimal_puts Optimizar la operación de la caché al mínimo número de escrituras.
true false
hibernate.cache.use_query_cache Habilitar la cache de consultas true false
hibernate.cache.use_second_level_cache Deshabilitar completamente la cache
true false
hibernate.cache.query_cache_factory Nombre de un interfaz QueryCache propio
hibernate.cache.region_prefix Prefijo de los nombres regionales de la caché
hibernate.cache.use_structured_entries Usar entradas de cache legibles true false
� Propiedades de transacciones: gestión de transacciones.
hibernate.transaction.factory_class Nombre de la factoría de transacciones
jta.UserTransaction Nombre para obtener la transacción JTA
hibernate.transaction.manager_lookup_class Nombre del TransactionManagerLookup
hibernate.transaction.flush_before_completion La sesión se lanza antes de concluir la transacción
true
false
hibernate.transaction.auto_close_session Cierra la sesión tras concluir la
transacción
true
false
[44]
HIBERNATE
� Otras propiedades.
hibernate.current_session_context_class
Estrategia para
obtener la sesión
actual
jta
thread
managed
custom Class
hibernate.query.factory_class Implementación del
HQL Parser
org.hibernate.hql.ast.
ASTQueryTranslatorFactory
org.hibernate.hql.classic.
ClassicQueryTranslatorFactory
hibernate.query.substitutions Mapeo a consultas SQL
hibernate.hbm2ddl.auto Genera el esquema automáticamente al crear la Sesión
validate
update
create
create-drop
hibernate.cglib.use_reflection_optimizer Utilización de CGLIB
en lugar de reflexión
true
false
� Mapeos: ficheros de mapeo de clases (hbm.xml) que se utilizan en la aplicación.
3.3.1. MAPEOS
Los ficheros de mapeo que se utilizan en la aplicación se definen en hibernate.cfg, indicando la
ruta donde se encuentran los mismos. Cada uno de estos ficheros con extensión hbm.xml
mapean una clase de la aplicación con una de las tablas de la Base de datos especificada en la
conexión, y contienen los detalles de dicho mapeo.
[45]
HIBERNATE
Los ficheros de mapeo normalmente incluyen el nombre de la tabla y clase relacionadas, y el
detalle del mapeo entre columnas de la tabla y propiedades de la clase, como puede
observarse en la ilustración anterior. Un fichero de mapeo puede contener varias clases
mapeadas, aunque habitualmente se realiza con una sola clase (cuyo nombre debería coincidir
con el nombre del fichero de mapeo).
Los elementos básicos de un fichero de mapeo aparecen a continuación. Se marcarán en
negrita aquellos elementos importantes para su utilización:
1. hibernate-mapping: datos generales del mapeo.
<hibernate-mapping
schema="schemaName" (a)
catalog="catalogName" (b)
default-cascade="cascade_style" (c)
default-access="field|property|ClassName" (d)
default-lazy="true|false" (e)
auto-import="true|false" (f)
package="package.name" (g)
/>
a. schema (opcional): nombre del esquema de bases de datos.
b. catalog (opcional): nombre del catálogo de la base de datos.
c. default-cascade (opcional – none por defecto): estilo de cascada por defecto.
[46]
HIBERNATE
d. default-access (opcional – property por defecto): estrategia de acceso a las
propiedades.
e. default-lazy (opcional – true por defecto): valor por defecto del atributo lazy
de los mapeos de clases y colecciones.
f. auto-import (opcional – true por defecto): permite usar nombres de clases sin
calificar en el lenguaje SQL. Si hay dos clases con el mismo nombre sin calificar,
esta propiedad tiene que tener el valor false.
g. package (opcional): prefijo de paquete para los nombres de clases sin cualificar.
2. class: datos de la clase persistente y su relación con la tabla de la base de datos.
<class
name="ClassName" (a)
table="tableName" (b)
discriminator-value="discriminator_value" (c)
mutable="true|false" (d)
schema="owner" (e)
catalog="catalog" (f)
proxy="ProxyInterface" (g)
dynamic-update="true|false" (h)
dynamic-insert="true|false" (i)
select-before-update="true|false" (j)
polymorphism="implicit|explicit" (k)
where="arbitrary sql where condition" (l)
persister="PersisterClass" (m)
batch-size="N" (n)
optimistic-lock="none|version|dirty|all" (o)
lazy="true|false" (p)
entity-name="EntityName" (q)
check="arbitrary sql check condition" (r)
rowid="rowid" (s)
subselect="SQL expression" (t)
abstract="true|false" (u)
node="element-name"
/>
[47]
HIBERNATE
a. name (opcional): nombre completo de la clase o interfaz Java. También se
pueden hacer persistentes clases estáticas internas.
b. table (opcional – por defecto el nombre de la clase sin cualificar): nombre de
la tabla de la base de datos.
c. discriminator-value (opcional – por defecto el nombre de la clase): distingue
subclases individuales, se usa para comportamiento polimórfico.
d. mutable (opcional – true por defecto): especifica si las instancias son mutables
o no. Las clases inmutables no se pueden modificar ni eliminar.
e. schema (opcional): sobreescribe el nombre de esquema especificado por
<hibernate-mapping>.
f. catalog (opcional): sobreescribe el nombre de catálogo especificado por
<hibernate-mapping>.
g. proxy (opcional): interfaz de proxy para la incialización perezosa.
h. dynamic-update (opcional – false por defecto): la sentencia SQL UPDATE se
genera en tiempo de ejecución y solamente contiene las columnas cuyos
valores hayan cambiado.
i. dynamic-insert (opcional – false por defecto): la sentencia SQL INSERT se
genera en tiempo de ejecución y solamente contiene las columnas cuyos
valores son distintos a null.
j. select-before-update (opcional – false por defecto): se produce una sentencia
SELECT antes de actualizar para comprobar si un objeto ha sido modificado. Si
no no se produce dicha actualización.
k. polymorphism (opcional – implicit por defecto): determina si se utiliza
polimorfismo implícito o explícito a la hora de recuperar objetos en las
consultas.
l. where (opcional): condición WHERE a utilizar cuando se recuperan objetos de
esta clase.
m. persister (opcional): especifica un ClassPersister propio, permitiendo la
redefinición de la estrategia de almacenamiento persistente.
n. batch-size (opcional – 1 por defecto): tamaño batch para la búsqueda de
instancias de esta clase por campo clave.
[48]
HIBERNATE
o. optimistic-lock (opcional – version por defecto): determina la estrategia de
bloqueo optimista.
p. lazy (opcional): búsqueda perezosa.
q. entity-name (opcional – por defecto el nombre de la clase): Hibernate3
permite mapear una misma clase distintas veces (con distintas tablas), y este
nombre será el utilizado.
r. check (opcional): expression SQL para generar una comprobación en la
generación automática de esquemas.
s. rowid (opcional): utilización de ROWID.
t. subselect (opcional): mapea una entidad immutable de solo lectura con una
subconsulta de la base de datos, por ejemplo para usar vistas en lugar de
tablas.
u. abstract (opcional): marca superclases abstractas en jerarquías con <union-
subclass>.
3. id: dentro de la etiqueta class, se debe mapear la clave primaria de la tabla.
<id
name="propertyName" (a)
type="typename" (b)
column="column_name" (c)
unsaved-value="null|any|none|undefined|id_value" (d)
access="field|property|ClassName"> (e)
node="element-name|@attribute-name|element/@attribute|."
<generator class="generatorClass"/>
</id>
a. name (opcional): nombre de la propiedad.
b. type (opcional): indica el tipo.
c. column (opcional –nombre de la propiedad por defecto): nombre de la
columna.
[49]
HIBERNATE
d. unsaved-value (opcional): valor del identificador que indica que la instancia es
nueva, para distinguir de instancias guardadas o cargadas en sesiones
anteriores. Casi nunca se utiliza en Hibernate 3.
e. access (opcional – property por defecto): estrategia de acceso al valor de la
propiedad.
4. generator: dentro del id, es necesario definir un generador de identificadores únicos
para la clase.
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
a. class: nombre de la clase que implementa el interfaz
org.hibernate.id.IdentifierGenerator, a la cual se pueden pasar parámetros a
través de los elementos <param>. Asimismo, existe una serie de clases
predefinidas:
i. increment: identificadores únicos siempre que no existan otros
procesos insertando en la tabla.
ii. identity: columna de tipo identity (autonumérica) para aquellos
sistemas que la soportan.
iii. sequence: asignación basada en secuencia para aquellos sistemas que
la soportan.
iv. hilo: genera identificadores con un algoritmo hi/lo dado un nombre de
tabla y de columna.
v. seqhilo: genera identificadores con un algoritmo hi/lo dado un nombre
de secuencia.
vi. uuid: genera claves de tipo uuid de tamaño 32.
vii. guid: usa una cadena guid generada por la base de datos para aquellos
sistemas que la soportan.
[50]
HIBERNATE
viii. native: selecciona identity, sequence o hilo dependiendo de la base de
datos en uso.
ix. assigned: asignado por la aplicación. Es la opción por defecto si no se
incluye generator.
x. select: asignación por trigger.
xi. foreign: usa el identificador de otro objeto asociado, normalmente a
través de un <one-to-one>.
xii. sequence-identity: usa una secuencia y el método getGeneratedKeys
de JDBC3 para la generación.
5. composite-id: utilizado en el caso que la clave primaria esté compuesta por más de
una columna.
<composite-id
name="propertyName" (a)
class="ClassName" (b)
mapped="true|false" (c)
access="field|property|ClassName"> (d)
node="element-name|."
<key-property name="propertyName" type="typename" column="column_name"/> (e)
<key-many-to-one name="propertyName class="ClassName" column="column_name"/> (f)
......
</composite-id>
a. name (opcional): nombre de la propiedad.
b. class (opcional): la forma de implementar una clave compuesta en Hibernate
es a través de una clase. Esta propiedad indica el nombre de dicha clase, que
contendrá atributos para cada una de las columnas que componen la clave.
c. mapped (opcional – false por defecto): utilización de un identificador
compuesto mapeado.
d. access (opcional – property por defecto): estrategia de acceso al valor de la
propiedad.
[51]
HIBERNATE
e. key-property: dentro de la etiqueta composite-id, indica cada uno de los campos
que forman la clave y que no corresponden a claves foráneas de otras tablas:
i. name: nombre de la propiedad (de la clase que implementa el
identificador).
ii. type (opcional): tipo Hibernate de la propiedad.
iii. column (opcional – nombre de la propiedad por defecto): nombre de
la columna.
f. key-many-to-one: dentro de la etiqueta composite-id, indica cada uno de los
campos que forman la clave y que corresponden a claves foráneas de otras tablas:
i. name: nombre de la propiedad (de la clase que implementa el identificador).
ii. class: nombre de la clase relacionada a través de la clave foránea.
iii. column (opcional – nombre de la propiedad por defecto): nombre de
la columna.
6. property: declaración de propiedades de la clase.
<property
name="propertyName" (a)
column="column_name" (b)
type="typename" (c)
update="true|false" (d)
insert="true|false" (e)
formula="arbitrary SQL expression" (f)
access="field|property|ClassName" (g)
lazy="true|false" (h)
unique="true|false" (i)
not-null="true|false" (j)
optimistic-lock="true|false" (k)
generated="never|insert|always" (l)
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
[52]
HIBERNATE
a. name: nombre de la propiedad (primera letra siempre en minuscule).
b. column (opcional – nombre de la propiedad por defecto): nombre de la
columna de base de datos mapeada. También puede ser especificado a través
de etiquetas <column>.
c. type (opcional): indica el tipo Hibernate, que puede tratarse de uno de los
siguientes, aunque en caso de no especificar Hibernate lo asignará
automáticamente en función del tipo de la columna de base de datos:
i. Tipo básico Hibernate (integer, string, character, date, …).
ii. Clase Java o tipo básico (int, float, java.util.Date, …).
iii. Clase Java serializable.
iv. Clase propia.
d. update (opcional – true por defecto): las columnas mapeadas deben incluirse
en las sentencias UPDATE.
e. insert (opcional – true por defecto): las columnas mapeadas deben incluirse en
las sentencias INSERT.
f. formula (opcional): expresión SQL que define el valor de una propiedad
calculada. Las propiedades calculadas no se mapean a ninguna columna.
g. access (opcional – property por defecto): estrategia utilizada para acceder a la
propiedad.
h. lazy (opcional – false por defecto): indica si esta propiedad debe ser buscada
de forma perezosa cuando la instancia se accede por vez primera.
i. unique (opcional): restricción unique para esta columna.
j. not-null (opcional): restricción not-null para esta columna.
k. optimistic-lock (opcional – true por defecto): las actualizaciones de esta
propiedad requieren la adquisición del bloqueo optimista.
l. generated (opcional – never por defecto): esta propiedad es generada por la
base de datos.
7. many-to-one
[53]
HIBERNATE
<many-to-one
name="propertyName" (a)
column="column_name" (b)
class="ClassName" (c)
cascade="cascade_style" (d)
fetch="join|select" (e)
update="true|false" (f)
insert="true|false" (g)
property-ref="propertyNameFromAssociatedClass" (h)
access="field|property|ClassName" (i)
unique="true|false" (j)
not-null="true|false" (k)
optimistic-lock="true|false" (l)
lazy="proxy|no-proxy|false" (m)
not-found="ignore|exception" (n)
entity-name="EntityName" (o)
formula="arbitrary SQL expression" (p)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
a. name: nombre de la propiedad.
b. column (opcional): nombre de la columna de clave foránea. También puede
ser especificado a través de etiquetas <column>.
c. class (opcional): nombre de la clase asociada.
d. cascade (opcional): operaciones que deben ejecutarse en cascada desde el
padre al objeto asociado.
e. fetch (opcional – select por defecto): elige entre búsqueda mediante outer-
join o select secuencial.
f. update (opcional – true por defecto): las columnas mapeadas deben incluirse
en las sentencias UPDATE.
g. insert (opcional – true por defecto): las columnas mapeadas deben incluirse en
las sentencias INSERT.
[54]
HIBERNATE
h. property-ref (opcional): nombre de la propiedad de la clase asociada relacionada
con esta foreign key. Si no se especifica se utiliza la clave de la clase asociada.
i. access (opcional – property por defecto): estrategia utilizada para acceder a la
propiedad.
j. unique (opcional): restricción unique para la clave foránea.
k. not-null (opcional): restricción not-null para la clave foránea.
l. optimistic-lock (opcional – true por defecto): las actualizaciones de esta
propiedad requieren la adquisición del bloqueo optimista.
m. lazy (optional – proxy por defecto): indica si esta propiedad debe ser buscada
de forma perezosa cuando la instancia se accede por vez primera.
n. not-found (opcional – exception por defecto): cómo se tratan las claves
foráneas que referencian a filas inexistentes.
o. entity-name (opcional): nombre de entidad de la clase asociada.
p. formula (opcional): expresión SQL que define el valor de una propiedad calculada.
8. one-to-one
<one-to-one
name="propertyName" (a)
class="ClassName" (b)
cascade="cascade_style" (c)
constrained="true|false" (d)
fetch="join|select" (e)
property-ref="propertyNameFromAssociatedClass" (f)
access="field|property|ClassName" (g)
formula="any SQL expression" (h)
lazy="proxy|no-proxy|false" (i)
entity-name="EntityName" (j)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/>
a. name: nombre de la propiedad.
b. class (opcional): nombre de la clase asociada.
[55]
HIBERNATE
c. cascade (opcional): operaciones que deben ejecutarse en cascada desde el
padre al objeto asociado.
d. constrained (opcional): una clave foránea de la clave primaria de la tabla
mapeada referencia a la tabla de la clase asociada.
e. fetch (opcional – select por defecto): elige entre búsqueda mediante outer-
join o select secuencial.
f. property-ref (opcional): nombre de la propiedad de la clase asociada relacionada
con esta foreign key. Si no se especifica se utiliza la clave de la clase asociada.
g. access (opcional – property por defecto): estrategia utilizada para acceder a la
propiedad.
h. formula (opcional): expresión SQL que define el valor de una propiedad calculada.
i. lazy (optional – proxy por defecto): indica si esta propiedad debe ser buscada
de forma perezosa cuando la instancia se accede por vez primera.
j. entity-name (opcional): nombre de entidad de la clase asociada.
9. set: las relaciones inversas normalmente se modelan a través de un conjunto (set), pero
existen otros elementos que se pueden usar como list, map, bag, array o primitive-array.
[56]
HIBERNATE
<set
name="propertyName" (a)
table="table_name" (b)
schema="schema_name" (c)
lazy="true|extra|false" (d)
inverse="true|false" (e)
cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan" (f)
sort="unsorted|natural|comparatorClass" (g)
order-by="column_name asc|desc" (h)
where="arbitrary sql where condition" (i)
fetch="join|select|subselect" (j)
batch-size="N" (k)
access="field|property|ClassName" (l)
optimistic-lock="true|false" (m)
mutable="true|false" (n)
node="element-name|."
embed-xml="true|false" >
<key .... /> (o)
<map-key .... />
<element .... />
</map>
a. name: nombre de la propiedad.
b. table (opcional – nombre de la propiedad por defecto): nombre de la tabla de
la colección (no se usa para one-to-many).
c. schema (opcional): nombre del esquema para sobreescribir el esquema
definido en el elemento raíz.
d. lazy (opcional – true por defecto).
e. inverse (opcional – false por defecto): indica el lado inverso en una asociación
bidireccional.
f. cascade (opcional - none por defecto): permite operaciones en cascada sobre
los elementos hijos.
g. sort (opcional): especifica una colección ordenada mediante orden natural o
especificando una clase comparadora.
[57]
HIBERNATE
h. order-by (opcional): solo en JDK 1.4, especifica un nombre de columna que
define el orden de iteración.
i. where (opcional): condición WHERE a utilizar cuando se recuperan o eliminan
objetos de la colección.
j. fetch (opcional – select por defecto): elige entre búsqueda mediante outer-
join o select secuencial.
k. batch-size (opcional – 1 por defecto): tamaño batch para la búsqueda de
instancias de esta clase por campo clave.
l. access (opcional – property por defecto): estrategia utilizada para acceder a la
propiedad.
m. optimistic-lock (opcional – true por defecto): las actualizaciones del estado de
la colección requieren la adquisición del bloqueo optimista.
n. mutable (opcional – true por defecto): los elementos de la colección nunca
cambian.
o. key: dentro de la etiqueta set indica la clave foránea de la colección:
<key
column="columnname" (i)
on-delete="noaction|cascade" (ii)
property-ref="propertyName" (iii)
not-null="true|false" (iv)
update="true|false" (v)
unique="true|false" (vi)
/>
i. column (opcional): nombre de la columna que actúa como clave foránea.
También puede especificarse a través de elementos <column>.
ii. on-delete (opcional – noaction por defecto): la clave foránea tiene
activado el borrado en cascada en la base de datos.
iii. property-ref (opcional): la clave foránea se refiere a columnas que no
son la clave primaria de la tabla original.
iv. not-null (opcional): las claves foráneas no se pueden establecer como
null.
[58]
HIBERNATE
v. update (opcional): las claves foráneas no se pueden actualizar.
vi. unique (opcional): la clave foránea debería tener una restricción
unique.
p. one-to-many: indica
<one-to-many
class="ClassName" (i)
not-found="ignore|exception" (ii)
entity-name="EntityName" (iii)
node="element-name"
embed-xml="true|false"
/>
i. class: nombre de la clase asociada.
ii. not-found (opcional – exception por defecto): cómo se tratan las
claves foráneas que referencian a filas inexistentes.
iii. entity-name (opcional): nombre de entidad de la clase asociada.
3.4. EL INTERFAZ SESSION
El interfaz Session es la principal vía de interacción entre la aplicación Java e Hibernate. Representa
a la sesión actual, donde se producen transacciones para la interacción con la Base de datos,
efectuando operaciones de lectura, creación, modificación y eliminación de instancias de clases
mapeadas. Las instancias de tales clases se pueden encontrar en tres estados posibles:
� transient: no es persistente, no está asociada a ninguna sesión. Se pueden convertir en
persistent invocando a los métodos save, persist o saveOrUpdate. Se encuentra en la
aplicación pero no en la Base de datos.
� persistent: asociado a una única sesión. Se encuentra en la aplicación y en la Base de
datos.
[59]
HIBERNATE
� detached: persistente previamente, no asociado con ninguna sesión. Se encuentra en
la Base de datos, pero no se ha recuperado desde la aplicación.
En la siguiente ilustración aparecen estos estados, y cuáles son los métodos que provocan la
transición de uno a otro.
Para poder operar con una sesión es necesario obtener una instancia de la misma y comenzar
una transacción. Cualquier operación sobre dicha instancia debe contenerse dentro de una
transacción.
Si se produce una excepción dentro de la transacción, ésta debe ser cancelada (rollback), si no
hay que confirmarla para hacer persistentes los cambios (commit):
[60]
HIBERNATE
//Obtener la sesión actual
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
try{
//Comenzar la transacción
session.beginTransaction();
//Operaciones con session
…
}catch (Exception ex){
//Deshacer transacción
session.getTransaction().rollback();
}finally{
//Confirmar transacción
session.getTransaction().commit();
}
Los métodos que se invocan sobre la instancia de Session producen operaciones sobre la Base
de datos a través de sentencias SQL, generadas en el dialecto concreto configurado en
hibernate.cfg. De esta forma, las siguientes sentencias se producen para los métodos
enumerados anteriormente:
� INSERT: save, persist, saveOrUpdate, replicate.
� UPDATE: update, merge, saveOrUpdate, replicate.
� DELETE: delete.
A continuación se exponen algunos métodos de la clase Session utilizados habitualmente. Para
mayor información se puede acceder a la API de Hibernate:
http://www.hibernate.org/hib_docs/v3/api/
[61]
HIBERNATE
public Transaction beginTransaction()
Comienza una unidad de trabajo y devuelve la instancia de la transacción asociada. El objeto Transaction es quien tiene los métodos commit y rollback.
public Transaction getTransaction() Obtiene la instancia de transacción asociada con esta sesión. El objeto Transaction es quien tiene los métodos commit y rollback.
public Criteria createCriteria(Class pstClass) Crea una nueva instancia de Criteria.
public Query createQuery(String qryString) Crea una nueva instancia de Query para una cadena HQL.
public SQLQuery createSQLQuery(String qryString)
Crea una nueva instancia de SQLQuery para una cadena SQL.
public Object get(Class clazz, Serializable id)
Devuelve la instancia persistente de una clase, correspondiente al identificador id, o null si no existe un elemento con dicho identificador.
public Object load(Class theClass, Serializable id)
Devuelve la instancia persistente de la clase dada, correspondiente al identificador id, asumiendo que la instancia existe. Este método no debe utilizarse para determinar si existe una instancia (utilizar get en su lugar).
public Serializable save(Object object)
Convierte en persistente la instancia actual, asignando en primer lugar un identificador generado (o utilizando el id actual si se ha utilizado un generador asigned).
public void saveOrUpdate(Object object) Guarda (save) o actualiza (update) una instancia, dependiendo de si ésta era persistente anteriormente o no.
public void update(Object object) Actualiza una instancia persistente con el identificador de la instancia actual.
public Object merge(Object object)
Copia el estado de una instancia en el objeto persistente con el mismo identificador. Si no existe una instancia persistente en la sesión actual la carga. En otro caso, si la instancia no se había guardado, guarda una copia y la devuelve como una nueva instancia persistente.
public void persist(Object object) Convierte una instancia transient en persistente.
[62]
HIBERNATE
public void delete(Object object) Elimina una instancia persistente del almacén de datos.
public void replicate(Object o,ReplicationMode rm)
Hace persistente el estado de una instancia, reutilizando su identificador.
public void refresh(Object object)
Vuelve a leer el estado de una instancia de la base de datos. Útil cuando se ejecutan triggers, se inserta un campo binario, o las sesiones son muy largas.
public Serializable getIdentifier(Object object) Devuelve el identificador asociado a una instancia.
public boolean contains(Object object) Comprueba si una instancia está asociada con esta sesión.
public void evict(Object object) Elimina una instancia de la sesión, con lo cual las actualizaciones no tendrán reflejo en la Base de datos.
4. ASOCIACIONES
Las asociaciones en Hibernate representan las relaciones entre tablas. En un modelo ORM, las
claves foráneas habitualmente no se modelan con el campo identificador únicamente, sino con
instancias completas de objetos para poder aprovechar la potencia del Framework.
[63]
HIBERNATE
En la imagen anterior se puede observar la relación que existe entre la tabla Person (Persona) y
Address (Dirección). Se trata de una relación uno a muchos donde el campo AddressId actúa como
una clave foránea de la tabla Address (maestro) en la tabla Person (detalle) lo cual modela que en
una Dirección pueden habitar muchas Personas. Este tipo de relación es muy habitual en Bases de
datos y como se ha comentado, se suele denominar uno a muchos (leyendo en la dirección
Address � Person). En Hibernate suele leerse de forma inversa, muchos a uno (many-to-one), pero
sigue tratándose de la misma relación. Esta relación a nivel de clases persistentes se modela de
forma sencilla a través de un atributo address en la clase Person, de forma que si se dispone de una
instancia de Person se pueden conocer todos los datos de la instancia Address donde reside esa
persona, sin necesidad de realizar ningún tipo de consulta adicional a la Base de datos. En una Base
de datos irremediablemente habría que realizar una consulta cruzada para recuperar tales datos.
El otro atributo fruto de esta relación que aparece a nivel de clases es el conjunto people. Esto
Hibernate se denomina la relación inversa, ya que las relaciones se pueden modelar en ambos
sentidos. Este sentido de la relación es el que habitualmente se utiliza al realizar una consulta
cruzada, es decir, cuando interesa conocer quiénes son los habitantes de una determinada
dirección. De nuevo Hibernate proporciona esta información de forma sencilla sin necesidad
de implementar consultas, ya que el atributo de tipo Set de la clase Address contendrá todas
las instancias de la clase Person para aquellas personas que cumplan con la relación.
Para poder aprovechar estas funcionalidades es necesario tener en cuenta otros factores como
la configuración de los ficheros de mapeo, o los métodos a utilizar para recuperar los valores
de la Base de datos. Ambos conceptos se explican ampliamente en este capítulo y el siguiente.
4.1. RELACIONES EN BASES DE DATOS
En general, en una base de datos existen tres tipos de relación:
[64]
HIBERNATE
Las relaciones que existen entre las tablas dan lugar a modificaciones en las mismas a la hora
de normalizar el modelo, de forma que no existan campos duplicados ni pérdidas de integridad
por un mal diseño.
De las imágenes anteriores cabe destacar un aspecto importante, todas los tipos de relaciones
que aparecían en primera instancia, se pueden reducir a un único tipo, la relación Muchos a
uno, ya que:
� Las relaciones Uno a Uno se eliminan
� Las relaciones Muchos a Muchos se transforman en relaciones Muchos a Uno sobre la
tabla intermedia.
Este hecho va a simplificar considerablemente la gestión de asociaciones en Hibernate, ya que
solamente hará falta controlar un tipo de relación para poder construir modelos ORM que
representen modelos Entidad-Relación de cierta complejidad.
Los tipos de asociaciones en Hibernate que servirán para modelar las relaciones citadas es la
siguiente:
� Unidireccionales: solamente se representa un extremo de la relación.
� Unidireccionales con tabla de unión.
� Bidireccionales: se representan ambos extremos.
� Bidireccionales con tabla de unión.
[65]
HIBERNATE
Teniendo en cuenta esos tipos de relaciones y los dos sentidos de la relación aparecen en
Hibernate 12 tipos distintos de relación que se pueden modelar de forma directa. Todas estas
relaciones se pueden reducir básicamente a tres tipos que serán explicados a continuación.
Como se ha comentado, lo más sencillo para el desarrollo puede resultar en el diseño
adecuado del modelo Entidad-Relación de la Base de datos, y simplemente en utilizar mapeos
many-to-one para las relaciones entre las tablas.
Para mayor información se puede consultar la documentación de Hibernate relativa a Asociaciones:
http://www.hibernate.org/hib_docs/v3/reference/en/html/associations.html
4.2. MANY-TO-ONE
La primera de las relaciones que se explicarán es la relación Many-to-one. Para poder modelar
un sistema de Bases de datos puede resultar la única relación imprescindible ya que como se
ha explicado, el resto de ellas se pueden transformar.
Esta relación normalmente se denomina Maestro-Detalle, donde existe un extremo Maestro
que dispone de las claves principales, y un Detalle donde esta clave aparece como foránea.
A nivel de Hibernate, la relación bidireccional se modela de la siguiente forma:
� Many-to-one en el Detalle.
� One-to-Many en el Maestro, incorporando el atributo inverse. No es obligatorio
modelar este sentido de la relación.
En el siguiente ejemplo aparecen los mapeos que se producirán en las clases para modelar
esta relación. En el apartado 3.3.1 se puede consultar el significado completo de los atributos
que aquí aparecen.
[66]
HIBERNATE
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address" column="addressId"/>
</class>
En la clase Person se mapea el sentido many-to-one que es el más sencillo:
� A la clase se agrega un atributo Address address.
� En el fichero de mapeo se agrega una etiqueta many-to-one:
� name: nombre del atributo.
� column: nombre de la columna de la tabla de base de datos que se
corresponde con el atributo (la clave foránea).
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class>
En la clase Address se mapea el sentido one-to-many, el sentido inverso:
� A la clase se agrega un atributo Set people.
� En el fichero de mapeo se agrega una etiqueta set:
� name: nombre del atributo.
� inverse (true): la relación es inversa.
� key:
� column: nombre de la columna de la tabla de base de datos que actúa
como clave foránea en la otra tabla.
� one-to-many
� class: nombre de la clase que modela la otra tabla en la relación.
[67]
HIBERNATE
4.3. MANY-TO-MANY
Como se ha comentado, esta relación se puede reducir a dos relaciones Many-to-one sobre la
tabla intermedia de la Base de datos.
En Hibernate se pueden modelar relaciones Many-to-Many con tablas intermedias de forma
directa en lugar de recurrir a dos relaciones Many-to-One de la siguiente forma:
� Many-to-many en cada tabla.
� No es necesario crear la clase persistente Person_Address.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId" class="Address"/>
</set>
</class>
[68]
HIBERNATE
En la clase Person se mapea el sentido directo:
� A la clase se agrega un atributo Set addresses.
� En el fichero de mapeo se agrega una etiqueta set:
� name: nombre del atributo.
� table: nombre de la tabla intermedia de la base de datos.
� key:
� column: nombre de la columna de la tabla que modela esta clase que
actúa como clave foránea en la otra tabla.
� many-to-many
� class: nombre de la clase que modela la otra tabla en la relación.
� column: nombre de la columna de la otra tabla en la relación que
actúa como la otra clave foránea en la tabla intermedia.
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId" class="Person"/>
</set>
</class>
En la clase Address se mapea el sentido inverso:
� A la clase se agrega un atributo Set people.
� En el fichero de mapeo se agrega una etiqueta set:
� name: nombre del atributo.
� inverse (true): la relación es inversa.
� table: nombre de la tabla intermedia de la base de datos.
� key:
� column: nombre de la columna de la tabla que modela esta clase que
actúa como clave foránea en la otra tabla.
[69]
HIBERNATE
� many-to-many
� class: nombre de la clase que modela la otra tabla en la relación.
� column: nombre de la columna de la otra tabla en la relación que
actúa como la otra clave foránea en la tabla intermedia.
4.4. ONE-TO-ONE
Como se ha comentado, esta relación se puede eliminar de la Base de datos pasando los datos
de una de las tablas a la otra.
En Hibernate se pueden modelar relaciones One-to-One de forma directa de la siguiente
forma:
� Many-to-one en un sentido, incluyendo el atribute unique.
� One-to-one en el sentido inverso.
� Se mantienen ambos mapeos.
[70]
HIBERNATE
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address" column="addressId" unique="true" not-null="true"/>
</class>
En la clase Person se mapea el sentido directo:
� A la clase se agrega un atributo Address address.
� En el fichero de mapeo se agrega una etiqueta many-to-one:
� name: nombre del atributo.
� column: nombre de la columna de la tabla de base de datos que se
corresponde con el atributo (la clave foránea).
� unique (true): restricción de unicidad para cumplir la relación.
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person" property-ref="address"/>
</class>
En la clase Address se mapea el sentido inverso:
� A la clase se agrega un atributo Person person.
� En el fichero de mapeo se agrega una etiqueta one-to-one:
� name: nombre del atributo.
� property-ref: nombre de la propiedad que se corresponde con esta clase en el
otro extremo.
[71]
HIBERNATE
5. RECUPERACIÓN DE DATOS
En las bases de datos relacionales, las operaciones más frecuentes suelen ser las
recuperaciones de datos de una o más tablas o vistas. Para poder realizar esta operación, en
dichas bases de datos se recurre al lenguaje SQL, y más concretamente a la sentencia SELECT.
De esta forma, especificando una sentencia más o menos compleja es posible obtener la
información solicitada
Como se vio en el apartado anterior, la relación más habitual entre dos tablas es la relación
Maestro-Detalle o many-to-one. Sobre esta relación se pueden efectuar 2 tipos de consultas
cruzadas bastante habituales:
Obtener todos los datos de las personas y de la de la dirección donde viven:
SELECT PERSON.NAME, PERSON.AGE, ADDRESS.STREET, ADDRESS.NUMBER
FROM PERSON, ADDRESS
WHERE PERSON.ADDRESSID = ADDRESS.ADDRESSID
Obtener los nombres de todas las personas que viven en una dirección determinada:
SELECT PERSON.NAME
FROM PERSON, ADDRESS
WHERE PERSON.ADDRESSID = ADDRESS.ADDRESSID
AND ADDRESSID = 1
[72]
HIBERNATE
En Hibernate se podría realizar de la misma forma (SQLQuery) pero lo más habitual no es esto,
sino aprovechar las características del modelo ORM para acceder a esta información de forma
más eficiente. Para ello existen dos alternativas:
� Usar una consulta HQL para ejecutar la consulta, de forma que usando un lenguaje
orientado a objetos pero muy similar a SQL.
� Usar un Criteria, de forma que a través de programación se puedan utilizar diversos
métodos para recuperar los datos deseados de forma similar a como lo hace HQL.
Como se ha venido comentando, la relación entre las dos tablas anteriores se modela a nivel
de clases con una asociación many-to-one, y los siguientes atributos relacionados:
� Address address en la clase Person, que permitirá acceder a todos los datos de la
dirección de una Persona.
� Set people en la clase Address, que permitirá conocer todas las personas que habitan
una misma dirección.
Lo que se modela en las clases es exactamente lo que se estaba accediendo a través de
sentencias SELECT anteriores. Por tanto casi todo el trabajo ya está hecho, solamente hace
falta acceder a los datos:
List list = (List) session.createQuery("from Person").list();
List list = (List) session.createCriteria(Person.class).list();
En ambos casos se obtiene una lista con una instancia de la clase Person por cada una de las
personas de la tabla Person, en cuyo atributo address se puede consultar toda la información
necesaria sobre la dirección. Sobre esta capacidad es necesario destacar que no siempre
Hibernate recupera todos los datos relacionados ya que suele comportarse en modo “lazy”, es
decir, solamente recupera los datos principales de la clase, mientras que los atributos de
asociaciones (como address) quedan sin inicializar por motivos de eficiencia. Para forzar que
estos atributos siempre se carguen se utilizan los fetch o alias a la hora de recuperar los datos,
los cuales se explican con mayor detalle en los apartados posteriores.
[73]
HIBERNATE
Set set = ((Address)session.createQuery("from Address where id=1").list()).getPeople();
Set set = ((Address)session.createCriteria(Address.class).
add(Restrictions.eq("id", new Integer(1)))
.list()).getPeople();
5.1. HQL
Hibernate usa un potente lenguaje de consulta (HQL), que es similar en apariencia a SQL. En
comparación con SQL, sin embargo, HQL es completamente orientado a objetos y es capaz de
gestionar conceptos como herencia, polimorfismo y asociaciones.
La información completa se puede encontrar en:
http://www.hibernate.org/hib_docs/v3/reference/en/html/queryhql.html
5.1.1. SENSIBILIDAD A MAYÚSCULAS
Exceptuando los nombres de clases y atributos, el lenguaje HQL es insensible a mayúsculas
(por ejemplo se admite SELECT, select o SEleCT).
5.1.2. DATOS DE LA CONSULTA
La consulta HQL más simple es la siguiente:
from Cat
Devuelve todas las instancias de la clase Cat. Normalmente NO es necesario utilizar el nombre
de paquete (from eg.Cat) ya que la opción auto-import es true por defecto.
A la hora de referirse a Cat desde otras partes de la consulta, normalmente se utiliza un alias a
continuación de la clase. La especificación del alias puede usar la palabra clave as (as cat) o no:
from Cat cat
[74]
HIBERNATE
5.1.3. JOINS
Los joins son la forma de realizar cruces entre tablas en Bases de datos. Habitualmente no se
utilizan los joins porque la forma de escribir las consultas resulta más amigable utilizando
condiciones where, de forma que es el gestor de la Base de datos quien se encarga de realizar
la transformación necesaria para utilizar este tipo de unión. Las consultas siguientes son
equivalentes en cuanto al resultado obtenido:
SELECT e.nombre, d.nombre FROM empleados e, departamentos d
WHERE d.numero = e.dnumero
SELECT e.nombre, d.nombre FROM empleados e
INNER JOIN departamentos d ON d.numero = e.dnumero
Existen varios tipos de JOIN posibles entre 2 tablas de la Base de datos:
� CROSS JOIN: unión de todos los datos de dos tablas sin especificar ningún criterio
(producto cartesiano). Este tipo de JOIN es muy ineficiente.
10 Javier 2B
Manuel 1B
12 Antonio 1A
GARAGE NOMBRE PISO
PROPIETARIOS
22 12
22 11
20 10
TAMAÑO NUMERO
GARAJES
20 10 Manuel 1B
22 11 Manuel 1B
20 10 12 Antonio 1A
22 11 12 Antonio 1A
22 12 12 Antonio 1A
… … … …
[75]
HIBERNATE
En Hibernate se especifica de forma similar a SQL:
from Formula, Parameter
� INNER JOIN o JOIN: unión de los datos de dos tablas solamente en aquellos casos
donde existan datos en el campo de combinación. Este tipo de JOIN es el que resulta
equivalente a realizar la combinación a través de un WHERE.
En Hibernate también se especifica de forma similar a SQL, solamente teniendo en cuenta que
no se hacen uniones entre tablas propiamente dichas, sino con los atributos sobre los que se
han montado asociaciones. Se puede usar con las palabras clave inner join o inner outer join, o
incluso simplemente join:
from Cat as cat
inner join cat.mate as mate
10 Javier 2B
Manuel 1B
12 Antonio 1A
GARAGE NOMBRE PISO
PROPIETARIOS
22 12
22 11
20 10
TAMAÑO NUMERO
GARAJES
20 10 Javier 2B
22 12 Antonio 1A
TAMAÑO NUMERO NOMBRE PISO
[76]
HIBERNATE
� LEFT OUTER JOIN o LEFT JOIN: se seleccionan todos los datos de la primera tabla de la
combinación, uniendo los datos de la segunda tabla solamente en los casos que sea
posible, y poniendo null en los casos que no sea posible.
De nuevo se pueden especificar con HQL sobre los atributos sobre los que se han
montado asociaciones uniones de este tipo a través de left join o left outer join:
from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten
� RIGHT OUTER JOIN o RIGHT JOIN: se seleccionan todos los datos de la segunda tabla
de la combinación, uniendo los datos de la primera tabla solamente en los casos que
sea posible, y poniendo null en los casos que no sea posible.
10 Javier 2B
Manuel 1B
12 Antonio 1A
GARAGE NOMBRE PISO
PROPIETARIOS
22 12
22 11
20 10
TAMAÑO NUMERO
GARAJES
null null Manuel 1B
20 10 Javier 2B
22 12 Antonio 1A
TAMAÑO NUMERO NOMBRE PISO
[77]
HIBERNATE
Se puede especificar con HQL con right join o right outer join.
� FULL OUTER JOIN o FULL JOIN: es una combinación de los dos anteriores.
10 Javier 2B
Manuel 1B
12 Antonio 1A
GARAGE NOMBRE PISO
PROPIETARIOS
22 12
22 11
20 10
TAMAÑO NUMERO
GARAJES
22 11 null null
null null Manuel 1B
20 10 Javier 2B
22 12 Antonio 1A
TAMAÑO NUMERO NOMBRE PISO
10 Javier 2B
Manuel 1B
12 Antonio 1A
GARAGE NOMBRE PISO
PROPIETARIOS
22 12
22 11
20 10
TAMAÑO NUMERO
GARAJES
22 11 null null
20 10 Javier 2B
22 12 Antonio 1A
TAMAÑO NUMERO NOMBRE PISO
[78]
HIBERNATE
from Formula form
full join form.parameter param
En general, sobre cualquier tipo de join se pueden establecer condiciones:
from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0
Por último, es muy importante la utilización de la palabra clave fetch. Como se ha comentado,
normalmente las colecciones o atributos que se refieren a asociaciones mapeadas no se
inicializan debido al comportamiento “lazy” por defecto. Para asegurar la recuperación de los
datos en dichas propiedades, es necesario utilizar fetch en conjunción con los joins:
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
Para realizar un fetch sobre todas las propiedades susceptibles de búsqueda, se puede utilizar
una sentencia de este tipo:
from Document
fetch all properties
order by name
También es posible la utilización de un join implícito, es decir, no aparece la cláusula join en la
consulta pero para Hibernate es necesario realizarla para resolver las condiciones impuestas.
En este ejemplo no sería necesario realizar un join cat.mate si directamente se incluye como
condición en la condición where, aunque se recomienda su inclusión por claridad a la hora de
leer la consulta:
from Cat as cat
where cat.mate.name like '%s%'
[79]
HIBERNATE
5.1.4. SELECT
La cláusula Select permite obtener propiedades individuales en lugar de recuperar instancias
completas. Estas propiedades pueden referirse a atributos simples o a atributos mapeados. En
el siguiente ejemplo se accede a la propiedad name (String) y a la propiedad mate (Cat),
destacando que en este último caso se hace un join implícito (si no sería necesario incluir un
join cat.mate).
select cat.name, cat.mate as cmate
from Cat cat
Dentro de las cláusulas Select se puede hacer referencia al identificador de la clase de dos
formas distintas:
� Utilizando la propiedad id ya que este atributo se encuentra mapeado siempre con ese
identificador (cat.id).
� Utilizando el nombre de la propiedad si ésta tiene un nombre distinto (cat.catId).
Cuando se hace una consulta HQL sin Select, a nivel de programación se recuperan las
instancias concretas de la clase implicada en dicha consulta. En el siguiente fragmento de
código se recuperan una colección de tipo List, donde cada elemento es una instancia de la
clase Cat:
List list = session.createQuery("from Cat").list();
Cat cat = (Cat) list.get(0);
Cuando se utiliza la cláusula Select, o si se realiza un cross join de clases (from Formula,
Parameter), resulta imposible para Hibernate determinar qué clase ha de emplear en la
recuperación de los datos, por lo que éstos aparecen en forma de Object[]. Por tanto el
conjunto de datos resultado sería una colección List de Object[]:
List list = session.createQuery("select cat.name, cat.mate from Cat cat").list();
Object[] res = (Object[]) list.get(0);
[80]
HIBERNATE
En el Object[] obtenido como resultado, cada una de las posiciones es un objeto de la clase
concreta a la que pertenece el atributo, ya sea porque se ha mapeado de forma explícita en el
fichero hbm.xml (atributo type de la etiqueta property), o porque Hibernate lo ha resuelto de
forma automática:
String name = (String) res[0];
Cat mate = (Cat) res[1];
En lugar de obtener el resultado en un Object[], se puede forzar en la consulta a obtener una
colección de tipo List:
select new list(mother, offspr, mate.name)
from DomesticCat as mother
inner join mother.mate as mate
left outer join mother.kittens as offspr
También se puede obtener una colección Map, donde los alias actúan como claves y los
valores son los recuperados en la consulta:
select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
from Cat cat
Por último, se puede forzar a devolver el resultado en una clase concreta, cuyo constructor
debe responder a la estructura utilizada en la consulta:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
[81]
HIBERNATE
5.1.5. OPERACIONES Y FUNCIONES AGREGADAS
En HQL se pueden utilizar las operaciones y expresiones habituales en SQL:
� Operadores matemáticos: +, -, *, /
� Operadores de comparación: =, >=, <=, <>, !=, like
� Operadores lógicos: and, or, not, in, is null, …
� Funciones: concat() (o ||), current_timestamp(), day(), month(), …
� …
Asimismo, es posible utilizar funciones agregadas habituales:
� avg(), sum(), min(), max(), count()
5.1.6. CONDICIONES
La cláusula Where se utiliza para la especificación de condiciones en las consultas. Puede
incorporar expresiones para determinar la condición, de forma similar a como se realiza en
SQL, teniendo en cuenta las características orientadas a objetos de HQL:
from Foo foo
where foo.bar.baz.customer.address.city is not null
Es posible comparar instancias completas, así como acceder a la propiedad class:
from Cat cat, Cat rival
where cat.mate = rival.mate
and cat.class = DomesticCat
[82]
HIBERNATE
5.1.7. ORDEN
La cláusula utilizada para ordenar el conjunto de resultados es Order By:
from DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate
5.1.8. AGRUPACIÓN
La agrupación del conjunto de resultados normalmente es utilizada para la aplicación de
funciones de agregado sobre los grupos. La utilización en HQL no difiere de la habitual en SQL:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
5.1.9. CONSULTAS ANIDADAS
HQL también permite la utilización de consultas anidadas tanto en la cláusula Select como en
las condiciones Where:
select cat.id, (select max(kit.weight) from cat.kitten kit) as kitweight
from Cat as fatcat
where fatcat.weight > (
select avg(cat.weight) from DomesticCat cat
)
[83]
HIBERNATE
5.2. CRITERIA
Hibernate proporciona una API intuitiva y extensible para la construcción de consultas, la cual
evita al desarrollador la necesidad de conocer HQL ni SQL para la recuperación de datos.
La información completa se puede encontrar en la ayuda de la API de la clase Criteria, o en el
siguiente enlace:
http://www.hibernate.org/hib_docs/v3/reference/en/html/querycriteria.html
5.2.1. DATOS DE LA CONSULTA
La forma más simple para construir un Criteria es la siguiente:
Criteria crit = sess.createCriteria(Cat.class);
El método createCriteria devuelve un objeto perteneciente al interfaz Criteria. La mayoría de
los métodos que se invocan sobre un Criteria devuelven otro objeto de tipo Criteria, de forma
que las llamadas se van enlazando para construir una consulta completa. A lo largo de este
capítulo se explican los distintos métodos disponibles.
List cats = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount() )
.add( Projections.avg("weight") )
.add( Projections.max("weight") )
.add( Projections.min("weight") )
.add( Projections.groupProperty("color") )
)
.addOrder( Order.asc("color") )
.list();
[84]
HIBERNATE
Hay dos formas básicas de recuperar los datos de un Criteria:
� Invocando al método list, que devolverá una lista con las instancias concretas
obtenidas por la consulta.
List cats = session.createCriteria(Cat.class)
.list();
Cat cat = (Cat) cats.get(0);
� Invocando al método uniqueResult, que devolverá un único resultado como
consecuencia de la ejecución de la consulta. Este método resulta en excepción si el
resultado no es un único registro.
Cat cat = (Cat) session.createCriteria(Cat.class)
.add(Restrictions.eq("id", new Integer(1)))
.uniqueResult();
5.2.2. JOINS
En un Criteria, la única posibilidad de join que existe es utilizar un fetch:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.setFetchMode("mate", FetchMode.JOIN)
.setFetchMode("kittens", FetchMode.JOIN)
.list();
[85]
HIBERNATE
El valor FetchMode.JOIN indica que debe realizarse un inner join a la hora de realizar el fetch.
En el caso que se quiera realizar otro tipo de join se utilizará el método createAlias indicando
que se trata de un LEFT_JOIN, RIGHT_JOIN o FULL_JOIN:
List cats = sess.createCriteria(Cat.class)
.createAlias("kittens", "kt", CriteriaSpecification.LEFT_JOIN)
.add( Restrictions.eqProperty("kt.name", "miao") )
.list();
En el siguiente ejemplo aparece la creación de un alias normal, es simplemente equivalente a
la creación de un alias en HQL (as kt):
List cats = sess.createCriteria(Cat.class)
.createAlias("kittens", "kt")
.add( Restrictions.eqProperty("kt.name", "miao") )
.list();
5.2.3. SELECT
La cláusula Select de SQL o HQL permite realizar la operación denominada Proyección en
álgebra relacional. El método setProjection se encarga de indicar que se van a seleccionar
propiedades concretas del conjunto de resultados:
List results = session.createCriteria(Domestic.class, "cat")
.createAlias("kittens", "kit")
.setProjection( Projections.projectionList()
.add( Property.forName("cat.name") )
.add( Projections.property("kit.name"), "kitName" )
)
.list();
[86]
HIBERNATE
El método setProjection recibe como parámetro un ProjectionList, que se irá construyendo a
través de métodos add. El método add recibe la propiedad concreta que se va a proyectar, y el
segundo parámetro en caso de existir se trata del alias de dicha propiedad.
Para recuperar una propiedad a incluir en la lista de proyecciones se puede utilizar
indistintamente Property.forName() o Projections.property().
Al igual que ocurre con HQL cuando se utiliza setProjection resulta imposible para Hibernate
determinar qué clase ha de emplear en la recuperación de los datos, por lo que éstos aparecen
en forma de Object[].
5.2.4. CONDICIONES
Para establecer condiciones en un Criteria se usa la clase Restrictions. A través del método add
del objeto Criteria se van añadiendo las distintas restricciones, de forma que se van uniendo a
través de una conjunción, es decir, a través de and. En la siguiente consulta, la condición
resultante será name like "Fritz%" and weight >= 10.
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.ge("weight", new Integer(10)) )
.list();
Existen restricciones para la mayoría de los operadores habituales en consultas SQL. Para unir
restricciones a través de un or se puede usar el método or() para 2 restricciones, o
disjunction() para un número mayor de restricciones:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.or(
Restrictions.eq( "age", new Integer(0) ),
Restrictions.isNull("age")
) )
.list();
[87]
HIBERNATE
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
.add( Restrictions.disjunction()
.add( Restrictions.isNull("age") )
.add( Restrictions.eq("age", new Integer(0) ) )
.add( Restrictions.eq("age", new Integer(1) ) )
.add( Restrictions.eq("age", new Integer(2) ) )
) )
.list();
También se pueden obtener restricciones a través de las propiedades directamente:
Property age = Property.forName("age");
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.disjunction()
.add( age.isNull() )
.add( age.eq( new Integer(0) ) )
.add( age.eq( new Integer(1) ) )
.add( age.eq( new Integer(2) ) )
) )
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
.list();
Por último, se pueden establecer restricciones SQL directamente, indicando la condición SQL,
los valores a aplicar y los tipos de dichos valores:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)",
"Fritz%",
Hibernate.STRING) )
.list();
[88]
HIBERNATE
5.2.5. ORDEN
El método utilizado para ordenar el conjunto de resultados es addOrder. Este método recibe
una instancia de la clase Order que se puede obtener invocando al método asc() (orden
ascendente) o desc (orden descendente) sobre la propia clase Order, o sobre una propiedad
directamente:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%")
.addOrder( Order.asc("name") )
.addOrder( Property.forName("age").desc() )
.list();
5.2.6. AGRUPACIÓN
La agrupación del conjunto de resultados y las funciones de agregado también están
permitidas sobre un Criteria. Las funciones agregadas se añaden a la lista de proyecciones del
criteria de la misma forma que las propiedades:
List results = session.createCriteria(Cat.class)
.setProjection( Projections.projectionList()
.add( Projections.rowCount() )
.add( Projections.avg("weight") )
.add( Projections.max("weight") )
.add( Projections.groupProperty("color") )
)
.list();
[89]
HIBERNATE
5.2.7. LIMITAR EL CONJUNTO DE SALIDA
Para limitar el número de elementos devueltos por un criteria se utiliza el método limit(),
conjugado con el método setFirstResult() si se quiere empezar en un registro distinto del
primero (0). El siguiente Criteria obtiene una lista con 5 instancias de la clase Cat, comenzando
en el registro con índice 2 del conjunto de resultados.
List list = sess.createCriteria(Cat.class).
setMaxResults(5).
setFirstResult(2).
list();
5.3. SQL
También se permite la utilización de consultas en SQL nativo desde Hibernate, lo cual resulta
bastante útil en algunos casos como la utilización de sentencias DDL o ejecución de
procedimientos.
La principal desventaja de este método es que al utilizar el dialecto SQL de una base de datos
concreta, la portabilidad de la aplicación entre distintos tipos de Bases de datos se ve
comprometida.
La información completa se puede encontrar en la página correspondiente del portal de
Hibernate:
http://www.hibernate.org/hib_docs/v3/reference/en/html/querysql.html
[90]
HIBERNATE
5.3.1. CONSULTAS SQL
Las consultas SQL se especifican a través del método createSQLQuery:
List list = sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS")
.list();
Object[] res = (Object[]) list.get(0);
La lista obtenida como resultado estará formada por un Object[] con las instancias que
representan a los campos especificados en la consulta. Se pueden especificar los tipos de las
instancias devueltas de forma explícita a través del método addScalar:
List list = sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE);
Object[] res = (Object[]) list.get(0);
Se puede añadir una entidad a una consulta de forma sencilla, para que la lista de resultados
sea un conjunto de instancias de la clase concreta:
List list = sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS")
.addEntity(Cat.class);
Cat cat = (Cat) list.get(0);