====== Optimización ======
Uno de los mayores problemas que nos podemos encontrar al usar un ORM es la lentitud que puede tener respecto a una aplicación realizada directamente con JDBC.
Cuando realizamos una aplicación mediante JDBC podemos determinar el número de SQL que van a lanzarse y optimizar cada una de ellas. Sin embargo, desde Hibernate inicialmente no podemos hacerlo ya que es Hibernate el que se encarga de realizar todas las SQL por nosotros. El no controlar las SQL nos puede llevar a problemas de rendimiento en nuestra aplicación.
Por suerte para nosotros y gracias a la potencia de Hibernate podemos ajustar mucho **cuántas SQLs** y **qué SQLs** lanza Hibernate. El problema de ello es que deberemos conocer más profundamente cómo funciona Hibernate con el coste que ello lleva asociado.
===== Modelo =====
En los ejemplos que vamos a realizar van a usarse las siguientes clases Java y tablas, que sólo mostraremos en formato UML. No vamos a poner el código fuente ni los ficheros de hibernate de mapeo ya que aún no han sido explicadas en lecciones anteriores todas las características que usan.
==== Modelo de Java ====
El modelo de clases Java es el siguiente:
class Profesor
Profesor : int id
Profesor : String nombre
Profesor : String ape1
Profesor : String ape2
class CorreoElectronico
CorreoElectronico: int idCorreo
CorreoElectronico: String direccionCorreo
Profesor "1" -- "0..n" CorreoElectronico: correosElectronicos
==== Modelo de Tablas ====
El modelo de tablas es el siguiente:
class Profesor <
>
Profesor : INTEGER id
Profesor : VARCHAR nombre
Profesor : VARCHAR ape1
Profesor : VARCHAR ape2
class CorreoElectronico <
>
CorreoElectronico: INTEGER idCorreo
CorreoElectronico: VARCHAR direccionCorreo
CorreoElectronico: INTEGER idProfesor
Profesor "1" -- "0..n" CorreoElectronico
===== =====
Veamos ahora dos optimizaciones que podemos realizar en hibernate:
* [[#El problemas de las "n+1" SELECTs]]: Mejoraremos el rendimiento minimizando **cuántas SQL** lanza Hibernate.
* [[#Consultas nativas]]: Mejoraremos el rendimiento ajustanto **qué SQLs** lanza Hibernate
===== El problemas de las "n+1" SELECTs =====
Uno de los mayores problemas de los ORM es el problema de los "n+1" SELECTs. Este problema consiste en que al lanzar un consulta con HQL que retorna //n// filas, el ORM lanza n+1 consultas SQL de SELECT. Como podemos imaginar ésto genera unas perdidas de rendimiento brutales.
Este es el mayor problema de rendimiento al que nos podemos enfrentar con Hibernate así que será necesario tenerlo siempre en cuenta
Veamos ahora un ejemplo con Hibernate de este problema.
Query query = session.createQuery("SELECT p FROM Profesor p");
List profesores = query.list();
for (Profesor profesor : profesores) {
System.out.println(profesor.toString());
for (CorreoElectronico correoElectronico : profesor.getCorreosElectronicos()) {
System.out.println("\t"+correoElectronico);
}
}
Este código lo único que hace es mostrar todos los profesores y para cada profesor mostrar todas sus direcciones de correo. Ësto lo hemos realizado lanzado únicamente una consulta HQL contra Hibernate.
Al ejecutar el programa y comprobar las SELECTs de SQL que se han lanzado podemos ver coóo se lanza una primera consulta para obtener todos los profesores pero posteriormente se lanza una consulta adicional por cada profesor para obtener los correos electrónicos de cada profesor. Es decir que se ejecutan "''n+1''" SELECTs siendo "''n''" el número de filas que retorna la primera consulta, en nuestro caso el número de profesores.
==== Solucion con left join fetch ====
La solución más sencilla es modificar la consulta de HQL para que cargue también todos los correos electrónicos. Para ello deberemos hacer un ''LEFT JOIN FETCH'' entre los profesores y los correos electrónicos.
La consulta HQL que realiza el ''LEFT JOIN FETCH'' entre los profesores y los correos electrónicos es la siguiente:
SELECT p FROM Profesor p LEFT JOIN FETCH p.correosElectronicos
Como vemos el ''LEFT JOIN FETCH'' se hace entre la tabla principal y la propiedad de la que queremos que se carguen todos los datos, en nuestro caso la propiedad ''Profesor.correosElectronicos''.
El código Java en este caso queda de la siguiente forma:
Query query = session.createQuery("SELECT p FROM Profesor p LEFT JOIN FETCH p.correosElectronicos");
List profesores = query.list();
for (Profesor profesor : profesores) {
System.out.println(profesor.toString());
for (CorreoElectronico correoElectronico : profesor.getCorreosElectronicos()) {
System.out.println("\t"+correoElectronico);
}
}
Vemos que sólo hemos modificado la línea 1 con la nueva consulta.
Si ejecutamos ahora el código podremos ver en la consola que se ha lanzado **unicamente** la siguiente ''SELECT'' de SQL, por lo que se ha solucionado el problema.
select profesor0_.Id as Id0_0_, correosele1_.IdCorreo as IdCorreo1_1_, profesor0_.nombre as nombre0_0_, profesor0_.ape1 as ape3_0_0_, profesor0_.ape2 as ape4_0_0_, correosele1_.direccionCorreo as direccio2_1_1_, correosele1_.idProfesor as idProfesor1_1_, correosele1_.idProfesor as idProfesor0_0__, correosele1_.IdCorreo as IdCorreo0__ from Profesor profesor0_ left outer join CorreoElectronico correosele1_ on profesor0_.Id=correosele1_.idProfesor
En la SQL podemos ver como se realiza un //Left Outer Join// entre la tabla ''Profesor'' y la tabla ''CorreoElectronico''
Desgraciadamente con ésto no es suficiente. Si ejecutamos el código veremos que se repiten los objetos ''Profesor''. ¿Porqué?
El lenguaje SQL no permite consultar jerárquicas , así que lo que hace es retornar todos los profesores con todos los correos , por lo que el mismo profesor está repetido tantas veces como correos.
Hibernate desgraciadamente no elimina esa duplicidad así que debemos explicitamente desde Java eliminar los duplicados.
Para eliminar los objetos duplicados usaremos el truco de pasarlos todos a un java.util.Set|Set de Java , ya que por definición no permite objetos duplicados, a diferencia de un java.util.List|List que si que permite duplicados y ya sin duplicados, volver a añadirlos a la lista.
Query query = session.createQuery("SELECT p FROM Profesor p LEFT JOIN FETCH p.correosElectronicos");
List profesores = query.list();
Set profesoresSinDuplicar = new LinkedHashSet(profesores);
profesores.clear();
profesores.addAll(profesoresSinDuplicar);
for (Profesor profesor : profesores) {
System.out.println(profesor.toString());
for (CorreoElectronico correoElectronico : profesor.getCorreosElectronicos()) {
System.out.println("\t"+correoElectronico);
}
}
Vemos que en la línea 4 se añaden los datos de la lista al java.util.Set|Set , el cual eliminará los duplicados, se borra la lista original en la línea 5 y finalmente en la línea 6 se vuelven a poner en la lista pero ya sin duplicados.
Por último queda explicar porqué usamos un java.util.LinkedHashSet|LinkedHashSet en vez de un java.util.HashSet|HashSet. Simplemente porque el java.util.LinkedHashSet|LinkedHashSet nos garantiza que al obtener los datos (línea 6) estarán en el mismo orden en el que se insertaron lo que hará que se mantenga el mismo orden de la lista original, cosa muy necesaria si en la HQL se uso un ''ORDER BY''.
Mas información en:
* [[https://community.jboss.org/wiki/HibernateFAQ-AdvancedProblems?_sscc=t#Hibernate_does_not_return_distinct_results_for_a_query_with_outer_join_fetching_enabled_for_a_collection_even_if_I_use_the_distinct_keyword|Hibernate does not return distinct results for a query with outer join fetching enabled for a collection (even if I use the distinct keyword)?]]
* [[https://hibernate.atlassian.net/browse/HHH-1076|FETCH JOIN with DISTINCT returns duplicate values]]
* [[http://www.javalobby.org/java/forums/t68781.html|How to remove duplicate objects returned in Hibernate when using HQL?]]
==== Solucion con Lazy Loading ====
Hasta ahora no hemos hablado del Lazy Loading ( o carga perezosa en castellano ). El Lazy Loading consiste en que cuando lanzamos una consulta HQL , Hibernate por defecto intenta cargar el mínimo de datos posible ya que quizás no los necesitemos todos y sea una perdida de recursos.
Debido a las relaciones que hay entre los distintos objetos el cargar un objeto Java podría implicar cargar cientos de objetos relacionados con él. Imaginemos el siguiente diagrama UML con relaciones entre clase Java.
class Centro
class Aula
class Familia
class Ciclo
class Modulo
class Profesor
class Alumno
Centro "1" -- "1..n" Aula
Centro "*" -- "1..n" Familia
Familia "1" -- "1..n" Ciclo
Familia "1" -- "1..n" Profesor
Ciclo "1" -- "1..n" Modulo
Modulo "0..n" -- "1..n" Profesor
Modulo "1..n" -- "0..n" Alumno
Centro "*" -- "1..n" Profesor
Nos puede haber parecido erróneo en el ejemplo inicial que Hibernate al cargar un profesor no haya cargado también sus correos electrónicos, -l fin y al cabo son datos del profesor- pero siguiendo con esa lógica veamos que pasaría con el ejemplo que hemos mostrado en el diagrama UML.
* Si cargamos un objeto ''Centro''porque queremos saber simplemente el nombre del centro, deberían cargarse sus objetos ''Aula'' , ''Familia'' y ''Profesor''
* pero si hemos cargado algún objeto de ''Familia'' también habría que cargar los objetos de ''Ciclo''
* pero si hemos cargado algún objeto de ''Ciclo'' habría que cargar también los objetos de ''Modulo''
* pero si hemos cargado algún objeto de ''Modulo'' también habría que cargar los objetos de ''Alumno''
Es decir que con una simple consulta HQL como la siguiente:
SELECT c FROM Centro c
Se cargaría toda la base de datos en memoria, cosa que también empeoraría el rendimiento de la aplicación al saturar la memoria del servidor de aplicaciones.
Así que Hibernate hace una carga perezosa ( Lazy Loading ) cargando los datos sólo a medida que los vamos necesitando, lo que nos llevaría al problema del ''n+1'' SELECTs. Pero en este caso no sería sido un problema ya que solo queremos saber el nombre de los centros y NO todas las propiedades del centro como sus profesores, familias o aulas.
Resumiendo, el problema del ''n+1'' SELECTs puede no ser un problema si no vamos a acceder a otros datos //hijos// de la clase que hemos solicitado. Por lo tanto Hibernate hace bien en no cargarlos todos y permitir al programador definir cómo se hace la carga.
===== Consultas nativas =====
Los creadores de Hibernate no han sido tan soberbios como para pensar que las consultas en SQL que lanza Hibernate son las mejores que se pueden hacer. Por ello hibernate permite que el usuario pueda lanzar directamente consultas SQL contra la base de datos y de esa forma hacerlo de la forma más optimizada.
==== createSQLQuery ====
La clase org.hibernate.Session|Session contiene el método org.hibernate.SharedSessionContract#createSQLQuery(java.lang.String)|createSQLQuery(java.lang.String) que retorna un objeto org.hibernate.Query|Query pero a partir de un SQL nativa de la base de datos.
La posterior llamada al método org.hibernate.Query#list()|list() retornará un ''List