8 Hibernate¶
Introducción¶
Hibernate es un proveedor de JPA, uno de los más populares y ampliamente utilizados en la comunidad de desarrollo de Java. Actúa como una implementación de la especificación JPA, permitiendo a los desarrolladores interactuar con bases de datos relacionales utilizando entidades mapeadas en objetos Java.
Hibernate ofrece una serie de características poderosas para el mapeo objeto-relacional, incluyendo:
- Mapeo de entidades: Hibernate permite mapear clases Java a tablas en la base de datos y atributos de clase a columnas en esas tablas.
- Consultas JPQL: Proporciona un lenguaje de consulta llamado JPQL (Java Persistence Query Language) que permite realizar consultas orientadas a objetos en lugar de escribir SQL directamente.
- Gestión de transacciones: Hibernate facilita la gestión de transacciones en aplicaciones JPA, lo que garantiza la consistencia y la integridad de los datos.
- Caché de primer y segundo nivel: Ofrece mecanismos de caché que pueden mejorar el rendimiento de las consultas y reducir la carga en la base de datos.
- Relaciones: Hibernate admite una variedad de tipos de relaciones, incluyendo relaciones uno a uno, uno a muchos y muchos a muchos, lo que facilita el modelado de datos complejos.
- Herencia: Permite mapear jerarquías de clases y herencia de manera eficiente.
- Integración con Java EE y Spring: Hibernate se puede integrar con entornos de desarrollo empresarial como Java EE y marcos de desarrollo como Spring.
Es importante destacar que Hibernate no es el único proveedor de JPA disponible. Otros proveedores, como EclipseLink y Apache OpenJPA, también implementan la especificación JPA y proporcionan funcionalidades similares. La elección de un proveedor específico depende de los requisitos del proyecto y las preferencias del desarrollador. Hibernate es popular debido a su amplia comunidad de usuarios, su amplia adopción y su rico conjunto de características.
Arquitectura Hibernate¶
Hibernate parte de la filosofía de mapear objetos Java (POJO). Para almacenar y recuperar estos objetos de la base de datos, el desarrollador debe mantener una conversación con el motor de Hibernate mediante un objeto especial que es la sesión (equiparable al concepto de conexión de JDBC). Igual que con las conexiones JDBC, las sesiones se deben crear y cerrar.
La clase Session
pertenece al paquete org.hibernate.Session
y ofrece métodos como save(Object object)
, createQuery(String sql)
, beginTransaction()
, close()
, etc, para interactuar con la base de datos.
Por ejemplo, podemos insertar un objeto con save(Object o)
sin necesidad de especificar una sentencia SQL. Una instancia de Session
no consume mucha memoria y su creación y destrucción es muy eficiente, lo que es muy importante ya que podemos crear y destruir sesiones todo el tiempo, quizás en cada petición.
Las interfaces de hibernate son las siguientes:
- La interfaz
SessionFactory
permite obtener instanciasSession
. Esta interfaz debe compartirse entre muchos hilos de ejecución. Por regla general hay una únicaSessionFactory
para toda la aplicación, creada durante la inicialización de la misma, y se utilizar para crear todas las sesiones relacionadas con un contexto dado. Si la aplicación accede a varias bases de datos se necesitará unaSessionFactory
por cada base de datos. - La interfaz
Configuration
se utiliza para configurar Hibernate. La aplicación la utiliza para especificar la ubicación de los documentos que indican el mapeado de los objetos y propiedades especificas de Hibernate, y a continuación crea laSessionFactory
. - La interfaz
Query
permite realizar consultar a la base de datos y controla cómo se ejecutan dichas consultas. Las consultas se escriben en HQL o en el dialecto SQL nativo de la base de datos que estemos utilizando. Una instancia de esta interfaz se utiliza para enlazar los parámetros de la consulta, limitar el número de resultados devueltos y para ejecutar dicha consulta. -
La interfaz
Transaction
nos permite asegurar que cualquier error que ocurra entre el inicio y el final de la transacción produzca el fallo de la misma.flowchart cfg["Configuration Configuration cfg = new Configuration().configure();"] factory["SessionFactory SessionFactory sessionFactory = cfg.buildSessionFactory();"] db[(Database)] session1["Session Session session = sessionFactory.openSession();"] session2["Session Session session = sessionFactory.openSession();"] subgraph one trans1["Transaction Transaction tx = session.beginTransaction"] subgraph two LR save1["save"] load1["load"] delete1["delete"] get1["get"] update1["update"] end subgraph three LR commit1["tx.commit();"] rollback1["tx.rollback();"] end close1["session.close();"] end subgraph four trans1["**Transaction** Transaction tx = session.beginTransaction"] subgraph five LR save2["save"] load2["load"] delete2["delete"] get2["get"] update2["update"] end subgraph six LR commit2["tx.commit();"] rollback2["tx.rollback();"] end close2["session.close();"] end cfg --> factory factory --> session1 factory --> session2 factory --> db session1 --> one session2 --> four
Figura 7 - Aplicación con Hibernate
Configuración¶
El fichero de configuración de Hibernate es un archivo XML que contiene una serie de elemento que indican sus propiedad, como el protocolo a seguir, y los datos de conexión.
El fichero de configuración debe ir en la raíz del proyecto o en la carpeta resources si se usa un gestor de proyectos. El nombre del fichero debe ser hibernate.cfg.xml
<hibernate-configuration>
<session-factory name="MyConnection">
<property name="hibernate.connection.drive_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/horario</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
</session-factory>
</hibernate-configuration>
En el ejemplo anterior vemos como tenemos una factoría de sesión llama MyConnection con las propiedades pertinentes para una conexión a la base de datos horario.
Este archivo de configuración servirá como sustituto del archivo de configuración de la JPA estándar (persistence.xml).
Para poder indicar el mapeo de una entidad, se usará la etiqueta <mapping>
. Se utilizará junto el atributo class
cuando el mapeo de una entidad viene determinado por una clase, mientras que con el atributo resource
se indicará el archivo ORM que define el mapeo.
Importante
Las clases que funcione como entidades deben implementar la interfaz Serializable
. Además se recomiendan los estándares de renombrado de JavaBean.
Archivos de mapeo¶
A la hora de realizar el mapeo de una entidad a través de anotaciones, no es necesario realizar ningún cambio, ya que Hibernate usa las anotaciones del paquete de JPA. Mientras que si queremos mapear una entidad a través un archivo XML, podemos usar la nomenclatura estándar ed la JPA o una específica para Hibernate (recomendado).
Un ejemplo de una configuración específica es:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ejemplo01.Profesor" table="Profesor" >
<id column="Id" name="id" type="integer">
<generator class="identity"/>
</id>
<property name="nombre" />
<property name="ape1" />
<property name="ape2" />
</class>
</hibernate-mapping>
De este ejemplo podemos destacar las siguientes etiqueta:
class
: indica la clase que hace de entidad. Con el atributoname
indicamos la ruta de paquetes del fichero Java y con el atributotable
indica el nombre de la tabla de la base de datos con la que está relacionada.id
: indica el atributo de la clase que actúa como primary key. Con el atributocolumn
indicamos el nombre de la columna de la base de datos a la que hace referencia, y con el atributoname
el nombre del atributo de la clase. Además contype
podemos indicar el tipo de dato.property
: indica los atributos de las clases. También tiene el atributoaccess
al igual que en los archivos ORM estándar pero en esta ocasión se usa por defecto el valor propertygenerator
: indica la forma en la que se va a generar la clave primaria. Los valores posibles son:- increment: genera identificadores.
- identity: admite columnas de identidad de DB2, MySQL, MS SQL Server, Sybase y HypersonicSQL.
- sequence: usa una secuencia en DB2, PostgreSQL, Oracle, SAP DB, McKoi o un generador en Interbase.
Info
Para más información de las etiquetas y de los atributos visite: http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd
Para las relaciones tenemos las siguientes etiquetas:
many-to-one
: indica una relación de muchos a uno. Algunos de sus atributos más comunes son:name
,column
,class
.one-to-one
: indica una relación de uno a uno.join
: mapea las propiedades de una clase a varias tablas que tengan relación uno-a-uno.
Para poder indicar una relación many-to-many
hacemos uso de las etiquetas set
, list
o map
. Los atributos más comunes son:
name
: indica el nombre del atributo de la clase que representa la lista.table
: indica el nombre de la tabla que representa la relación. Por ejemplo, si tenemos una tabla product y otra customer, la tabla que representa la relación puede ser cart.
Dentro de estas etiquetas podemos encontrar otras como:
key
: indica la clave de la relación, normalmente representa a la clave primaria de la entidad. En su interior se debe usar la etiquetacolumn
para indicar a que columna hace referencia.many-to-many
: indica la otra clase identidad que debe mapear, si la relación es many to many.one-to-many
: indica la clase a la que hace referencia, pero en esta ocasión la relación es one to many.
Este ejemplo trata dos entidad, Stock y StockDailyRecord, cuya relación es de uno a muchos. La clase Stock representa el stock de un producto, mientras que StockDailyRecord representa los cambios de stock diarios:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.example.stock.Stock" table="stock" catalog="example_db">
<id name="stockId" type="java.lang.Integer">
<column name="STOCK_ID" />
<generator class="identity" />
</id>
<property name="stockCode" type="string">
<column name="STOCK_CODE" length="10" not-null="true" unique="true" />
</property>
<property name="stockName" type="string">
<column name="STOCK_NAME" length="20" not-null="true" unique="true" />
</property>
<set name="stockDailyRecords" table="stock_daily_record"
inverse="true" lazy="true" fetch="select">
<key>
<column name="STOCK_ID" not-null="true" />
</key>
<one-to-many class="com.example.stock.StockDailyRecord" />
</set>
</class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.example.stock.StockDailyRecord" table="stock_daily_record"
catalog="example_db">
<id name="recordId" type="java.lang.Integer">
<column name="RECORD_ID" />
<generator class="identity" />
</id>
<many-to-one name="stock" class="com.example.stock.Stock" fetch="select">
<column name="STOCK_ID" not-null="true" />
</many-to-one>
<property name="priceOpen" type="java.lang.Float">
<column name="PRICE_OPEN" precision="6" />
</property>
<property name="priceClose" type="java.lang.Float">
<column name="PRICE_CLOSE" precision="6" />
</property>
<property name="priceChange" type="java.lang.Float">
<column name="PRICE_CHANGE" precision="6" />
</property>
<property name="volume" type="java.lang.Long">
<column name="VOLUME" />
</property>
<property name="date" type="date">
<column name="DATE" length="10" not-null="true" unique="true" />
</property>
</class>
</hibernate-mapping>
Sessions y objetos hibernate¶
Para trabajar con hibernate se recomienda tener una clase que se encargue de crear una SessionFactory
, se recomienda hacerlo con un patrón Singleton:
public class HibernateUtil{
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory(){
try{
Configuration configuration = new Configuration().configure()
return configuration.buildSessionFactory(new StandardServiceRegistryBuilder().configure().build());
} catch (Throwable ex){
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory(){
return sessionFactory;
}
}
Para poder obtener la sesión actual, será necesario cargar el fichero de configuración hibernate.cfg.xml. Para ello se llama a new Configuration().configure()
, y de esta forma se inicializa el entorno de Hibernate. Se necesita crear un objeto StandardServiceRegistry
que contiene una lista de servicios que utiliza Hibernate para crear el SessionFactory
.
Transacciones¶
Un objeto Session
de Hibernate representa una única unidad de trabajo para un almacén de datos dado y lo abre un ejemplar de SessionFactory
(openSession()
). Al crear la sesión se crea la transacción para dicha sesión (close()
). Se deben cerrar las sesiones cuando se haya completado todo el trabajo de una transacción:
// Obtener la sesión
SessionFactory factory = HibernateUtil.getSessionFactory();
// Crear la sesión
Session session = factory.openSession();
// Crear transacción
Transaction tx = session.beginTransaction();
// Operaciones
tx.commit();
session.close(); // Se cierra la sesión
El método beginTransaction()
marca el comienzo de una transacción y el método commit
valida una transacción. Mientras que el método, rollback()
deshace la transacción.
Estados de un objeto Hibernate¶
Hibernate define y soporta los siguientes estados de objeto:
-
Transitorio (Transient): Un objeto es transitorio si ha sido recién instanciando utilizando el operador new, y no está asociado a una sesión de Hibernate- No tiene una representación persistente en la base de datos y no se le ha asignado un valor identificador. Las instancias transitorias serán destruidas por el recolector de basura si la aplicación no mantiene más de una referencia. Utiliza la sesión de Hibernate para hacer un objeto persistente y deja que Hibernate se ocupe de las declaraciones SQL que necesitan ejecutarse para esta transición. Las instancias recién instanciadas de una clase persistente, Hibernate las considera como transitorias. Podemos hacer una instancia transitoria persistente asociándola con una sesión.
Department dep = new Department(); dep.setDeptNo((byte) 60); dep.setDName("MARKETING"); dep.setLoc("GUADALAJARA"); session.save(dep); // (1)!
save()
hace que la instancia sea persistente
-
Persistente (Persistent). Un objeto estará en este estado cuando ya está almacenado en la base de datos. Puede haber sido guardado o cargado, sin embargo, por definición, se encuentra en el ámbito de una sesión. Hibernate detectará cualquier cambio realizado a un objeto en estado persistente y sincronizará el estado con la base de datos cuando se complete la unidad de trabajo. En definitiva, los objetos transitorios solo existen en memoria y no en un almacén de datos, han ido instanciados por el desarrollador sin haberlos almacenado mediante una sesión. Los persistentes se caracterizan por haber sido ya creados y almacenados en una sesión o bien devueltos en una consulta realizada con la sesión.
- Separado (Detached). Un objeto está en este estado cuando cerramos la sesión mediante el método
close()
. Una instancia separada es un objeto que se ha hecho persistente, pero su sesión ha sido cerrada. La referencia al objeto todavía es válida, por supuesto, y la instancia separada podría incluso ser modificada en este estado. Una instancia separada puede ser asociada a una nueva sesión más tarde, haciéndola persistente de nuevo (con todas las modificaciones).
Carga de objetos¶
Para cargar objetos usaremos lo siguientes métodos de la clase Session
:
Método | Descripción |
---|---|
<T> T load(Class<T> clase, Serializable id) | Devuelve la instancia persistente de la clase indicada con el identificador dado. La instancia tiene que existir, si no existe el método lanza una excepción ObjectNotFound . |
Object load(String className, Serializable id) | Similar al método anterior, pero en este caso indicamos en el primer parámetro el nombre de la case en formato String . |
<T> T get(Class<T> class, Serializable id) | Devuelve la instancia persistente de la clase indicada con el identificador dado. Si la instancia no existe, devuelve null. |
Object get(String className, Serializable id) | Similar al método anterior, pero en este caso indicamos en el primer parámetro el nombre de la clase |
Si queremos obtener un objeto y que se lance una clase en caso de que no exista, se usaría el método load()
:
try{
Department dep = (Department) session.load(Department.class, (byte) 20);
// ...
} class (ObjectNotFoundException o){
// ...
}
Por el contrario, si no queremos que lance una excepción usaremos el método get()
:
Department dep = (Department) session.get(Department.class, (byte) 11);
if(dep == null){
System.out.println("No existe el departamento con id " + 11);
} else {
//...
}
Operaciones básicas de objetos¶
Para almacenamiento, modificación y borrado de objetos usamos los siguiente métodos:
Métodos | Descripción |
---|---|
Serializable persist(Object o) | Guarda el objeto que se pasa como argumento en la base de datos. Hace que la instancia transitoria del objeto sea persistente. |
void merge(Object o) | Actualiza en la base de datos el objeto que se pasa como argumento. El objeto a modificar debe ser cargado con el método load() o get() . |
void remove(Object o) | Elimina de la base de datos el objeto que se pasa como argumento. El Objeto a eliminar debe ser cargado con el método load() o get() . |
Al insertar un objeto puede producirse una excepción en caso de que se cree un objeto que necesite de otro, como Inscription necesita un estudiante y un curso (TransientPropertyValueException
, al hacer el método persist()
)
Además dichas operaciones puede dar una excepción en caso de que se intente actualizar o eliminar un objeto que no exista en la base de datos, o se intente almacenar un objeto ya existente (ConstraintViolationException
). Esta excepción se lanza en el método commit()
.
SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
try{
Department dep = new Department();
dep.setDeptNo((byte) 70);
dep.setDName("INFORMÁTICA");
dep.setLoc("TOLEDO");
session.save(dep);
tx.commit();
} catch(ConstraintViolationException e){
//...
}
session.close();
SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
try{
Employee em = (Employee) session.load(Employee.class, (short) 7369);
em.setSalary(em.getSalary() + 1000);
session.update(em);
tx.commit();
} catch(ObjectNotFoundException o){
// ...
} catch(ConstraintViolationException c){
// ...
}
session.close();
SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
try{
Employee em = (Employee) session.load(Employee.class, (short) 7369);
session.delete(em);
tx.commit();
} catch(ObjectNotFoundException o){
// ...
} catch(ConstraintViolationException c){
// ...
}
session.close();
Consultas¶
Hibernate soporta un lenguaje de consulta orientado a objetos denominado HQL (Hibernate Query Language) fácil de usar pero potente a la vez. Este lenguaje es una extensión orientada a objetos de SQL. Las consultas HQL y SQL nativas son representadas con una instancia de la interfaz Query
(del paquete org.hibernate
). Esta interfaz ofrece métodos para ligar parámetros, manejo del conjunto resultado, y para la ejecución de la consulta real. Siempre obtiene una Query
utilizando el objeto Session
actual.
Para crear una query se utilizaría el método createQuery(String sql)
de la interfaz SharedSessionContract
que implementa la Session
:
Algunos métodos importantes de esta interfaz son:
Método | Descripción |
---|---|
Iterator iterate() | Devuelve en un objeto Iterator el resultado de la consulta. |
List list() | Devuelve el resultado de la consulta en un List |
Query setFetchSize(int size) | Fija el número de resultados a recuperar en cada acceso a la base de datos al valor indicado en size. |
int executeUpdate() | Ejecuta la sentencia de modificación o borrado. Devuelve el número de entidades afectadas |
String getQueryString() | Devuelva la consulta en un String |
Object uniqueResult() | Devuelve un objeto (cuando sabemos que la consulta devuelve un objeto) o nulo si la consulta no devuelve resultados |
Query setCharacter(int position, char value) Query setCharacter(String name, char value) | Asigna el value indicado en el método a un parámetro de tipo char. position indica la posición del parámetro dentro de la consulta, empieza en 0. name es el nombre (se indica :name ) del parámetro dentro de la consulta |
Query setDate(int position, Date value) Query setDate(String name, Date value) | Asigna un value a un parámetro de tipo Date . |
Query setDouble(int position, double value) Query setDouble(String name, double value) | Asigna value a un parámetro de tipo decimal. |
Query setInteger(int position, int value) Query setInteger(String name, int value) | Asigna value a uin parámetro de tipo entero |
Query setString(int position, String value) Query setString(String name, String value) | Asigna un value a un parámetro de tipo VARCHAR |
Query setParameterList(String name, Collection values) | Asigna una colección de valores al parámetro cuyo nombre se indica en name |
Query setParameter(String name, Object value) | Asigna un valor al parámetro indicado en name |
Consulta la API de Hibernate
HQL¶
Hibernate Query Language (HQL) es un lenguaje bastante similar a SQL, pero en lugar de tablas se trata de clases y en lugar de columnas se trata de propiedades o atributos de una clase.
Las consultas HQL son consultas independientes de la base de datos porque las consultas HQL se convierten internamente en consultas SQL específicas de la base de datos utilizando la clase Dialect
mencionada en el archivo hibernate-cfg.xml.
Usar este lenguaje puede tener las siguientes ventajas:
- Independiente de la base de datos
- Fácil de aprender para el programador Java
- HQL soporta totalmente las consultas polimórficas. Es decir, junto con el objeto que se devuelve como resultado de una consulta, se devolverán todos los objetos secundarios (objetos de subclases) del objeto dado. Se tiene todo el modelo.
Las siguientes clausulas a usar en HQL son:
Clausula | Ejemplo | Descripción |
---|---|---|
FROM | FROM Employee | Devuelve el objeto completo que representa la clase |
AS | FROM Employee AS E | Indica un alias para acceder de forma sencilla a las propiedades de la clase (se puede omitir e indicar directamente el alias) |
SELECT | SELECT e.firstName FROM Employee as e | Devuelve las propiedades de la clase específicas del objeto |
WHERE | FROM Employee AS E WHERE E.firstName like 'A%' | Indica una condición para filtrar el conjunto de objetos. |
ORDER BY | FROM Person P WHERE P.id > 10 ORDER BY P.salary DESC | Se indica para ordenar la consulta. Puede ir seguido de ASC si se desea un orden ascendente (se puede omitir ya que es el valor por defecto) o DESC si, por el contrario, se quiere un orden descendente |
GROUP BY | SELECT SUM(P.salary), P.firstName FROM Person P GROUP BY P.firstName | Se usa para agrupar los datos de una consulta. Normalmente se usa para añadir un valor agregado. |
INSERT | INSERT INTO Employee (Salary, Loc) VALUES (1000, 'Cádiz') | Crea objetos de una entidad |
UPDATE | UPDATE Employee E SET E.salary = E.salary * 1000 WHERE E.ID = 10 | Actualiza los objetos |
DELETE | DELETE Employee E WHERE E.ID = 10 | Elimina objetos |
Se puede utilizar diferentes entidades separadas por coma ,
lo que producirá un cross join: from Department d, Employee e
.
Las funciones de agregación, que normalmente van con la clausula GROUP BY
, son:
- COUNT: Se utiliza para contar el número de objetos resultantes:
SELECT COUNT(*) FROM Employee E GROUP BY E.Loc
. - SUM: Suma los valores de una propiedad específica:
SELECT SUM(E.salary) FROM Employee E GROUP BY E.Loc
. - AVG: Calcula el valor promedio de una columna específica:
SELECT AVG(E.salary) FROM Employee E GROUP BY E.Loc
. - MIN/MAX: Encuentra el valor mínimo/máximo:
SELECT MIN(E.salary) FROM Employee E
. - COALESCE: Comprueba si el primer valor indicado es null, en caso de serlo, comprueba el segundo valor, y sí este lo es, devuelve el siguiente, así sucesivamente. Por ejemplo, en la expresión
coalesce(e.salary, null, 0)
, comprueba si salary es null, si no lo es devuelve su valor, y en caso de serlo comprueba el siguiente valor, que si es null, comprueba el siguiente valor, hasta encontrar uno de no es null, como en este caso, que si salary es null, devolverá 0, ya que el segundo argumento es null.
HQL también admite las consultas JOIN
con las clausulas INNER JOIN
, LEFT JOIN
, RIGHT JOIN
, FETCH JOIN
, etc.
En el siguiente ejemplo se va a recorrer a todos los empleados que tengan un salario mayor al indicado como parámetro:
String hql = "FROM Employee e where e.salary > :salary";
Query<Employee> = session.createQuery(hql, Employee.class);
query.setInteger("salary", 1000);
List<Employee> = query.list();
Consultas con parámetros¶
Hibernate soporta parámetros con nombres y parámetros de estilo JDBC (?
) en las consultas HQL. Los parámetros son enumerados desde 0, es decir, el primer parámetro que aparece estará en la posición 0, el siguiente en el 1, así sucesivamente. El uso de parámetros nombrados tiene una serie de ventajas:
- son insensibles al orden en que aparecen en la cadena de consulta.
- pueden aparecer múltiples veces en la misma petición.
- son autodocumentados.
Para asignar valores a los parámetros se utilizan los métodos setXXX
, vistos en la anterior tabla., siendo setParameter()
la sintaxis simple.
String hql = "from Employees emp where emp.department.deptNo = ? and emp.oficio = ?";
Query q = session.createQuery(hql);
q.setParameter(0, (byte) 10);
q.setParameter(1, "Director");
El ejemplo anterior se puede hacer con parámetros nombrados y con sus métodos específicos para cada dato:
String hql = "from Employees emp where emp.department.deptNo = :ndep and emp.oficio = :ofi";
Query q = session.createQuery(hql);
q.setInteger('ndep', (byte) 10);
q.setString('ofi', "Director");
Incluso podemos pasarle una lista de parámetros:
List<Byte> numbers = new ArrayList<>();
numbers.add((byte) 20);
numbers.add((byte) 20);
String hql = "from Employees emp where emp.department.deptNo in (:listaDep) order by emp.department.deptNo";
Query q = session.createQuery(hql);
q.setParameterList("listDep", numbers);
Consultas sobre clases no asociadas¶
Si queremos recuperar los datos de una consulta en la que intervienen varias tablas y los atributos no están asociados a ninguna clase podemos utilizar la clase object. Los resultados se reciben en un array de objetos, donde el primer elemento del array se corresponde con la primera clase que ponemos a la derecha de FROM, el siguiente elemento con la siguiente clase, y así sucesivamente.
String hql = "from Employee e, Department d where e.department.deptNo = d.deptNo order by apellido"
Query q = session.createQuery(sql);
Iterator iterate = q.iterate();
Object[] par;
Employee employee;
Department department;
while(q.hasNext()){
par = (Object[]) iterate.next();
employee = (Employee) par[0];
department = (Department) par[1];
// ...
}
Supongamos, ahora, que se desea obtener el nombre de departamento, su número de empleados y el salario medio. Como los datos de esta consulta no están asociados a ninguna clase, se puede crear una y utilizarla sin la necesidad de mapearla.
String hql = "select new package.Totales("
+ " d.deptNo, count(e.empNo), coalasce(ave(e.salario), 0)), d.nombre) "
+ " from Employees as e right join e.departments as d "
+ " group by d.deptNo, d.nombre";
Query q = session.createQuery(hql);
Iterator i = q.iterate();
Totales t;
while(t.hasNext()){
t = (Totales) q.next();
System.out.println(t);
}
Records¶
En Hibernate, no es posible utilizar records como clases de identidad, ya que es necesario tener los métodos setters para la modificación de la información que obtenemos de la base de datos. Sin embargo, a través del lenguaje JPQL o HPL, podemos obtener una consulta y envolverla en una clase record.
TypedQuery<BookWithAuthorNamesRecord> q =
em.createQuery(
"SELECT new org.thoughts.on.java.model.BookWithAuthorNamesRecord("
+ "b.id, b.title, b.price, concat(a.firstName, ' ', a.lastName)) "
+ " FROM Book b JOIN b.author a "
+ " WHERE b.title LIKE :title",
BookWithAuthorNamesRecord.class);
q.setParameter("title", "%Hibernate Tips%");
List<BookWithAuthorNamesRecord> books = q.getResultList();
Otra solución es en la clase de entidad, indicarle que al realizar la consulta, mapee el resultado a la clase record deseada:
@SqlResultSetMapping(
name = "BookWithAuthorNamesRecordMapping",
classes = @ConstructorResult(
targetClass = BookWithAuthorNamesRecord.class,
columns = { @ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "title"),
@ColumnResult(name = "price"),
@ColumnResult(name = "authorName")}))
Proyecto Students con Hibernate¶
Vamos ahora a modificar nuestro proyecto Students para añadirle todo lo aprendido. En primer lugar, eliminamos el persistence.xml y añadimos un fichero hibernate.cfg.xml:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="">
<property name="hibernate.connection.drive_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/students</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<mapping resource="orm/Course.xml"/>
<mapping resource="orm/Inscription.xml"/>
<mapping resource="orm/Student.xml"/>
</session-factory>
</hibernate-configuration>
Luego creamos los ficheros xml de mapeo (en caso de usar anotaciones, no se deben modificar):
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.irudev.data.entity.Course" table="course">
<id name="id" type="java.lang.Integer">
<column name="id"/>
<generator class="identity"/>
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="credits" type="int">
<column name="credits"/>
</property>
</class>
</hibernate-mapping>
<!-- estudiante.orm.xml -->
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.irudev.data.entity.Student" table="student">
<id name="id" type="java.lang.Integer">
<column name="id"/>
<generator class="identity"/>
</id>
<property name="name" type="string">
<column name="name" />
</property>
<property name="age" type="int">
<column name="age"/>
</property>
</class>
</hibernate-mapping>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.irudev.data.entity.Inscription" table="inscription">
<id name="id" type="java.lang.Long">
<column name="id"/>
<generator class="identity"/>
</id>
<many-to-one name="student" class="com.irudev.data.entity.Student">
<column name="idStudent"/>
</many-to-one>
<many-to-one name="course" class="com.irudev.data.entity.Course">
<column name="idCourse"/>
</many-to-one>
</class>
</hibernate-mapping>
Añadimos nuestro Singleton que controle nuestro acceso a las sesiones de Hibernate:
public class HibernateUtil {
private static HibernateUtil instance;
private SessionFactory factory;
private Session session;
private HibernateUtil(){}
private void buildSessionFactory(){
Configuration configuration = new Configuration().configure();
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
factory = configuration.buildSessionFactory(registry);
}
private void openSession(){
if(factory == null){
buildSessionFactory();
}
if(session == null || !session.isOpen()){
session = factory.openSession();
}
}
public void closeSession(){
if(session != null){
session.close();
}
if(factory != null){
factory.close();
}
}
public Session getCurrentSession(){
if(session == null){
openSession();
}
return session;
}
public static HibernateUtil getInstance(){
if(instance == null){
instance = new HibernateUtil();
}
return instance;
}
}
Por último, modificamos nuestros modelos:
public class JDBCCourseRepository implements CourseRepository {
private final HibernateUtil manager;
public JDBCCourseRepository(HibernateUtil manager) {
this.manager = manager;
}
@Override
public Course getById(Integer id) {
return manager.getCurrentSession().get(Course.class, id);
}
@Override
public List<Course> getAll() {
return manager.getCurrentSession().createQuery("from Course c", Course.class).list();
}
@Override
public boolean save(Course course) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().persist(course);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
@Override
public boolean update(Course course) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().merge(course);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
@Override
public boolean delete(Course course) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().remove(course);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
}
public class JDBCStudentRepository implements StudentRepository {
private final HibernateUtil manager;
public JDBCStudentRepository(HibernateUtil manager){
this.manager = manager;
}
@Override
public Student getById(Integer id) {
return manager.getCurrentSession().get(Student.class, id);
}
@Override
public List<Student> getAll() {
return manager.getCurrentSession().createQuery("from Student s", Student.class).list();
}
@Override
public boolean save(Student student) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().persist(student);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
@Override
public boolean update(Student student) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().merge(student);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
@Override
public boolean delete(Student student) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().remove(student);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
}
public class JDBCInscriptionRepository implements InscriptionRepository {
private final HibernateUtil manager;
public JDBCInscriptionRepository(HibernateUtil manager) {
this.manager = manager;
}
@Override
public Inscription getById(Integer id) {
return manager.getCurrentSession().get(Inscription.class, id);
}
@Override
public List<Inscription> getAll() {
return manager.getCurrentSession().createQuery("from Inscription i", Inscription.class).list();
}
@Override
public boolean save(Inscription inscription) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().persist(inscription);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
@Override
public boolean delete(Inscription inscription) {
try{
Transaction tx = manager.getCurrentSession().beginTransaction();
manager.getCurrentSession().remove(inscription);
tx.commit();
return true;
}catch (Exception e){
return false;
}
}
}