sábado, 22 de mayo de 2010

Hibernate - Parte 8: HQL Segunda Parte

En el tutorial anterior vimos una introducción al lenguaje de consultas de Hibernate, el Hibernate Query Language (HQL) y vimos algunas de sus características.

También vimos como ejecutar consultas desde el editor del NetBeans, y desde código dentro de una aplicación de ejemplo.

En esta segunda parte veremos el resto de las clausulas que componen este lenguaje y seguiremos con ejemplos en código.

Lo último que vimos fue como recuperar una lista con solo algunos valores de un objeto y colocarlos en un objeto “java.util.List” usando una sentencia SELECT con la sintaxis “SELECT NEW LIST”.

Esta no es la única forma en la que podemos recuperar valores de un objeto dentro de una colección. Además tenemos la opción de recuperar una lista de objetos tipo “java.util.Map”, en donde las llaves son las columnas que queremos consultar, y los valores son los valores recuperados de la base de datos.

Por ejemplo, si queremos recuperar el "identificador", "nombre", y "password" de los usuarios, dejando estos datos en un “java.util.Map” usamos la siguiente sentencia:

SELECT new map(u.id AS identificador, u.nombre AS nombre, u.password AS pass) FROM Usuario as u


En donde vemos que ahora usamos la clausula SELECT seguido de la sentencia NEW MAP, y los valores que deseamos recuperar dentro del mapa. Dentro de esta clausula colocamos como alias lo que serán las llaves, y como valores las columnas que queremos recuperar. Así tendremos como llaves “identificador”, “nombre”, y “pass”, y como valores el “id”, “nombre”, y “password” del Usuario.

Creamos un método para comprobar la salida de esta consulta:

private void obtenNombresYPasswordsComoMapa()
{
    iniciaOperacion();

    Query query = sesion.createQuery("SELECT new map(u.id as identificador, u.nombre as nombre, u.password as pass) FROM Usuario as u ");

    List<Map> listaResultados = query.list();

    for (int i = 0; i < listaResultados.size(); i++)
    {
        Map mapa = listaResultados.get(i);

        System.out.println("Datos del mapa " + i);

        Set llaves = mapa.keySet();

        for(Iterator<String> it = llaves.iterator(); it.hasNext();)
        {
            String llaveActual = it.next();

            System.out.println("\tLlave: " + llaveActual + ", valor: " + mapa.get(llaveActual));
        }
    }

    terminaOperacion();
}


Cuando ejecutamos este método obtenemos la siguiente salida:



Podemos ver que, efectivamente, obtenemos como llave lo que colocamos como alias, y los valores de las columnas que solicitamos, de los tres Usuarios que existen en la base de datos.

El recuperar los valores dentro de listas o dentro de mapas es algo muy útil y que nos ahorrará mucho esfuerzo en nuestras aplicaciones. Sin embargo Hibernate nos proporciona una forma aún más útil que podemos usar. De hecho podemos recuperar una lista de objetos propios, el cual reciba en su constructor los valores que estamos recuperando. Esto es muy útil cuando tenemos que recuperar datos que posteriormente deberán ser mostrados en un reporte. Podríamos, por ejemplo, querer recuperar los datos de un usuario y la cantidad total de las compras que ha realizado en un sitio y colocar estos datos en objeto para luego solo extraer los datos de estos objetos en el reporte.

Crearemos una clase “UsuarioDireccion”, en el paquete modelo, que mantendrá el nombre del Usuario, su calle y código postal, y el número de permisos que tiene (aún no hemos visto las funciones de agregación, de las que COUNT forma parte, pero nos servirá para ir practicando).

La clase "UsuarioDirección" se muestra a continuación:

public class UsuarioDireccion 
{
    private String nombre;
    private String calle;
    private String codigoPostal;
    private long numeroPermisos;

    public UsuarioDireccion(String nombre, String calle, String codigoPostal, long numeroPermisos)
    {
        this.nombre = nombre;
        this.calle = calle;
        this.codigoPostal = codigoPostal;
        this.numeroPermisos = numeroPermisos;
    }

    public String getCalle()
    {
        return calle;
    }

    public void setCalle(String calle)
    {
        this.calle = calle;
    }

    public String getCodigoPostal()
    {
        return codigoPostal;
    }

    public void setCodigoPostal(String codigoPostal)
    {
        this.codigoPostal = codigoPostal;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public long getNumeroPermisos()
    {
        return numeroPermisos;
    }

    public void setNumeroPermisos(long numeroPermisos)
    {
        this.numeroPermisos = numeroPermisos;
    }
}


Como podemos ver, esta clase solo tiene un constructor que recibe los cuatro parámetros que mencioné antes.

Regresaremos una lista con los valores de UsuarioDirección, además del número de Permisos que tiene cada uno de los Usuarios, que hay en la base de datos. Estos valores serán regresados en objetos de la clase “UsuarioDireccion”. Para lograr esto usamos la siguiente sentencia:

SELECT NEW hql.modelo.UsuarioDireccion(u.nombre, d.calle, d.codigoPostal, COUNT(p)) FROM Usuario u left outer join u.direccion as d left outer join u.permisos as p GROUP BY u.id


Como vemos ahora usamos la sentencia SELECT seguida de algo que parece la invocación del constructor de la clase “hql.modelo.UsuarioDireccion”, de esta forma:

hql.modelo.UsuarioDireccion(u.nombre, d.calle, d.codigoPostal, COUNT(p))


A la cual le pasamos los cuatro parámetros que recibe el siguiente constructor:

public UsuarioDireccion(String nombre, String calle, String codigoPostal, long numeroPermisos)


En la sentencia anterior es necesario usar la clausula GROUP BY, con el identificador del Usuario, ya que estamos usando una función de agregación (COUNT). Más adelante me adentraré un poco más en esto.

Además podemos ver que estamos usando un LEFT OUTER JOIN para que nos regrese los datos de los Usuarios aunque estos no tengan ni Direcciones ni Permisos. Pueden encontrar más información de cómo funcionan los distintos joins en esta página.

Si ejecutamos esta sentencia en el NetBeans obtendremos el siguiente resultado:



Con lo que podemos ver que los Usuarios 1 y 2 tienen sus datos de Direccion y 2 Permisos cada uno, mientras que el Usuario 3 no tiene datos de Direccion, y tiene un Permiso.

Creemos un método para poder recuperar estos mismos datos desde nuestra aplicación:

private void obtenUsuariDireccion()
{
    iniciaOperacion();

    Query query = sesion.createQuery("SELECT NEW hql.modelo.UsuarioDireccion(u.nombre, d.calle, d.codigoPostal, COUNT(p)) FROM Usuario u left outer join u.direccion as d left outer join u.permisos as p GROUP BY u.nombre");

    List<UsuarioDireccion> listaResultados = query.list();

    for (int i = 0; i < listaResultados.size(); i++)
    {
        UsuarioDireccion usuarioDireccion = listaResultados.get(i);
        System.out.println("->" + usuarioDireccion.getNombre() + ", permisos: " + usuarioDireccion.getNumeroPermisos());
    }

    terminaOperacion();
}


Con lo que obtenemos la siguiente salida:



La cual podemos observar que es la misma que obtuvimos del editor HQL del NetBeans.

Con esto terminamos todo lo relativo a la clausula SELECT que Hibernate nos proporciona. Ahora veremos cómo funcionan las funciones de agregación.


Las funciones de Agregación


En el mundo de SQL las funciones de agregación trabajan sobre conjuntos de filas para dar un resultado correspondiente a ese grupo. Afortunadamente para nosotros en HQL las funciones de agregación funcionan exactamente igual que en SQL. De hecho HQL soporte las mismas funciones que SQL:

  • AVG(…), SUM(…), MIN(…), MAX(…)
  • COUNT(*)
  • COUNT(…), COUNT(DISTINCT …), COUNT(ALL …)

En el ejemplo anterior hicimos uso de la función COUNT para obtener el número de Permisos que cada Usuario tiene relacionado. Ahora bien, como se menciona en el párrafo anterior, las funciones de agregación operan sobre grupos de datos o resultados (en el caso anterior era el grupo de Usuarios que se encuentran en la base de datos) por lo tanto, cada vez que usemos una función de agregación obtendremos como resultado una única fila. Si no queremos que esto ocurra deberemos agrupar nuestros resultados (usando la clausula GROUP BY) usando el criterio apropiado.

Explicaré esto usando el ejemplo anterior. En él recuperamos la lista de Usuarios de la base de datos, obteniendo además su Direccion y el número de permisos que tienen asociados. Para recuperar este número de permisos usamos la función de agregación COUNT. Sin embargo si no hubiéramos agrupado el resultado habría sido el siguiente:



Como podemos ver solo obtuvimos una fila como resultado, en el cual se muestra el total de permisos de los Usuarios que cumplen con el criterio de búsqueda (2 del primero + 2 del segundo + 1 del tercero). Además muestra los datos de Usuario y Direccion de uno de los Usuarios, seleccionado usando algún criterio del manejador de base de datos que estamos usando.

Como podemos ver esto difiere del resultado que queríamos obtener, ya que la intención era obtener los datos de cada uno de los Usuarios y, adicional a esto, en número de permisos que tenía. Esto nos demuestra que debemos tener cuidado cuando hacemos uso de las funciones de agregación.

¿Qué podemos hacer para obtener los datos que deseamos? Pues lo que hicimos en el ejemplo anterior, o sea, agrupar los resultados obtenidos mediante algún criterio que nos parezca adecuado. En este caso como lo que queremos es obtener los datos de cada uno de los Usuarios, debemos agrupar por algún atributo de Usuario que nos parezca adecuado. En este caso podría ser el "nombre" o el "identificador". Como la vez pasada usamos el "identificador", ahora usemos el "nombre" para ver que también nos da el resultado esperado:



El resto de las funciones de agregación funcionan de forma similar, así que dejaremos está parte hasta aquí para saltar a la explicación referente a la clausula WHERE.


La clausula WHERE


Esta clausula nos permite reducir o limitar el número de registros que recuperamos al hacer una consulta a nuestra base de datos, seleccionando aquellos registros que cumplan con la condición establecida dentro de esta clausula.

Dentro de esta clausula podemos colocar condiciones como que un valor sea igual a otro, que este dentro de ciertos rangos, que un objeto sea de cierta clase, etc. Básicamente lo mismo que podemos hacer con una sentencia WHERE de SQL estándar, pero agregándole algunos detalles extra para el trabajo con objetos.

El trabajo con la clausula WHERE es en realidad muy sencillo, solo debemos colocar el nombre del atributo que queremos restringir, y la restricción que tendrá. Podemos colocar más de una restricción, separando cada una con la palabra reservada AND.

Si queremos limitar el resultado de la consulta para regresar los usuarios de la base de datos, limitándonos a los que su “username” sea “usr456” restringiremos la consulta de la siguiente forma:

FROM Usuario u WHERE u.username='usr456'


Como el atributo que estamos restringiendo es una cadena colocamos el valor esperado entre comillas simples.

Al ejecutar esta consulta en el editor HQL de NetBeans obtenemos el siguiente resultado:



Como podemos ver, la restricción funcionó sin problemas.

Ahora supongamos que queremos restringir para recuperar los permisos cuyo “id” sea mayor a 2 y menor a 5 (o sea, 3 y 4 ^-^). La consulta tendría que ser de la siguiente forma:

FROM Permiso p WHERE p.id> 2 AND p.id < 5


Obteniendo el siguiente resultado:



Estas restricciones pueden extenderse para valores de propiedades de objetos relacionados (componentes), como por ejemplo:

FROM Usuario u WHERE u.direccion.codigoPostal = ‘123456’




También podemos obtener los valores de los Usuarios que tienen una Direccion asociada, o sea, cuya Direccion no está nula:

FROM Usuario u WHERE u.direccion IS NOT NULL




Algo interesante en HQL es que el operador "=" no solo sirve para comparar valores de atributos, sino que también nos sirva para comparar instancias:

FROM Usuario u1, Usuario u2 WHERE u1.direccion  = u2.direccion




En el ejemplo anterior se recuperan las instancias de Usuario donde usuario 1 tiene la misma Dirección que usuario 2, este ejemplo es poco útil pero sirve para probar el punto de la comparación de instancias. Si tuviéramos una base de datos más compleja tal vez podríamos encontrar un uso más adecuado a esta funcionalidad.

Otra cosa interesante es que Hibernate proporciona una propiedad especial llamada “class”, que nos ayuda cuando trabajamos con persistencia polimórfica (o sea cuando tenemos clases persistentes que heredan de otras clases persistentes, hablaremos de esto en un futuro tutorial ya que es un tema un poco amplio como para tocarlo aquí mismo).

Por ejemplo, imaginemos que tenemos una estructura de clases de esta forma:



Donde tenemos una clase Animal de la cual extienden dos clases: Domestico y Salvaje. En código sería algo más o menos así:

public abstract class Animal
{
}

public class Domestico extends Animal
{
}

public class Salvaje extends Animal
{
}


Si quisiéramos recuperar todas los Animales guardados en nuestra base de datos, sin importar si son Domesticos o Salvajes lo haríamos a través de una consulta como la siguiente:

FROM Animal a


Y listo, con esto recuperaríamos todos los Animales, dentro de una lista de referencias de tipo Animal, de la siguiente forma:

List<Animal> listaAnimales;


¿Qué pasaría si quisiéramos seguir usando la referencia anterior pero solo recuperar los Animales Domesticos de la base de datos? Es ahí donde entra en juego la propiedad especial “class” que nos proporciona Hibernate. Por lo que la consulta quedaría de la siguiente forma:


FROM Animal a WHERE a.class = Domestico
Tendrán que confiar un poco en mí sobre el funcionamiento de esto ya que, como mencioné antes, explicar las consultas polimórficas requiere un tutorial completo ya que tiene bastantes detalles que explicar.


Expresiones


Las expresiones se usan dentro de la clausula WHERE e incluyen las siguientes:

  • matemáticas: “+”, “-“, “*”, “/
  • comparación binaria: “=”, “>=”, “<=”, “<>”, “!=”, “LIKE
  • lógicos: “AND”, “OR”, “NOT
  • IN”, “NOT IN”, “BETWEEN”, “IS NULL”, “IS NOT NULL”, “IS EMPTY”, “IS NOT EMPTY”, “MEMBER OF”, y “NOT MEMBER OF
  • case simple: “CASE… WHEN… THEN… ELSE… END
  • case searched: “CASE WHEN… THEN… ELSE… END
  • concatenación de cadenas: “… || …” o “CONCAT(…,…)
  • CURRENT_DATE()”,”CURRENT_TIME()”, y “CURRENT_TIMESTAMP()
  • SECOND(…)”, “MINUTE(…)”, “HOUR(…)”, “DAY(…)” , “MONTH(…)”, y “YEAR(…)
  • SUBSTRING()”, “TRIM()”, “LOWER()”, “UPPER()”, “LENGTH()”, “LOCATE()”, “ABS()”, “SQRT()”, “BIT_LENGTH()”, “MOD()
  • COALESCE()” y “NULLIF()
  • STR()
  • CAST(… AS …)”, y “EXTRACT(… FROM …)
  • Funciones HQL que toman expresiones con valores de tipo colección: “SIZE()”, “MINELEMENT()”, “MAXELEMENT()”, “MININDEX()”, “MAXINDEX()”, junto con las funciones especiales “ELEMENTS()” e “INDICES()” que pueden ser cuantificadas usando “SOME”, “ALL”, “EXISTS”, “ANY”, e “IN
  • Cualquier función escalar soportado por el SQL del manejador de base de datos como “SIGN()”, “TRUNC()”, “RTRIM()”, y “SIN()
  • Parámetros posicionales estilo PreparedStatement de JDBC (usando “?”)
  • Parámetros con nombre (usando “:”)
  • Literales SQL: ‘foo’, 123, 6.66E+2, ‘1970-01-01 10:00:01.0
  • Constantes java: (public static final)


La Clausula ORDER BY


La lista regresada por una consulta puede ser ordenada por una propiedad de uno de los componentes de las clases regresadas usando esta clausula. Podemos indicar si queremos que los resultados sean ordenados de forma ascendente (de menor a mayor) o descendente (de mayor a menor) con “asc” (la default) o “desc”, respectivamente.

Por ejemplo, si recuperamos la lista de todos los Permisos existentes en la base de datos, con la siguiente clausula:

FROM Permiso p


El resultado que obtenemos queda de la siguiente forma:



Como vemos los resultados son ordenados por su "id", pero ¿y si quisiéramos regresarlos ordenamos por "nombre" para mostrarlos a nuestros clientes? En ese caso tendríamos que usar la clausula “ORDER BY” en el atributo “nombre” de la clase “Permiso”, de la siguiente forma:

FROM Permiso p ORDER BY p.nombre


Obteniendo el siguiente resultado:



Como vemos ahora los resultados están ordenamos por el "nombre" del Permiso de forma ascendente que, como mencioné, es la forma por default. Si quisiéramos ordenarlos de forma descendente nuestra consulta quedaría así:



Si queremos ordenar por más de un atributo, colocamos la lista de atributos en esta clausula separados por comas: ORDER BY atributo1, atributo2, atributo3, … atributo n.


La Clausula GROUP BY


Un query que regresa valores de agregación puede ser agrupado por cualquier propiedad de una clase o componente regresado por dicho query.

Si por ejemplo, queremos obtener en número de Permisos de cada tipo que tenemos en la base de datos, haríamos una consulta como la siguiente:

SELECT p.estatus, COUNT(p.estatus)  FROM Permiso p  GROUP BY p.estatus


Si colocamos esta consulta en el NetBeans obtenemos el siguiente resultado:



Como este resultado no nos dice mucho ^-^! Crearemos un método que ejecute esta consulta:

private void cuentaPermisos()
{
    iniciaOperacion();

    Query query = sesion.createQuery("SELECT p.estatus, COUNT(p.estatus) FROM Permiso p GROUP BY p.estatus");

    List<Object[]> datos = query.list();

    for (int i = 0; i < datos.size(); i++)
    {
        Object[] datoActual = datos.get(i);

        System.out.println(datoActual[0] + "(" + datoActual[1] + ")");
    }

    terminaOperacion();
}


Al ejecutar este método obtenemos la siguiente salida:



Que como podemos ver nos muestra el número de permisos de cada tipo que tenemos en la base de datos.

De la misma forma que con la clausula “ORDER BY”, si queremos agrupar por más de un atributo, colocamos la lista de atributos en esta clausula separados por comas: GROUP BY atributo1, atributo2, atributo3, … atributo n.

Si queremos usar la clausula ORDER BY en una consulta en la que también usemos la clausula GROUP BY, esta ultima debe aparecer antes que ORDER BY, de la siguiente forma:

SELECT … FROM … WHERE … GROUP BY… ORDER BY…


De lo contrario no obtendremos algún mensaje de error, pero podemos obtener resultados inesperados.


Subconsultas


En las bases de datos que soportan subselects, Hibernate soporta subconsultas dentro de consultas. Las subconsultas deben estar encerradas entre paréntesis. Aún las subconsultas correlacionadas (subconsultas que hacen referencia a un alias en la consulta externa) son permitidas siempre que la base de datos lo haga.

Por ejemplo, si quisiéramos obtener todos los Usuarios de la base de datos que tengan al menos un Permiso en estado ACTIVO (cuyo "estatus" sea igual a 1) lo haríamos con la siguiente consulta:

SELECT DISTINCT(u) FROM Usuario u inner join u.permisos as p WHERE p in (SELECT p FROM Permiso p WHERE p.estatus = 1)   


Si colocamos esta consulta en el editor HQL de NetBeans obtenemos el siguiente resultado:



Como podemos ver solo el Usuario con id = 2 tiene Permisos con el estatus de ACTIVO.

Para terminar esta sección diré que las subconsultas solo pueden estar dentro de clausulas SELECT o WHERE.


Sintaxis Row Value Constructor


HQL soporta el uso de la sintaxis de SQL ANSI “row value constructor”, algunas veces llamado sintaxis “AS tuple”, aun cuando el manejador de base de datos que estemos usando podría no soportar dicha notación. Aquí, por lo general nos referimos a comparaciones multi-valuadas, típicamente asociadas con componentes. Por ejemplo, consideremos nuestra entidad “Usuario”, la cual define un componente “Direccion”:

FROM Usuario u WHERE u.direccion.calle = 'Calle1' AND u.direccion.codigoPostal = '12345'


La anterior es una consulta valida aunque un poco larga. La documentación de Hibernate dice que podemos hacerla más corta con la sintaxis row value constructor, de la siguiente forma:

SELECT u.direccion FROM Usuario u WHERE u.direccion=('Calle1', '12345') 


Sin embargo no he logrado hacerlo funcionar, ni al ejecutarlo desde NetBeans, ni desde una aplicación. Obteniendo el siguiente error:



Afortunadamente (para este ejemplo ^_^!) la sintaxis row value constructor también puede ser usada en subconsultas que necesitan comparar multiples valores. Por ejemplo, si queremos obtener los datos del Usuario cuya Direccion tiene la misma "calle" y "codigoPostal" que la Direccion con "id" = 2 podemos usar la siguiente consulta:

FROM Usuario u WHERE (u.direccion.calle, u.direccion.codigoPostal) in (SELECT d.calle, d.codigoPostal FROM Direccion d WHERE d.id = 2) 


Con la cual obtenemos el siguiente resultado:



Lo que nos indica que el Usuario con id = 2 es el dueño de la Direccion con id = 2 ^-^.


Componentes


La mayoría de las veces que creamos un modelo de datos, nuestras clases entidad están construidas haciendo uso de la composición de objetos, teniendo una relación de tipo HAS-A (objetos construidos en base a otros objetos) como en el caso de la relación Usuario-Direccion; en donde un Usuario tiene una Direccion y una Direccion tiene un Usuario.

Cuando tenemos una relación de este tipo, y esta relación es de Uno a Uno o de Muchos a Muchos al Objeto incluido dentro de nuestra clase le llamamos “componente”. En este Caso si estamos dentro de la clase Usuario a la referencia que tenemos a un objeto de la clase Direccion (private Direccion direccion;) la llamamos el componente “dirección” del Usuario.

HQL nos permite hacer consultas a propiedades de los componentes de nuestras clases de la misma forma en la que lo hacemos con las propiedades de nuestro objeto principal. Estos valores pueden aparecen en la clausula SELECT de la siguiente forma:

SELECT u.nombre, u.direccion.calle, u.direccion.codigoPostal FROM Usuario u


Crearemos un método llamado "obtenNombreDireccion" en nuestra clase "Main" para asegurarnos que la consulta anterior efectivamente nos devuelve los valores que hemos pedido:

private void obtenNombreDireccion()
{
    iniciaOperacion();

    Query query = sesion.createQuery("SELECT u.nombre, u.direccion.calle, u.direccion.codigoPostal FROM Usuario u");

    List<Object[]> listaResultados = query.list();

    for(int i = 0; i < listaResultados.size(); i++)
    {
        Object[] resultadoActual = listaResultados.get(i);

        System.out.println("Nombre: " + resultadoActual[0] + ", calle: " + resultadoActual[1] + ", codigo postal: " + resultadoActual[2]);
    }

    terminaOperacion();
}


Recuerden que cuando hacemos un SELECT con solo algunas propiedades de nuestra entidad estas se obtienen en una lista de arreglos de objetos, donde cada uno de los valores pedidos está almacenado en los objetos recuperados en el mismo orden en el que aparecen en la clausula SELECT; esto lo aprendimos en el tutorial anterior.

Al ejecutar el código anterior obtenemos el siguiente resultado:



El cual, como podemos ver, obtiene además del nombre del Usuario la calle y el codigoPostal de su componente "direccion".

De esta misma forma podemos usar los atributos de los componentes en las clausulas WHERE y ORDER BY:

FROM Usuario u WHERE u.direccion.codigoPostal = '12345'




SELECT u, u.direccion FROM Usuario u ORDER BY u.direccion.codigoPostal desc




Es importante recordar que, como dije antes, esto solo podemos usarlo cuando tenemos relaciones de tipo Uno a Uno o Muchos a Muchos, o sea cuando nuestro componente es una referencia simple (no es un arreglo o una lista o algún otro tipo de colección).


Tips y Trucos


Para terminar este tutorial daré una serie de trucos que pueden facilitarnos un poco la vida al trabajar con HQL en nuestras aplicaciones del día a día.


Contar en número de resultados:

Podemos contar en número de resultados que regresará una consulta sin tener que ejecutarla. Esto nos permitirá evitar obtener un OutOfMemoryError si es que pensamos que nuestra consulta regresará muchos resultados.

Por ejemplo, tenemos la siguiente consulta, que es el producto cartesiano de todos los Usuarios y todos los Permisos que tenemos en la base de datos (si, sé que no debemos hacer eso, que debemos usar joins en vez de productos cartesianos, pero no se preocupen, es solo para ejemplificar u_u):

FROM Usuario u, Permiso p


Solo para que nos demos una idea, si ejecutamos esta consulta obtendremos lo siguiente:



Para obtener el número de resultados que regresará esta consulta sin regresar los resultados, podemos usar la siguiente consulta:

SELECT COUNT(*) FROM Usuario u, Permiso p


Crearemos un método llamado cuentaResultadosConsulta para poder ejecutar la consulta anterior:

public void cuentaResultadosConsulta()
{
    iniciaOperacion();

    int numeroResultados = ((Long) sesion.createQuery("SELECT COUNT(*) FROM Usuario u, Permiso p").iterate().next()).intValue();

    System.out.println("Numero de resultados que regresara la consulta: " + numeroResultados);

    terminaOperacion();
}


El método anterior nos da el siguiente resultado:



Como podemos ver, si ejecutamos la consulta esta nos regresará 15 resultados. Podemos comprobar que esto es correcto si contamos el número de resultados que nos regresó la consulta cuando la ejecutamos hace unos momentos.


Resultados en base al tamaño de una colección:

Si queremos ordenar los resultados que nos regresa una consulta en base al número de elementos que tiene una colección podemos hacerlo usando la función de agregación COUNT dentro de la clausula ORDER BY. Por ejemplo, si queremos ordenar los Usuarios obtenidos de la base de datos, en base al número de Permisos que tiene cada uno, podemos hacerlo con la siguiente consulta:

SELECT u FROM Usuario u left join u.permisos p GROUP BY u ORDER BY COUNT(p) asc


Si ejecutamos esta consulta en el NetBeans obtendremos el siguiente resultado:



También, si nuestra base de datos soporte subconsultas, podemos colocar una condición sobre el tamaño de la condición en la clausula SELECT de la consulta. Por ejemplo si queremos obtener todos los Usuarios que tienen menos de dos Permisos, utilizamos la siguiente consulta:

FROM Usuario u WHERE size(u.permisos) < 2


Si ejecutamos esta consulta obtendremos el siguiente resultado:



Si nuestra base de datos NO soporta subconsultas podemos usar la siguiente consulta para obtener el mismo resultado que con la consulta anterior (¿alguien ha contado cuántas veces he escrito la palabra consulta en este post?):

SELECT u FROM Usuario u join u.permisos p GROUP BY u HAVING  COUNT(p) < 2  


Obteniendo el mismo resultado que con la consulta anterior:



Con esto terminamos por fin este tutorial sobre HQL ^_^, espero que les sea de utilidad.

Ahora que sabemos cómo hacer consultas con HQL veremos, en el siguiente tutorial, como utilizar todo lo que hemos aprendido dentro de nuestras aplicaciones java.

Saludos

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas:

12 comentarios:

  1. Gracias Alex son un maestro en esto desde el primer tutorial te voy siguiendo..

    Aqui un pequeño aporte por si no conocias esta pagina de recursos gratuitos me imagino que es muy conocida por todos pero aqui esta por si acaso es http;//www.illasaron.com/ tiene muy buenos recursos de varios lenguajes!!!

    ResponderEliminar
  2. Hola luchonet;

    Gracias por tu comentario, y muy interesante el sitio que comentas de http://www.illasaron.com/ de videotutoriales, lo voy a revisar

    ResponderEliminar
  3. hola que tal!!

    oye ps sta muy bien tu trabajo, solo tengo una duda en cuanto a los iiner join,

    supongamos que tengo tres clases

    /* la clase A contiene dos referencia a otros objetos de la clase b y C*/
    public class A {

    private int id;
    private B objetoB;
    private C objetoC;

    /*
    setters and getters*/
    }

    public class B {

    private int id;
    private String unaCosa;

    /* setters and getters*/
    }

    public class C {

    private int id;
    private String otraCosa;

    /* setters and getters*/
    }

    ya tengo mapeada las tres clases en el *.hbm.xml y ejecuto la siguiente query con HQL

    From A a inner join fetch a.objetoB

    y me la ejecuta correctamente me hace el inner join de la tabla A con la tabla B, pero si quiero hacer el doble inner join lo hago asi:

    FROM A a inner join fetch a.objetoB inner join fetch a.objetoC

    y aqui no me marca ninguna excepcion pero no me regresa nada, query vacia. ¿Como puedo hacer el doble inner join? =( =(

    lo curioso es que si pongo el codigo SQL que imprime hibernate en la consola y lo ejecuto directamente en la consola de la base de datos sí funciona, me trae todas las filas de mis tres tablas

    ResponderEliminar
  4. aaahh y por cierto segun mi explorador has puesto la palabra "consulta" 58 veces jajaja

    ResponderEliminar
  5. Hola Juan Carlos;

    Primero que nada gracias por contar las veces que dije "consulta". Sabia que se repetia más de 20 veces jejeje.

    Sobre lo que preguntas, yo veo la consulta bien. Lo unico que se me ocurre que pueda estar pasando son tal vez problemas con tus indices entre A y C o alguna referencia nula.

    Puedes probar usando un left join fetch en ambos casos. Sino funciona podemos entrar más a detalle en el problema para encontrar qué es lo que está pasando.

    Saludos.

    ResponderEliminar
  6. hola!!! XD XD

    ps ya esta lo de consulta con doble inner join con HQL, y tenias razon la query estaba bien, mi error estaba en que la tercera tabla (la tabla C) la cree y le meti registros manualmente directamente en la consola de la BD y nose por que no me traía los registros de esa tabla, lo que hice (y fue de pura coicidencia) fue reiniciar la base de datos (yo uso oracle 10g) y con eso tuvo.

    Pero por que no agarra los cambios hibernate hasta reiniciar la BD???

    ResponderEliminar
  7. hola Alex!!!


    oye una duda, supongamos que quiero obtener nadas el nombre del usuario y los nombres de los permisos que tiene, asi que utilizo la querde HQL:

    "select u.nombre,p.permiso.nombre from Usuario u inner join u.permisos p"

    y me la ejecuta bien, obviamente no puedeutilizar el fetch para que me regrese objetos llenos, entonces mi duda es que si hay una manera para que me los llene o tengo mapearlos yo?? y como los mapearia??

    ResponderEliminar
  8. Hola Juan Carlos;

    Perdón pero creo que no entendí bien lo que quieres hacer.

    De lo que entendí creo que para lo que quieres necesitarias construir un nuevo objeto con los datos que quieres y pasar los valores en un constructor, como en el ejemplo de

    SELECT NEW hql.modelo.UsuarioDireccion

    ¿Esto es más o menos lo que preguntas?

    Saludos

    ResponderEliminar
  9. sí mira mi duda (bueno que digo duda es dudota) es esta:

    el ejercicio que propones en la parte 7 yo lo entiendo asi:

    las tablas de usuario y permisos son de catalogo es decir que solo cuando quiera agregar un usuario agrego un registro a usuario y a direccion y si por ejemplo el gerente de sistemas inventa un nuevo permiso pues lo agrego a la tabla permisos.

    la tabla usuarios_permisos es por ejemplo yo hice una pantalla de captura para un usuario "x" para que en esta pantalla yo le muestro en un combo los usuarios dados de alta y otro combo para que vea los permisos que puede asignar ese usuario y ya lo da de alta dependiendo que usuario eligió y que permiso todo eso me sale muy bien.

    el ejercicio yo lo hice con archivos hbm por que con anotaciones me marca un error(que porteriormente te comentare) y mis entidades quedan de esta manera:

    Usuario
    1-n con UsuarioPermiso
    1-1 con Direccion

    public class Usuario implements Serializable {

    private int id;
    private String nombre;
    private String password;
    private String username;
    private Set(UsuarioPermiso) permisos = new
    HashSet(UsuarioPermiso)();
    private Direccions direccion;

    }

    UsuarioPermiso
    n-1 con Usuario
    n-1 con Permiso

    public class UsuarioPermiso {

    private int id;
    private Usuario usuario;
    private Permiso permiso;
    }

    Permiso
    **con ninguna entidad**

    public class Permiso {

    private int id;
    private int estatus;
    private String nombre;
    }

    Direccion
    ** con ninguna entidad **

    public class Direccions {

    private int id;
    private String calle;
    private Usuario usuario;
    }

    ahora bien, mi entidad UsuarioPermiso no la tengo como tú en tu ejemplo de esta parte 8, esta entidad yo la hice para la pantalla que te comente mas arriba y me funciona bien guarda la relacion entre usuario y permiso a la tabla usuarios_permisos.

    si te das cuenta en mi entidad Usuario tengo una coleccion Set de mi entidad UsuarioPermiso y asu vez esta entidad UsuarioPermiso es la que apunta a la entidad Permiso. Todos los ejemplos de HQL que pones me salen muy bien apesar de que no tenemos los dos las entidades igual.

    Ahora bien mi graaaaaaaaaaaaaaaaaan duda es:

    Supongamos que el usuario del sistema quiere ver una pantalla con todos los usuario dados de alta con sus respectivos permisos, pero nada mas su nombre y su nombre de permiso. Entonces dadas mis entidades estaras de acuerdo que si yo hago una cosulta de este modo:

    from Usuario u inner join fetch u.permisos p

    pues me va a traer tooooooodo lo de Usuario,UsuarioPermiso y Pemiso y si se tienen unos 2,000 usuarios para este sistema imagina cuanto desperdicio de recursos de memoria y cpu se hacen cuando yo nada mas quiero el nombre de usuario y su permiso, entonces lo que hice yo es la siguiente consulta

    select u,p.permiso from Usuario u inner join u.permisos p

    y me trae correctamente los dos objetos (Usuario y Permiso) relacionados, ahora si vienen mis problemas

    supongamos que tengo un metodo que se va a encargar de esta funcionalidad(regresar los usuarios con sus permisos) y que va a regresar una lista de usuarios List(Usuario), dentro de este metodo hago la ultima consulta que puse y me regresa los dos objetos y mi problema es a la hora de mapearlos por que hago lo siguiente

    //continua en el siguiente post//

    ResponderEliminar
  10. //CONTINUACION*****////


    Iterator(Object[]) usuarios = session.createQuery("SELECT u,p.permiso FROM Usuario u " + "INNER JOIN u.permisos p").list().iterator();


    Object[] tupla;
    Set(UsuarioPermiso) usuarioPermiso;
    UsuarioPermiso usu;

    session.getTransaction().commit(); HibernateConnection.closeSession(session);
    List(Usuario) usuario= new ArrayList();

    while(usuarios.hasNext()) {

    tupla = (Object[])usuarios.next();
    Usuario u = (Usuario)tupla[0];
    Permiso p = (Permiso)tupla[1];

    usuarioPermiso = new
    HashSet();
    usu = new UsuarioPermiso();
    usu.setPermiso(p);
    usuarioPermiso.add(usu);

    u.setPermisos(usuarioPermiso);

    usuario.add(u);
    }

    asi queda mi metodo que te comento y si te das cuenta obtengo los dos objetos del itrator,les hago cast (y todo muy bien hasta ahi) pero despues tengo que crear un objeto UsuarioPermiso, un objeto HashSet y tengo que mapear todo yo y ahi es cuando ya no le veo lo practico a hibernate, es decir que hacer un triple mapeo oseda de Usuario -> UsuarioPermiso -> Permiso

    Entonces mi duda es que si en un proyecto ya real, ya trabajando como se hace mapeo??? o tengo que crear otro Bean como el de tu ejemplo UsuarioPermiso

    esa es mi duda =( =( =(

    ademas otro inconveniente que le veo a mi mapeo es que cuando regrese el resultado por ejemplo a una JSP voy a tener que poner mas codigo de JSP por toda esa maraña que llevo en el objeto Usuario

    PD

    en algunas lineas puse por ejemplo List(Usuario) por que no me deja porner los simbolos mayor que y menor que los toma como HTML

    ResponderEliminar
  11. Amigo, tengo una pregunta en cuanto a las subconsultas.

    Resulta que en MySQL, podria hacer una consulta como la siguiente (entendible por cierto):

    select * from usuario where valor = 1

    Luego, podria darle un alias a esa consulta asi:

    (select * from usuario where valor = 1) as tabla1

    y si quisiera segur consultando valores (pero de la tabla1, que es la que acabo de renombrar) lo haria asi por ejemplo:

    select tabla1.nombre, tabla1.apellido from
    (select * from usuario where valor = 1) as tabla1
    where tabla1.password < 5

    Hay alguna manera en Hibernate, de asignar un alias a las consultas, oo de trabajar subconsultas como la anterior? Es que a veces en proyectos es necesario trabajar con subconsultas en vez de consultas simples. Porque me ha tocado. Quisiera saber como se hacen.

    ResponderEliminar
  12. En el post aparece 72 veces escrita la palabra "consulta", como vos decis ^-^!

    Muy buenos tus post.

    Gracias.

    ResponderEliminar