21 de septiembre de 2009

Hibernate - Parte 7: HQL Primera Parte

Hibernate proporciona un lenguaje de consultas muy poderoso llamado "Hibernate Query Language" (HQL).

HQL es muy parecido al SQL estándar, con la diferencia de que es completamente orientado a objetos (usamos nombres de clases y sus atributos en lugar de nombres de tablas y columnas), por lo que podemos usar cosas como herencia, polimorfismo y asociaciones.

Nosotros escribimos las consultas en HQL y Hibernate se encarga de convertirlas al SQL usado por la base de datos con la que estemos trabajando y ejecutarla para realizar la operación indicada.

En este tutorial veremos cómo usar este lenguaje para hacer algunas consultas típicas dentro de nuestras aplicaciones para poder recuperar objetos simples, y arboles de objetos.

Lo primero que hay que saber sobre HQL es que es case-insensitive, o sea que sus sentencias pueden escribirse en mayúsculas y minúsculas. Por lo tanto "SeLeCt", "seleCT", "select", y "SELECT" se entienden como la misma cosa.

Lo único con lo que debemos tener cuidado es con los nombres de las clases que estamos recuperando y con sus propiedades, ahí si se distinguen mayúsculas y minúsculas. O sea, en este caso "pruebas.hibernate.Usuario" NO ES LO MISMO que "PrueBAs.HibernatE.UsuArio".

En este tutorial colocaré las clausulas HQL (SELECT, WHERE, ORDER BY, etc.) en mayúsculas.

Comencemos con el tutorial creando un nuevo proyecto en NetBeans.

Lo primero que haremos es crear un proyecto en NetBeans (menú "File -> New Project... -> Java -> Java Application"). Le damos un nombre y una ubicación al proyecto y nos aseguramos de que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer nuestra clase "Main" en el editor.

En el tutorial usaremos anotaciones en vez de archivos de mapeo para que sea un poco más claro, así que agregamos las bibliotecas de "Hibernate" y "HibernateAnotaciones", que creamos en el primer y segundo tutoriales. Hacemos clic derecho en el nodo "Libraries" del proyecto y en el menú contextual que se abre seleccionamos la opción "Add Library...":



En la ventana que se abre seleccionamos las bibliotecas "Hibernate" y “HibernateAnotaciones”:



Presionamos el botón "Add Library" para que la biblioteca se agregue a nuestro proyecto. Aprovechamos también para agregar el conector de MySQL. Debemos tener los siguientes archivos en nuestro proyecto:



Ahora creamos un paquete con el nombre “modelo” que contendrá las clases entidad. Hacemos clic derecho en el nodo del paquete que se creó al generar el proyecto, en el menú contextual que se abre seleccionamos la opción "New -> Java Package..." y creamos el paquete.



Que debe quedar así:



Ahora crearemos el archivo de configuración, el cual será muy parecido al del segundo tutorial. Recuerden que este archivo debe llamarse "hibernate.cfg.xml" y debe encontrarse en la raíz del classpath del proyecto (el paquete default)



<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
   <session-factory>

       <!-- parametros para la conexion a la base de datos -->
       <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
       <property name="connection.url">jdbc:mysql://localhost:3306/hibernatehql</property>
       <property name="connection.username">root</property>
       <property name="connection.password">123</property>

       <!-- Configuracion del pool interno -->
       <property name="connection.pool_size">1</property>

       <!-- Dialecto de la base de datos -->
       <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

       <!-- Otras propiedades importantes -->
       <property name="show_sql">true</property>
       <property name="hbm2ddl.auto">none</property>

       <!-- Aqui iran las clases entidades -->
      

   </session-factory>
</hibernate-configuration>


Por ahora dejaremos el archivo de configuración como está mientras creamos nuestras clases entidades:


Nuestras Entidades


Para este ejemplo crearemos tres clases entidad: "Usuario", "Direccion" y "Permiso". La relación que existirá sobre estas tres clases es una relación uno a uno entre "Usuario" y "Dirección" y una relación uno a muchos de "Usuario" con "Permiso"; o sea, un "Usuario" puede tener una "Dirección" y un "Usuario" puede tener muchos "Permisos".

Aunque estas relaciones son simples, nos ayudaran a mostrar las consultas que realizamos con mayor frecuencia en las aplicaciones.

Primero, crearemos la clase “Permiso”. Creamos una nueva clase en el paquete “modelo” con el siguiente contenido:

@Entity
@Table(name="permisos")
public class Permiso implements Serializable
{
    public enum Estatus {PENDIENTE, ACTIVO, INACTIVO};

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String nombre;
    private Estatus estatus = Estatus.PENDIENTE;

    public Permiso()
    {
    }

    public Estatus getEstatus()
    {
        return estatus;
    }

    public void setEstatus(Estatus estatus)
    {
        this.estatus = estatus;
    }

    public long getId()
    {
        return id;
    }

    public void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

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


La entidad “Permiso” es muy sencilla pero nos permitirá observar el comportamiento de las consultas HQL. Usa las anotaciones que expliqué en el segundo tutorial.

Noten que agregué una enum para restringir los valores de los estatus solo para ejemplificar en uso de las mismas dentro de las entidades.

La siguiente clase que crearemos es “Dirección”, creamos esta clase en el paquete “modelo”. Este clase mantendrá una relación uno a uno bidireccional con "Usuario". La clase queda de esta forma:

@Entity
@Table(name="direcciones")
public class Direccion implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String calle;
    private String codigoPostal;

    @OneToOne(mappedBy="direccion")
    private Usuario usuario;

    public Direccion()
    {
    }

    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 long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public Usuario getUsuario()
    {
        return usuario;
    }

    public void setUsuario(Usuario usuario)
    {
        this.usuario = usuario;
    }
}


Como dije antes: la clase “Direccion” mantiene una relación uno a uno bidireccional con la clase “Usuario” y, como lo indica el elemento “mappedBy” de la anotación @OneToOne, la entidad “Usuarioes la dueña de la relación.

Ahora veamos la clase “Usuario”.

Creamos la clase “Usuario” en el paquete “modelo”. Esta clase mantendrá la relación uno a muchos con “Permiso” y la relación uno a uno con “Direccion”; y queda de la siguiente forma:

@Entity
@Table(name="usuarios")
public class Usuario implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String nombre;
    private String username;
    private String password;

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
    private List<Permiso> permisos = new ArrayList<Permiso>();

    @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    private Direccion direccion;

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

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

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }

    public List<Permiso> getPermisos()
    {
        return permisos;
    }

    public void setPermisos(List<Permiso> permisos)
    {
        this.permisos = permisos;
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public Direccion getDireccion()
    {
        return direccion;
    }

    public void setDireccion(Direccion direccion)
    {
        this.direccion = direccion;
        direccion.setUsuario(this);
    }
} 


Usuario” es una entidad muy similar a las que hemos visto a lo largo de estos tutoriales, así que no debería haber problemas para entenderla. Lo único interesante sobre son las relaciones que mantiene con las otras entidades.

Ahora que tenemos las entidades no olvidemos que debemos indicarlas en el archivo “hibernate.cfg.xml”:

<mapping class="hql.modelo.Permiso" />
<mapping class="hql.modelo.Usuario" />
<mapping class="hql.modelo.Direccion" />


Ahora que tenemos todo configurado comencemos propiamente con el tutorial.

Para los ejercicios usaremos una base de datos llamada "hibernatehql", en MySQL.

Lo siguiente que debemos saber sobre las consultas en HQL es que, como mencione antes, trabajamos usando los nombres de las clases; esto quiere decir que si tenemos una clase entidad llamada “Usuario” en el paquete “hql.modelo”, cuyos registros se guardan en una tabla “usuarios”, NO colocamos el nombre de la tabla en las consultas, sino el nombre de la clase. O sea en vez de hacer algo asi:


SELECT u.col_username FROM usuarios u…


Haríamos

SELECT u.username FROM hql.modelo.Usuario u…


O también podemos hacer:

SELECT u.username FROM Usuario u…


De la misma forma si el valor del atributo “username” del usuario se guarda en la columna “col_username” hacemos referencia a este valor con el nombre del atributo: “username” y no con el nombre de la columna.

Ahora veremos las clausulas con las que podemos formar una sentencia:


Características de HQL


Estas son algunas de las características más importantes que nos proporciona HQL:

  • Soporte completo para operaciones relacionales: HQL permite representar consultas SQL en forma de objetos. HQL usa clases y atributos o propiedades en vez de tablas y columnas.
  • Regresa sus resultados en forma de objetos: Las consultas realizadas usando HQL regresan los resultados de las mismas en la forma de objetos o listas de objetos, que son más fáciles de usar, ya que eliminan la necesidad de crear un objeto y llenarlo con los datos obtenidos de un ResultSet (como hacemos normalmente cuando trabajamos con JDBC).
  • Consultas Polimórficas: Podemos declarar el resultado usando el tipo de la superclase y Hibernate se encargara de crear los objetos adecuados de las subclases correctas de forma automática.
  • Fácil de Aprender: Es muy similar a SQL estándar, así que si has trabajado con SQL, HQL te resultará muy fácil.
  • Soporte para características avanzadas: HQL contiene muchas características avanzadas que son muy útiles y que no siempre están presentes en todas las bases de datos, o no es fácil usarlas, como paginación, fetch joins con perfiles dinámicos, inner y outer joins, etc. Además soporta proyecciones, funciones de agregación (max, avg), y agrupamientos, ordenamientos, y subconsultas.
  • Independiente del manejador de base de datos: Las consultas escritas en HQL son independientes de la base de datos (siempre que la base de dats soporte la característica que estamos intentando utilizar ^-^).

Bueno, ahora que sabemos un poco de HQL veamos cómo usarlo. Para ver los resultados arrojados por las consultas no necesitaremos crear una aplicación, ya que NetBeans nos permite ejecutar estas directamente desde un editor especial. Pero para usarlo debemos configurar algunas otras cosas, que explicaré a continuación.


Configurando el NetBeans para los ejemplos


Lo primero que debemos hacer es crear nuestra base de datos en MySQL con las tablas y datos de prueba necesarios. Creamos una base de datos llamada “hibernatehql” con cuatro tablas: “direcciones”, “permisos”, “usuarios”, y “usuarios_permisos”. Esta última tabla mantendrá las relaciones entre usuarios y permisos. Pueden usar el siguiente script para crear y poblar la base de datos, o si lo desean pueden descargarlo desde aquí:

CREATE DATABASE IF NOT EXISTS  `hibernatehql`;

USE `hibernatehql`;

/*Estructura de la tabla `direcciones` */

DROP TABLE IF EXISTS `direcciones`;

CREATE TABLE `direcciones` (                 
               `id` BIGINT(20) NOT NULL AUTO_INCREMENT,   
               `calle` VARCHAR(255) DEFAULT NULL,         
               `codigoPostal` VARCHAR(255) DEFAULT NULL,  
               PRIMARY KEY (`id`)                         
             ) ENGINE=INNODB DEFAULT CHARSET=latin1;    


/*Datos de la tabla `direcciones` */

insert  into `direcciones`(`id`,`calle`,`codigoPostal`) values (1,'Calle1','12345'),(2,'Calle2','54321');
/*Estructura de la tabla `permisos` */

DROP TABLE IF EXISTS `permisos`;

CREATE TABLE `permisos` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `estatus` int(11) DEFAULT NULL,
  `nombre` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

/*Datos de la tabla `permisos` */

insert  into `permisos`(`id`,`estatus`,`nombre`) values (1,0,'Lectura Archivos'),(2,0,'Creacion Archivos'),(3,1,'Eliminacion Archivos'),(4,1,'Moficacion Archivos'),(5,2,'Sin Permisos');

/*Estructura de la tabla `usuarios` */

DROP TABLE IF EXISTS `usuarios`;

CREATE TABLE `usuarios` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `nombre` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  `direccion_id` BIGINT(20) DEFAULT NULL,      
  PRIMARY KEY (`id`),                                                                         
  KEY `FK_DIRECCIONES` (`direccion_id`),                                                    
  CONSTRAINT `FK_DIRECCIONES` FOREIGN KEY (`direccion_id`) REFERENCES `direcciones` (`id`)  
  ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;          
  

/*Datos de la tabla `usuarios` */

insert  into `usuarios`(`id`,`nombre`,`password`,`username`,`direccion_id`) values (1,'Usuario 1','abcdefg','usr001',1),(2,'Usuario1','hijklm','usr456',2),(3,'Usuario3','alex','alex',null);
/*Estructura de la tabla `usuarios_permisos` */

DROP TABLE IF EXISTS `usuarios_permisos`;

CREATE TABLE `usuarios_permisos` (
  `usuarios_id` bigint(20) NOT NULL,
  `permisos_id` bigint(20) NOT NULL,
  UNIQUE KEY `permisos_id` (`permisos_id`),
  KEY `FK_USUARIOS` (`usuarios_id`),
  KEY `FK_PERMISOS` (`permisos_id`),
  CONSTRAINT `FK_PERMISOS` FOREIGN KEY (`permisos_id`) REFERENCES `permisos` (`id`),
  CONSTRAINT `FK_USUARIOS` FOREIGN KEY (`usuarios_id`) REFERENCES `usuarios` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

/*Datos de la tabla `usuarios_permisos` */

INSERT  INTO `usuarios_permisos`(`usuarios_id`,`permisos_id`) VALUES (1,1),(1,2),(2,3),(2,4),(3,5);


Ahora vayamos al NetBeans.

Anteriormente, cuando instalamos plugins en el NetBeans y lo configuramos para usar el tomcat, usamos un panel llamado “Services”, el cual nos permite agregar servicos y herramientas externas (servidores web y de aplicaciones, bases de datos, servicios web, etc.) para tener acceso a ellos dentro de NetBeans.

En esta ocasión agregaremos un servicio que nos permitirá tener una conexión a nuestra base de datos de prueba.



En el panel “Services” hacemos clic derecho sobre el nodo “Databases” y en el menú contextual que se abre seleccionamos la opción “New Connection…



Con lo que se abrirá la ventana “New Database Connection”. En esta ventana podemos escribir directamente la URL de la conexión (jdbc:mysql://…), o escribir algunos parámetros y dejar que la URL se genere automáticamente. Nosotros haremos este último.

Lo primero que hacemos es seleccionar, en el campo “Name” la opción “MySQL (Connector/J driver)”. El resto de los datos se obtienen de su conexión a su servidor de base de datos.



Usen los datos propios de su servidor para conectarse. Al final, con los datos de mi conexión, la ventana queda así:



Les recomiendo que habiliten la opción “Remember password” y “Show JDBC URL”. Hacemos clic en el botón “OK” y veremos que en el panel “Services”, en el nodo “Databases” aparecerá la conexión a nuestra base de datos. En caso de que haya un error aparecerá el mensaje correspondiente para que podamos corregirlo.



Gracias a esta configuración ahora podremos ejecutar sentencias de SQL directamente en NetBeans:



Pero, lo más importante, es que también podemos ejecutar sentencias HQL desde el NetBeans, sin necesidad de crear una aplicación:



Será esta forma en la que ejecutaremos las clausulas durante este tutorial.

Veremos las clausulas más comunes de este lenguaje y haremos ejemplos usando nuestras entidades.


La clausula FROM


La clausula más simple que existe en Hibernate es “FROM”. Esta clausula regresa todas las instancias de la clase indicada que se encuentran en la base de datos, es como si hiciéramos un “SELECT *” en MySQL. Por ejemplo, para recuperar todos los usuarios de la base de datos ejecutamos la consulta de esta forma:

FROM hql.modelo.Usuario


Normalmente no necesitamos el “fully qualified name” de la clase, así que esta misma sentencia podríamos escribirla de esta forma:

FROM Usuario


Al ejecutar esta sentencia en NetBeans, con los datos de la base de pruebas que creamos, obtendremos el siguiente resultado:



Dentro de esta clausula FROM podemos colocar el nombre de más de una clase, lo que resultará en un producto cartesiano entre las entidades que coloquemos. Podemos colocar el nombre de dos de nuestras entidades en la clausula, de esta forma:

FROM Usuario, Permiso


Resultando en la siguiente salida:



Aquí se muestra un todos contra todos (producto cartesiano) ya que no colocamos ninguna restricción en cuanto a las condiciones que deben cumplir los objetos.

Cuando tenemos que referirnos a nuestras clases en otras partes de la consulta, como en las restricciones, debemos asignarle un alias y hacer referencia a la clase a través de este alias. No podemos usar directamente el nombre del objeto ya que resultaría en una excepción. Por ejemplo, continuando con la consulta anterior, si quisiéramos hacer referencia al “id” del “Usuario”, si lo hacemos de esta forma:

FROM Usuario, Permiso WHERE Usuario.id = 1


Obtendríamos la siguiente excepción:



En vez de esto debemos asignarle un alias a la clase entidad "Usuario" y hacer referencia a los atributos de esta entidad usando el alias, de esta forma:

FROM Usuario as u, Permiso WHERE u.id = 1


O así:

FROM Usuario u, Permiso WHERE u.id = 1


Resultando en la salida esperada:




Asociaciones y Joins


HQL también permite los siguientes cuatro tipos de joins entre entidades:

  • Inner Join
  • Left Outer Join
  • Right Outer Join
  • Full Join (siempre que la base de datos que estemos usando los soporte)

Además también podemos asignar alias a las entidades asociadas o a los elementos de una colección de valores usando un join, por ejemplo:

FROM Usuario u inner join u.permisos as p


Con su correspondiente salida



También podemos proporcionar condiciones extra al join con la palabra reservada WITH:

FROM Usuario u inner join u.permisos as p WITH p.estatus = 1




La parte que voy a explicar es muy importante y nos resultará muy útil cuando trabajemos con colecciones de objetos. ¿Recuerdan que cuando vimos las relaciones uno a muchos, dije que en la relación podemos indicar la forma de fetch o recuperación de los elementos de la colección? ¿Y que estos tipos solo son 2: EAGER y LAZY?

Cuando tenemos una colección con tipo de fetch EAGER no hay problema, al recuperar la entidad recuperaremos también todos los objetos de la colección. Pero ¿qué ocurre cuando el tipo de fetch es LAZY? En ese caso necesitamos recuperar los elementos de la colección de otra forma.

Pues bien, esto puede hacerse usando un tipo especial de join llamado "fetch" join, el cual nos permite inicializar los elementos de estas asociaciones o colecciones. Este fetch join “sobreescribe” la declaración de fetch LAZY de la colección (solo para la consulta que se está realizando).

Por ejemplo, nosotros tenemos en nuestra entidad "Usuario" declarada la lista de permisos de la siguiente forma:

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Permiso> permisos = new ArrayList<Permiso>();


Esto quiere decir que cuando recuperemos, ya dentro de una aplicación, al Usuario, sus permisos no serán recuperados e inicializados en ese momento. Esto es útil si estamos, por ejemplo, mostrando una lista de usuarios y no nos interesa mostrar nada relacionado con sus permisos en ese momento. Pero ¿qué ocurre cuando el usuario ingresa a la aplicación y nos interesa validar los permisos que tiene? En ese caso si debemos recuperar la lista de permisos. Pues bien, es para esto que nos sirve el fetch join, y se usa de la siguiente forma:

FROM Usuario u inner join fetch u.permisos


Recuerden que aunque la instrucción se llama fetch join la usamos al revés ("join fetch"). Y con esto se recuperarán los permisos del usuario al mismo tiempo que el usuario. En el editor de HQL de NetBeans esto no se logra apreciar bien, pero créanme, cuando estén realizando una aplicación esto puede salvarnos la vida ^-^.




Sintaxis de los joins


HQL soporta dos sintaxis para los joins: implícita y explícita.

En los ejemplos anteriores hemos estado usando la forma explícita, o sea, que colocamos la palabra join dentro de la clausula. Esta es la forma recomendada ya que es más clara, sin embargo si queremos ahorrar un poco de espacio podemos usar la forma implícita de esta manera:



Como vemos, ocurre un join implícito entre "Usuario" y "Dirección", usando el atributo dirección de la clase Usuario. Lo malo de este tipo de joins es que solo puede hacerse cuando tenemos una relación uno a uno o muchos a uno, o sea, no funciona cuando la propiedad es una colección:




Preguntando por el Identificador


En HQL existen dos formas de referirnos a la propiedad que sirve como identificador de la entidad:

  • La propiedad especial “id” (así con minúsculas). No importa si la clase entidad no tiene una propiedad llamada id.
  • Si la entidad define una propiedad identificador, podemos referirnos a ella a través del nombre de dicha propiedad (en el caso de estos ejemplos la propiedad identificadora es llamada id, así que no me será posible ejemplificar esto claramente).

Importante: Esto quiere decir que si nuestra entidad tiene un atributo llamado “id”, pero esto no es el identificador de la entidad, no habrá forma de hacer referencia a él.


La clausula SELECT


La clausula SELECT indica cuáles objetos y propiedades se regresarán en el conjunto de resultados de la consulta (así es, podemos regresar solamente algunas propiedades de los objetos y no todo el objeto, pero esto lo veremos más adelante).

Supongamos que tenemos la siguiente consulta:

SELECT dir FROM Usuario as u inner join u.direccion as dir


Esta consulta regresará la direccion de los usuarios. Podemos expresar esta misma consulta de una forma más compacta haciendo uso de los joins implícitos:

SELECT u.direccion FROM Usuario as u




Como dije hace un momento: podemos recuperar solo algunos atributos o valores del objeto que estemos regresando. Por ejemplo, la siguiente consulta regresa solo el nombre del usuario:

SELECT u.nombre FROM Usuario as u


En la ventana de ejecución del HQL en el NetBeans, vemos el resultado de esta forma:



Intentaré dar una explicación al porqué se ven de esta forma los datos. Si me equivoco en la explicación por favor corríjanme.

Cuando recuperamos solo un atributo de un objeto, recuperamos una lista de objetos del tipo del atributo que queremos recuperar (si es String recuperamos Strings, si es int recuperamos ints, etc.).

Sin embargo, si recuperamos más de un atributo de un objeto, Hibernate NO nos regresa una lista del tipo de objeto (en este caso "Usuario"), si no que nos regresa una lista de arreglos de objetos, o sea, una lista en la que cada uno de los elementos es un arreglo de objetos. Cada uno de los objetos del arreglo representa uno de los valores recuperados. Como nosotros solo estamos recuperando el valor de una propiedad (el nombre del usuario) vemos que en el NetBeans solo tenemos un valor en cada fila (no tengo idea de porqué muestra varias filas). Como Hibernate no sabe (o no le importa) el tipo del objeto que es el valor del atributo cuando lo regresa, solo que es un objeto, nos regresa un arreglo de bytes (finalmente todo objeto es un arreglo de bytes) y es esto lo que vemos en los valores regresados (cuando tenemos que un valor empieza solo con un corchete que abre ([) significa que el valor es un arreglo.

Como supongo que algunos de ustedes no me creerán que esto funciona, tendré que demostrarlo en código ^_^.

Como siempre, usaremos la clase HibernateUtil desarrollada antes, la del segundo tutorial, ya que estamos trabajando con anotaciones.

Como vamos a hacer varios pequeños ejemplos, crearemos un método para cada uno y lo invocaremos en el constructor de la clase Main. Después iremos comentando y des-comentando las invocaciones a los métodos para ver su funcionamiento individual.

Además crearemos un atributo de instancia en nuestra clase Main que nos ayudará a manejar las conexiones en todos los métodos. Esta variable será de tipo “org.hibernate.Session”, la declararemos de esta forma:

private Session sesion;


Y dos métodos de utilidad. El primero creará una nueva sesión a la base de datos e iniciar una transacción. El segundo método terminará la transacción y cerrará la conexión.

Este es el método que inicia la sesión:

private void inciaOperacion()
{
        SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        sesion = sessionFactory.openSession();
        sesion.getTransaction().begin();
}


Y este es el método que la termina:

private void terminaOperacion()
{
        sesion.getTransaction().commit();
        sesion.close();
}


Ahora crearemos el método que recuperará solo el nombre del usuario haciendo uso de la consulta anterior.

Hasta ahora no habíamos realizado consultas con HQL, así que tendré que explicar cómo usarlas en código.

Con Hibernate normalmente haremos consultas el HQL (también podemos hacer consultas en el SQL de la base de datos que estemos usando, pero no hablaré de eso). Para ejecutar una consulta en HQL necesitamos un objeto de tipo “org.hibernate.Query”. Query es una interface, por lo que necesitamos una clase que la implemente para usarla.

Afortunadamente el objeto “org.hibernate.Session” que estamos usando (sesion) tiene un método que nos permite obtener un objeto de este tipo:

Query query = sesion.createQuery("Aquí ira nuestra consulta");


En el lugar que he indicado con la cadena “Aquí ira nuestra consulta” es donde colocaremos el HQL que estemos usando.

Estas consultas pueden ser básicamente de tres tipos:

  • Búsquedas de listas de resultados (este es el tipo de consulta que hemos estado haciendo hasta el momento)
  • Búsquedas de un objeto único
  • Actualizaciones de datos (actualizaciones o eliminaciones)

Cada una de estas operaciones la realizamos con un método específico de la interface Query:

query.list();          //Para las listas de resultados
query.uniqueResult();  //Para los objetos unicos
query.executeUpdate(); //Para las actualizaciones de datos


Nosotros usaremos el primer método (list()) en los siguientes ejemplos.

Bien, ahora si veamos el método.

Crear este método será muy sencillo. Básicamente lo que haremos es crear un nuevo método, colocar los dos métodos auxiliares que creamos anteriormente, obtener un objeto query, colocarle la consulta anterior, e invocar a su método list.

El método, para no hacer la explicación más aburrida, queda de esta forma:

private void obtenNombres()
{
        iniciaOperacion();

        Query query = sesion.createQuery("SELECT u.nombre FROM Usuario as u");

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

        for (int i = 0; i < listaResultados.size(); i++)
        {
            System.out.println("Nombre " + i + ": " + listaResultados.get(i));
        }

        terminaOperacion();    
}


Ahora, solo nos queda colocar la siguiente llamada en el método main:

new Main();


y esto en el constructor de Main:

obtenNombres();


La clase Main debe quedar así:

public class Main 
{
    private Session sesion;

    public Main()
    {
        obtenNombres();
    }

    public static void main(String[] args) 
    {
        new Main();
    }

    private void iniciaOperacion()
    {
        SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        sesion = sessionFactory.openSession();
        sesion.getTransaction().begin();
    }

    private void terminaOperacion()
    {
        sesion.getTransaction().commit();
        sesion.close();
    }

    private void obtenNombres()
    {
        iniciaOperacion();

        Query query = sesion.createQuery("SELECT u.nombre FROM Usuario as u");

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

        for (int i = 0; i < listaResultados.size(); i++)
        {
            System.out.println("Nombre " + i + ": " + listaResultados.get(i));
        }

        terminaOperacion();
    }
}


Ahora, al ejecutar la aplicación vemos la siguiente salida:



Como vemos, obtuvimos solamente los nombres, justo como esperábamos ^-^.

La clausula SELECT también nos permite recuperar múltiples objetos y/o propiedades de objetos. Es este caso ocurre lo que expliqué anteriormente: recuperamos una lista de arreglos de objetos. Para comprobar esto, crearemos una consulta que recuperará el nombre y el password de un usuario.

Coloquemos la siguiente clausula en el ejecutor de HQL de NetBeans:

SELECT u.nombre, u.password FROM Usuario as u


Con lo que obtenemos la siguiente salida:



Como esta salida también es poco clara, crearemos un nuevo método para ver que, efectivamente, estemos recuperando una lista de arreglos de objetos con los valores indicados.

Como en este caso estamos recuperando dos valores (nombre y password), el primer elemento del arreglo (el elemento 0) será el nombre, y el segundo (el elemento 1) será el password:

private void obtenNombresYPasswords()
{
        iniciaOperacion();

        Query query = sesion.createQuery("SELECT u.nombre, u.password FROM Usuario as u ");

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

        for (int i = 0; i < listaResultados.size(); i++)
        {
            System.out.println("Nombre " + i + ": " + listaResultados.get(i)[0] + ", password: " + listaResultados.get(i)[1]);
        }

        terminaOperacion();
}


Cuando ejecutamos este código obtenemos la siguiente salida:



Como vemos, al hacer

listaResultados.get(i)[0]


Obtenemos el nombre que se encuentra en el primer elemento del arreglo, y al hacer

listaResultados.get(i)[1]


Obtenemos el password que se encuentra en el segundo elemento del arreglo, justo como habíamos predicho ^-^.

Aunque esto que acabamos de ver nos resultará bastante útil cuando no necesitemos todo el objeto (ya que nos ahorrará mucha memoria) puede parecernos un poco raro trabajar con arreglos de objetos; más a los que estamos acostumbrados solo a las listas ( ^-^!).

Para personas como yo, Hibernate nos permite recuperar estos valores dentro de una lista, en vez del arreglo de objetos, usando una notación especial. A partir de aquí veremos algunas sintaxis extrañas, pero no se preocupen, que con el tiempo nos acostumbraremos a verlas.

Para recuperar los mismos datos que en el ejemplo anterior, pero con una lista en vez de un arreglo de objetos, debemos escribir la consulta de la siguiente forma:

SELECT new list(u.nombre, u.password) FROM Usuario as u


Como vemos la consulta es muy similar a la anterior, pero con ese extraño elemento “new list”. Coloquémonos en el NetBeans:



Como podemos ver el NetBeans tampoco nos ayuda mucho cuando trabajamos con este tipo de consultas u.u, así que tendremos que crear nuevamente un método:

private void obtenNombresYPasswordsComoLista()
{
    iniciaOperacion();

    Query query = sesion.createQuery("SELECT new list(u.nombre, u.password) FROM Usuario as u ");

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

    for (int i = 0; i < listaResultados.size(); i++)
    {
        System.out.println("Nombre " + i + ": " + listaResultados.get(i).get(0) + ", password: " + listaResultados.get(i).get(1));
    }

    terminaOperacion();
}


Con lo que obtenemos la siguiente salida:



Como podemos ver, obtuvimos el mismo resultado que en la ocasión anterior, pero ahora en vez de hacer esto:

listaResultados.get(i)[0] 
listaResultados.get(i)[1]


Hacemos esto:

listaResultados.get(i).get(0) 
listaResultados.get(i).get(1)


Podemos incluso recuperar un objeto propio pasando los valores a su constructor o recuperar un Map. Pero eso escapa al propósito de este tutorial.

Y como me he tardado más de lo esperado en escribir este tutorial, lo dejaré hasta aquí en este momento y lo terminaré en una segunda parte. Falta ver un poco de la sentencia WHERE, funciones de agregación, y sub-consultas, así que espero sea un poco más corto.

No olviden dejar todas sus dudas y comentarios. También pongan lo que les gustaría ver en el siguiente tutorial.

Saludos.

Entradas Relacionadas: