domingo, 25 de julio de 2010

Hibernate - Parte 9: Parámetros en HQL

En los dos tutoriales anteriores aprendimos cómo crear sentencias en el Hibernate Query Language (HQL) para poder hacer consultas más específicas, que las que por default nos proporciona Hibernate, a objetos almacenados en nuestra base de datos.

Hasta ahora nuestras consultas siempre han sido estáticas, pero en nuestras aplicaciones usualmente deberemos pasarles parámetros para obtener los datos correctos, como por ejemplo una cadena que represente un criterio de búsqueda, o el username y password de un usuario que queramos buscar.

Ahora veremos cómo poder pasar parámetros a estas consultas de una forma “correcta”; y digo correcta porque siempre podríamos concatenar los parámetros al String que usemos para generar nuestra consulta.

Afortunadamente existen dos formas de pasar los parámetros sin tener que recurrir a la concatenación. Y como solo existen dos formas este tutorial será corto, se los prometo ^_^!

Comencemos recordando un poco cómo hacemos, desde nuestro código Java, una consulta en HQL.

Nota: Aunque uso el término general consulta, no solo me refiero a pedir datos de la base, también uso este término para referirme a inserciones o actualizaciones de datos.

Hibernate proporciona la interface “org.hibernate.Query”, la cual representa una consulta a la base de datos. Para obtener una instancia de “Query” usamos el método “createQuery” del objeto “org.hibernate.Session”, que a su vez obtenemos de la clase “org.hibernate.SessionFactory” (si no recuerdan cómo obtener Session pueden revisarlo en la clase “HibernateUtil” que creamos en el primer y en el segundo tutorial).

El método “createQuery” recibe como parámetro una cadena que es nuestra consulta HQL, sin embargo, como mencioné antes, hasta ahora solo hemos usado consultas estáticas. Sin embargo solo en raras ocasiones estas consultas estáticas nos serán del todo útiles.

Hibernate proporciona dos formas de pasar parámetros a nuestras consultas:

  • Parámetros con nombre (named query parameters).
  • Parámetros posicionales estilo JDBC.

Antes de entrar en el detalle de cada una de ellas veamos el modelo de datos que usaremos para los ejemplos.

La aplicación que crearemos será un mini sistema de ventas. Tendremos un “Usuario” que puede realizar “Compras” de “Productos”. Este “Usuario” tendrá una “Direccion” de entrega a la que llegarán los “Productos” que compre.

Por lo tanto, tendremos una clase entidad “Usuario” que tendrá una relación uno a uno con la clase entidad “Direccion”. El “Usuario” además tendrá una relación uno a muchos con la clase entidad “Compra” que a su vez tendrá una relación muchos a muchos con la clase entidad “Producto”.

Como podemos ver el ejemplo es pequeño pero podremos usar varias de las cosas que hemos aprendido a lo largo de estos tutoriales.

En los tutoriales solamente usaré anotaciones para el ejemplo, sin embargo pueden descargar también la versión con archivos de mapeo desde la liga que se encuentra al final del tutorial.

Creamos un nuevo proyecto en NetBeans (menú “File -> New Project… -> Java -> Java Application”). Le damos un nombre y una ubicación, 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 en el editor nuestra clase "Main".

Agregamos la biblioteca de "Hibernate" que creamos en el primer tutorial de la serie. Hacemos clic derecho en el nodo "Libraries" del proyecto. En el menú contextual que se abre seleccionamos la opción "Add Library...":



En la ventana que se abre seleccionamos la biblioteca "Hibernate":



Presionamos el botón "Add Library" para que la biblioteca se agregue a nuestro proyecto. De la misma forma agregamos la biblioteca “HibernateAnotaciones” que creamos en el segundo tutorial. Aprovechamos también para agregar el conector de MySQL. Debemos tener los siguientes archivos en nuestro proyecto:



Ahora creamos dos paquetes, uno con el nombre "modelo", que contendrá las clases entidades, y otro con el nombre "dao" que contendrá las clases que se encargarán del manejo de los datos que almacenaremos en la base de datos.

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 los dos paquetes.



Que deben quedar así:



También crearemos el archivo de configuración “hibernate.cfg.xml” que será prácticamente igual que el de los últimos 8 tutoriales:

<!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/ hibernateparametros </property>
      <property name="connection.username">usuario</property>
      <property name="connection.password">password</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">create-drop</property>

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

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


Para los tutoriales usaremos una base de datos llamada “hibernateparametros”. Las tablas serán creadas de forma automática en base a nuestras entidades anotadas.

Comencemos con la creación de nuestro modelo de datos, dentro del paquete “modelo”, con la clase “Direccion” que queda de esta forma:

@Entity
public class Direccion implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    private String calle;
    private String codigoPostal;

    public Direccion()
    {
    }

    public Direccion(String calle, String codigoPostal)
    {
        this.calle = calle;
        this.codigoPostal = codigoPostal;
    }

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

    private void setId(int id)
    {
        this.id = id;
    }
}


No hay mucho que explicar sobre esta clase, es prácticamente la misma que hemos visto en los tutoriales anteriores: una clase marcada con la anotación @Entity e indicando cuál de sus atributos servirá como identificador usando la anotación @Id.

La clase “Direccion” es muy parecida a la clase “Producto” que queda así:

@Entity
public class Producto implements Serializable
{
    public static enum Estatus{ACTIVO, INACTIVO};
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String nombre;
    private String codigoBarras;
    private float precio;
    private Estatus estatus = Estatus.ACTIVO;
    
    public Producto()
    {
    }

    public Producto(String nombre, String codigoBarras, float precio)
    {
        this.nombre = nombre;
        this.codigoBarras = codigoBarras;
        this.precio = precio;
    }
    
    public String getCodigoBarras()
    {
        return codigoBarras;
    }

    public void setCodigoBarras(String codigoBarras)
    {
        this.codigoBarras = codigoBarras;
    }

    public Estatus getEstatus()
    {
        return estatus;
    }

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

    public int getId()
    {
        return id;
    }

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

    public String getNombre()
    {
        return nombre;
    }

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

    public float getPrecio()
    {
        return precio;
    }

    public void setPrecio(float precio)
    {
        this.precio = precio;
    }
}


Esta clase es un poco más interesante ya que, como podemos ver arriba, incluye una enumeración llamada “Estatus” que nos indica si el producto está “ACTIVO” o “INACTIVO”. Esta enumeración es public y static para que podamos acceder a ella desde afuera de la clase Producto de esta forma: Producto.Estatus.ACTIVO y Producto.Estatus.INACTIVO, y podamos usarlos en nuestras consultas.

También, la clase Producto incluye un atributo de tipo Estatus, llamado “estatus” y que inicializamos con el valor “Estatus.ACTIVO”, o sea, cada vez que se cree un producto este estará activo por default.

Ahora comenzaremos a ver las clases entidad que tienen relación con otras entidades. La entidad “Usuario” queda de la siguiente manera:

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

    @OneToOne(cascade = CascadeType.ALL)
    private Direccion direccion;

    @OneToMany(cascade = CascadeType.ALL)
    private List<Compra> compras = new ArrayList<Compra>();

    public Usuario()
    {
    }

    public Usuario(String nombre, String username, String password)
    {
        this.nombre = nombre;
        this.username = username;
        this.password = password;
    }
    public List<Compra> getCompras()
    {
        return compras;
    }

    public void setCompras(List<Compra> compras)
    {
        this.compras = compras;
    }

    public void addCompra(Compra compra)
    {
        this.compras.add(compra);
        compra.setUsuario(this);
    }

    public Direccion getDireccion()
    {
        return direccion;
    }

    public void setDireccion(Direccion direccion)
    {
        this.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 String getUsername()
    {
        return username;
    }

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


La entidad “Usuario” es un poco más interesante que las dos que vimos anteriormente, ya que, además de indicar cuál es su atributo identificador y mostrar algunos atributos más, vemos que tiene las anotaciones que indican las relaciones que tiene con las otras entidades.

La relación uno a uno que tiene con “Direccion” la representamos mediante la anotación “@OneToOne”, que como podemos ver realizará todas las operaciones en cascada con esta entidad, en su atributo direccion.

También podemos ver que tiene una relación uno a muchos, representada mediante la anotación “@OneToMany”, con la entidad “Compra” en su atributo “compras”.

Finalmente, la entidad “Compra” queda de esta manera:

@Entity
public class Compra implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Producto> productos = new ArrayList<Producto>();
    
    private double importeTotal;
    
    @ManyToOne
    private Usuario usuario;

    public Usuario getUsuario()
    {
        return usuario;
    }

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

    public double getImporteTotal()
    {
        return importeTotal;
    }

    public void setImporteTotal(double importeTotal)
    {
        this.importeTotal = importeTotal;
    }

    public List<Producto> getProductos()
    {
        return productos;
    }

    public void setProductos(List<Producto> productos)
    {
        this.productos = productos;
    }

    public void addProducto(Producto producto)
    {
        this.productos.add(producto);
        importeTotal += producto.getPrecio();
    }

    public long getId()
    {
        return id;
    }

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


Como vemos, la entidad “Compra” tiene una relación muchos a muchos con la entidad “Producto”, a través de su atributo “listaProductos”, en la cual colocamos la anotación “@ManyToMany” que representa la relación. En esta anotación también hemos colocado el valor “FetchType.EAGER” al atributo “fetch” ya que queremos que al momento de recuperar una compra, se recuperen de forma automática todos los productos que la conforman.

Como dije antes, esta relación será de tipo muchos a muchos, por lo que, además de saber qué Productos están en una Compra, también podremos saber quién compró qué Productos, a través de la relación muchos a uno que “Compra” tiene con la entidad “Usuario”.

Ahora ya tenemos las cuatro entidades que usaremos para este ejemplo. No olviden indicar estas clases en el archivo “hibernate.cfg.xml”, que queda de esta forma:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernateparametros</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.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">create-drop</property>

        <!-- Aqui iran las clases mapeadas -->
        <mapping class="hibernate.parametros.modelo.Direccion" />
        <mapping class="hibernate.parametros.modelo.Compra" />
        <mapping class="hibernate.parametros.modelo.Producto" />
        <mapping class="hibernate.parametros.modelo.Usuario" />

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


Para continuar esta parte de configuración crearemos la clase “HibernateUtil” como lo hicimos en el segundo tutorial de la serie.

A continuación crearemos una clase de utilidad que nos ayudará a para realizar las operaciones de consultas a la base de datos y de paso nos ayudará a mostrar un poco como debe ser la separación de capas de lógica y de persistencia en nuestra aplicación. Esta será una clase abstracta que contendrá unos métodos de utilidad para las operaciones de base de datos y de las cuales extenderá el resto de nuestras clases de persistencia.

Hacemos clic derecho sobre nuestro paquete “dao” y en el menú contextual que aparece seleccionamos las opciones “New -> Java Class”. En la ventana que se abre colocamos como nombre de la clase “AbstractDAO” y presionamos el botón “Finish”.



Como dije antes, esta clase servirá de base para el resto de nuestras clases de persistencia. Pero no queremos que alguien pueda crear una instancia de esta clase directamente, así que la marcamos como “abstract”, de la siguiente forma:

public abstract class AbstractDAO
{
}


No entraré mucho en los detalles de lo que hace esta clase porque es básicamente lo mismo que hemos venido viendo a lo largo de los tutoriales, así que solo colocaré el código final, que queda de esta forma:

public abstract class AbstractDAO
{
    private Session sesion;

    protected void iniciaOperacion()
    {
        sesion = HibernateUtil.getSessionFactory().openSession();
        sesion.getTransaction().begin();
    }

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

    protected void manejaExcepcion(HibernateException he) throws HibernateException
    {
        sesion.getTransaction().rollback();
        throw he;
    }

    protected Session getSession()
    {
        return sesion;
    }

    public static void almacenaEntidad(Object entidad) throws HibernateException
    {
        AbstractDAO dummy = new AbstractDAO(){};

        try
        {
            dummy.iniciaOperacion();
            dummy.getSession().saveOrUpdate(entidad);
            dummy.getSession().flush();
        }
        catch (HibernateException he)
        {
            dummy.manejaExcepcion(he);
        }
        finally
        {
            dummy.terminaOperacion();
        }
    }

    public static <T> T getEntidad(Serializable id, Class<T> claseEntidad) throws HibernateException
    {
        AbstractDAO dummy = new AbstractDAO(){};

        T objetoRecuperado = null;

        try
        {
            dummy.iniciaOperacion();
            objetoRecuperado = (T) dummy.getSession().get(claseEntidad, id);
        }
        catch (HibernateException he)
        {
            dummy.manejaExcepcion(he);
        }
        finally
        {
            dummy.terminaOperacion();
        }

        return objetoRecuperado;
    }

    public static <T> List<T> getListaEntidades(Class<T> claseEntidad) throws HibernateException
    {
        AbstractDAO dummy = new AbstractDAO(){};

        List<T> listaResultado = null;

        try
        {
            dummy.iniciaOperacion();
            listaResultado = dummy.getSession().createQuery("FROM " + claseEntidad.getSimpleName()).list();
        }
        catch (HibernateException he)
        {
            dummy.manejaExcepcion(he);
        }
        finally
        {
            dummy.terminaOperacion();
        }

        return listaResultado;
    }
}


Como podemos ver, los métodos “iniciaOperacion”, “terminaOperacion”, y “manejaExcepcion” son los mismos que hemos venido viendo hasta ahora. El resto de los métodos podrían parecer un poco extraños así que los explicaré sin entrar mucho en detalles.

El método “almacenaEntidad” es un método estático que nos permite almacenar cualquier tipo de objeto entidad. El tener este método aquí nos evitará el tener que crear métodos específicos para cada una de las entidades, y como el método es estático ni siquiera tendremos que crear una instancia de una clase para guardar una entidad. Créanme que esto resulta muy útil en los proyectos ^-^.

Como podemos ver, es necesario crear un objeto “dummy”, el cual es una clase que extiende a la misma “AbstractDAO” para poder acceder a los métodos “iniciaOperacion”, “terminaOperacion”, y “manejaOperacion” que están marcados como protected.

El método “getEntidad” nos permite recuperar cualquier objeto entidad del cual sepamos su identificador. Como vemos, este recibe como parámetros un “Serializable” que es el identificador (que puede ser un Long, String, Integer, en fin, casi cualquier clase que queramos), y la clase a la cual pertenece la entidad que queremos recuperar.

El método “getListaEntidades” es muy parecido al anterior con la diferencia que aquí se recuperan todas las entidades de un tipo que tengamos almacenadas.

Tanto en “getEntidad” y “getListaEntidades” podemos ver que se hace uso de generics (indicado por <T>) para recuperar los objetos de una clase determinada.

Pues bien, comencemos viendo cómo pasar parámetros a nuestras consultas usando parámetros con nombre.


Parámetros con nombre


Es la forma más simple de pasar parámetros a una consulta en HQL ya que, como su nombre lo indica, se le da un nombre al parámetro y posteriormente se hace uso de este nombre para hacer referencia al parámetro.

El nombre del parámetro debe ir dentro del String de la consulta, este debe ir precedido por “:”.

Como primer ejemplo haremos una consulta para buscar a un Usuario que nos proporciona su nombre de usuario y contraseña (como en un login). Esta consulta estará dentro de un método de la clase “UsuariosDAO” que extenderá a la clase “AbstractDAO” que creamos hace un momento.

Hacemos clic derecho en el paquete “dao”. En el menú que se abre seleccionamos “New -> Java Class”, ponemos como nombre de la clase “UsuariosDAO” y presionamos el botón “Finish”. La clase que se crea será una subclase de “AbstractDAO” por lo que lo indicamos con la palabla clave extends.

public class UsuariosDAO extends AbstractDAO
{
}


Ahora pasaremos a ver la consulta. Primero explicaré cada una de las partes y posteriormente veremos cómo queda el método completo.

Si recuerdan, en el tutorial de HQL vimos cómo hacer una consulta simple para recuperar un objeto. En este caso la consulta quedaría inicialmente de la siguiente forma:

FROM Usuario u


La consulta anterior nos regresará a todos los Usuarios que tengamos en la base de datos. Sin embargo esto no es lo que queremos, lo que queremos es recuperar a un Usuario específico cuyos nombres de usuario y contraseña coincidan con los valores que le proporcionaremos. Para esto debemos usar una restricción con la clausula “WHERE”, y después indicarle los parámetros que usaremos. Como dije hace un momento: para indicar los parámetros lo hacemos con el símbolo “:” seguido del nombre que tendrá el parámetro, este nombre puede ser cualquiera que nosotros queramos. De esta forma:

FROM Ususario u WHERE u. username = :nombreUsuario AND u. password = :password


La consulta anterior está esperando recibir dos parámetros “nombreUsuario” y “password”, una vez que reciba estos dos parámetros reemplazará las cadenas “:nombreUsuario” y “:password” con los valores correspondientes.

Ahora la pregunta es: ¿Cómo establecemos estos parámetros? Pues bien, la respuesta a la pregunta anterior es que la interface “org.hibernate.Query” tiene un método llamado “setParameter” que recibe como primer argumento el nombre del parámetro que queremos establecer, y como segundo argumento el valor de dicho parámetro. El hecho de que el segundo parámetro sea un objeto nos permite que le pasemos valores de tipos arreglo, colecciones, bytes, booleanos, o prácticamente cualquier cosa que necesitemos, como enumeraciones (lo cual haremos en unos momentos). La forma de usar este método es la siguiente:

query.setParameter(“nombreUsuario”, username);
query.setParameter(“password”, password);


Finalmente, para recuperar el valor que regresa esta consulta usamos el método “uniqueResult()” de la interface “org.hibernate.Query”, como en los tutoriales anteriores:

Usuario usuario = (Usuario) query.uniqueResult();


Ahora pondremos todo lo anterior en un método llamado “getUsuario” que queda de la siguiente forma (ya agregándole los métodos de utilidad que obtenemos por herencia de la clase “AbstractDAO”.

    public Usuario getUsuario(String username, String password) throws HibernateException
    {
        Usuario usuario = null;

        try
        {
            iniciaOperacion();
            Query query = getSession().createQuery("FROM Ususario u WHERE u. username = :nombreUsuario AND u. password = :password");
            query.setParameter("nombreUsuario", username);
            query.setParameter("password", password);

            usuario = (Usuario)query.uniqueResult();
        }
        catch (HibernateException he)
        {
            manejaExcepcion(he);
        }
        finally
        {
            terminaOperacion();
        }


        return usuario;
    }


Y listo, con esto podremos recuperar a un Usuario dado su nombre de usuario y contraseña (username y password). Probemos que este método funciona. Para esto, en nuestra clase “Main” crearemos un método llamado “creaUsuarios” que se encargará de llenar nuestra tabla de Usuarios con algunos datos. Colocamos el método como sigue:

    private void creaUsuarios()
    {
        try
        {
            AbstractDAO.almacenaEntidad(new Usuario("Usuario de Prueba numero 1", "estudioso", "desvelado"));
            AbstractDAO.almacenaEntidad(new Usuario("Usuario de Prueba numero 2", "caperucita", "loboFeroz"));
            AbstractDAO.almacenaEntidad(new Usuario("Usuario de Prueba numero 3", "empleado", "infelicidad"));
            AbstractDAO.almacenaEntidad(new Usuario("Usuario de Prueba numero 4", "usuarioComun", "password"));
        }
        catch (HibernateException he)
        {
            System.err.println("Ocurrió un error al agregar los usuarios...");
            he.printStackTrace();
        }
    }


Como vemos, aquí usamos el método estático “almacenaEntidad” de clase “AbstractDAO” para guardar 4 Usuarios en la base de datos. Mandamos llamar a este método en el constructor de nuestra clase “Main”.

Ahora creamos un método para probar la recuperación de Usuarios, el cual intentará recuperar al "Usuario de prueba 2", usando su nombre de usuario “caperucita” y su contraseña “loboFeroz”. Si el Usuario se encuentra mostraremos su nombre en la consola, si no mostraremos un mensaje de error. El método queda de la siguiente forma:

    private void buscaUsuario()
    {
        Usuario usuario = null;
        UsuariosDAO usuariosDAO = new UsuariosDAO();

        try
        {
            usuario = usuariosDAO.getUsuario("caperucita", "loboFeroz");
        }catch (HibernateException e)
        {
            System.err.println("Ocurrió un error al recuperar usuario");
            e.printStackTrace();
        }

        if(usuario == null)
        {
            System.out.println("No se encontró al usuario");
        }
        else
        {
            System.out.println("El usuario es: " + usuario.getNombre());
        }
    }


Invocamos a este método en el constructor de la clase “Main”, justo después de la llamada a “creaUsuarios”.

public Main()
{
    creaUsuarios();
    buscaUsuario();
}


Ejecutamos la aplicación y vemos que la salida generada nos indica que, efectivamente, el Usuario recuperado es el Usuario 2, con nombre “Usuario de Prueba numero 2”:


En realidad creo que no hay mucho más que decir en este punto sobre parámetros con nombre, así que lo dejaré hasta aquí para pasar a la siguiente y última parte de este tutorial en la que veremos cómo pasar parámetros a una consulta por si posición.


Parámetros Posicionales estilo JDBC


Este estilo de paso de parámetros les será muy conocido a los qué han trabajado con PreparedStatement en JDBC ya que, igual que en estos últimos establecemos en qué parte de la consulta deseamos que se coloquen los parámetros, haciendo uso de signos de cierre de interrogación “?”, que establecemos después usando la posición de los signos de interrogación (iniciando en 0).

Para establecer los parámetros usando esta forma, nuevamente hacemos uso del método “setParameter” de la interface “org.hibernate.Query” que hemos venido usando hasta el momento.

Para este segundo ejemplo haremos algo un poco más complejo. En primer lugar lo que intentaremos hacer es recuperar todos los Usuarios que han comprado un Producto que ya ha sido dado de baja del catálogo (que su estado sea “INACTIVO”) en el catálogo de Productos y que además, queremos que el código postal de su “Direccion” sea igual al que le pasemos como parámetro a la función.

Como lo que queremos hacer es recuperar una lista de Usuarios la primera parte de nuestra consulta queda de la siguiente manera:

SELECT u FROM Usuario u


En este caso el “SELECT u” es necesario ya que haremos joins con varias otras entidades. Ahora, recordemos que cada “Usuario” está relacionado con la entidad “Producto” mediante las “Compras” que ha realizado, por lo que el siguiente paso será hacer un join entre estas dos entidades. El join será de tipo fetch join ya que queremos que la Compra del Usuario sea recuperada junto con los datos del Usuario, de la siguiente forma:

SELECT u FROM Usuario u JOIN FETCH u.compras c


Y después hacemos nuevamente un join entre las Compras y los Productos:

SELECT u FROM Usuario u JOIN FETCH u.compras c JOIN c.productos p


El paso anterior es necesario ya que tanto las Compras del lado de del Usuario como los Productos del lado de la Compra son colecciones.

Finalmente, colocaremos las dos restricciones que mencionamos anterioremente: que el estatus sea igual a un valor (que posteriormente estableceremos como inactivo y que el código postal sea igual al que le pasemos como parámetros). Recordemos que colocaremos signos de “?” en los lugares en los que queremos que posteriormente sean colocados nuestros parámetros, por lo que la consulta final queda de la siguiente forma:

SELECT u FROM Usuario u JOIN FETCH u.compras c JOIN c.productos p WHERE p.estatus = ? AND u.direccion.codigoPostal = ?


Ahora estableceremos los valores de los parámetros. Como dije antes, para eso usaremos un número que representa la posición de cada uno de los signos de “?”, iniciando en 0. Por lo tanto, la posición del parámetro asociado a “p.estatus” será “0” y la posición del parámetro asociado a “u.direccion.codigoPostal” será "1".

Establecemos los valores de la siguiente forma:

    query.setParameter(0, Producto.Estatus.INACTIVO);
    query.setParameter(1, codigoPostal);


Finalmente, como lo que queremos hacer es obtener una lista de Usuarios usamos el método “list” de la interface “org.hibernate.Query”:

    List<Usuario> listaUsuarios = query.list();


Y ya está. Con esto tenemos todo lista la consulta. Pondremos todo lo anterior en un método llamado “getUsuariosConComprasInactivas” en la clase “UsuadiosDAO”:

    public List<Usuario> getUsuariosConComprasInactivas(String codigoPostal) throws HibernateException
    {
        List<Usuario> listaUsuarios = null;

        try
        {
            iniciaOperacion();

            Query query = getSession().createQuery("FROM Usuario u JOIN u.compras c JOIN c.productos p WHERE p.estatus = ? AND u.direccion.codigoPostal = ?");
            query.setParameter(0, Producto.Estatus.INACTIVO);
            query.setParameter(1, codigoPostal);
            

            listaUsuarios = query.list();

        }catch(HibernateException he)
        {
            he.printStackTrace();
            manejaExcepcion(he);
        }finally
        {
            terminaOperacion();
        }

        return listaUsuarios;
    }


Ahora probemos que este método funciona correctamente. Primero coloquemos algunos datos en la base creando un método llamado “creaCompras” en la clase “Main”. El método queda de la siguiente forma:

    private void creaCompras()
    {
        Producto libros    = new Producto("Libro",  "ABC123456", 120.0F);
        Producto iPads     = new Producto("iPad",   "RAF755576", 1315.45F);
        Producto televisor = new Producto("T.V.",   "AOF765984", 379.64F);
        Producto postales  = new Producto("Postal", "ELF",       15.65F);
        Producto juegos    = new Producto("Videojuego", "MEN", 158.24F);

        libros.setEstatus(Producto.Estatus.INACTIVO);
        postales.setEstatus(Producto.Estatus.INACTIVO);

        Usuario usuario1 = new Usuario("Usuario de Prueba numero 1", "estudioso", "desvelado");
        usuario1.setDireccion(new Direccion("calle principal", "12345"));

        Usuario usuario2 = new Usuario("Usuario de Prueba numero 2", "caperucita", "loboFeroz");
        usuario2.setDireccion(new Direccion("primera avenida", "AVR-175"));

        Usuario usuario3 = new Usuario("Usuario de Prueba numero 3", "empleado", "infelicidad");
        usuario3.setDireccion(new Direccion("puesta del sol", "12345"));

        Usuario usuario4 = new Usuario("Usuario de Prueba numero 4", "usuarioComun", "password");
        usuario4.setDireccion(new Direccion("Este 145", null));

        Compra compraUsuario1 = new Compra();
        compraUsuario1.addProducto(libros);
        usuario1.addCompra(compraUsuario1);

        compraUsuario1 = new Compra();
        compraUsuario1.addProducto(televisor);
        compraUsuario1.addProducto(juegos);
        usuario1.addCompra(compraUsuario1);

        Compra compraUsuario2 = new Compra();
        compraUsuario2.addProducto(iPads);
        compraUsuario2.addProducto(televisor);
        compraUsuario2.addProducto(juegos);
        usuario2.addCompra(compraUsuario2);

        Compra compraUsuario3 = new Compra();
        compraUsuario3.addProducto(iPads);
        compraUsuario3.addProducto(televisor);
        usuario3.addCompra(compraUsuario3);

        compraUsuario3 = new Compra();
        compraUsuario3.addProducto(postales);
        compraUsuario3.addProducto(juegos);
        usuario3.addCompra(compraUsuario3);

        Compra compraUsuario4 = new Compra();
        compraUsuario4.addProducto(libros);
        usuario4.addCompra(compraUsuario4);

        try
        {
            AbstractDAO.almacenaEntidad(libros);
            AbstractDAO.almacenaEntidad(iPads);
            AbstractDAO.almacenaEntidad(televisor);
            AbstractDAO.almacenaEntidad(postales);
            AbstractDAO.almacenaEntidad(juegos);

            AbstractDAO.almacenaEntidad(usuario1);
            AbstractDAO.almacenaEntidad(usuario2);
            AbstractDAO.almacenaEntidad(usuario3);
            AbstractDAO.almacenaEntidad(usuario4);
        }
        catch (HibernateException he)
        {
            System.err.println("Ocurrió un error al agregar los usuarios...");
            he.printStackTrace();
        }
    }


El método anterior se encarga de crear algunos Productos, Usuarios y Compras para estos Usuarios. Ahora crearemos el método que se encargará de ejecutar la consulta que hemos realizado y mostrar en la salida el nombre de los Usuarios que cumplan con la condición y los Productos que compraron:

    private void buscaUsuariosProductosInactivos()
    {
        List<Usuario> listaUsuarios = new UsuariosDAO().getUsuariosConComprasInactivas("12345");

        for(int i = 0; i < listaUsuarios.size(); i++)
        {
            Usuario usuarioActual = listaUsuarios.get(i);
            
            System.out.println("\nUsuario: " + usuarioActual.getNombre());

            List<Compra> listaCompras = usuarioActual.getCompras();

            for(int numeroCompra = 0; numeroCompra < listaCompras.size(); numeroCompra++)
            {
                List<Producto> listaProductos = listaCompras.get(numeroCompra).getProductos();

                for(int numeroProducto = 0; numeroProducto < listaProductos.size(); numeroProducto++)
                {
                    Producto producto = listaProductos.get(numeroProducto);

                    System.out.println("\t" + producto.getNombre());
                }
            }
        }
    }


El método anterior solo muestra en la salida el nombre del Usuario que cumple con las condiciones que establecimos, y los Productos que compró.

Coloquemos los dos métodos anteriores en el constructor de nuestra clase “Main” y ejecutemos la aplicación para ver cuál es la salida generada:



Como podemos ver, hay dos Usuarios que cumplen con la condición de habar comprado un Producto inactivo (Libro y Postal) y que tienen como código postal “12345”.

Pues bien, con esto concluye este tutorial sobre paso de parámetros a consultas HQL. Espero que les sea de utilidad. En el próximo tutorial mostraré cómo usar Hibernate cuando tenemos clases entidad que hacen uso de Herencia (clases que extienden de otras clases).

No olviden dejar sus dudas, comentarios y sugerencias en la sección de comentarios.
Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas:

11 comentarios:

  1. Hola Alex ,ante todo gracias por estos tutoriales ya que me estan ayudando mucho a aprender hibernate. Tengo una duda respecto hql ¿se puede aplicar en una consulta las funciones de upper, lower, trim?

    ResponderEliminar
  2. Hola Elizabet;

    Si, claro que es posible, y se usan igual que en SQL estandar.

    Saludos

    ResponderEliminar
  3. Hola,
    Sigo tus tutoriales con interés y me han ido muy bien para aprender. Hasta hoy no habia tenido ningun problema pero con la aplicación de este tutorial tengo uno.
    Se produce una excepcion en la clase UsuarioDAO metodo getUsuario.
    Ocurrió un error al recuperar usuario
    org.hibernate.TransactionException: Transaction not successfully started
    No se encontró al usuario
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:100)
    at hibernateparametros.dao.AbstractDAO.terminaOperacion(AbstractDAO.java:26)
    at hibernateparametros.dao.UsuariosDAO.getUsuario(UsuariosDAO.java:33)
    at hibernateparametros.Main.buscaUsuario(Main.java:51)
    at hibernateparametros.Main.(Main.java:21)
    at hibernateparametros.Main.main(Main.java:25)

    Hasta este puntod todo funciona bien, se crea el esquema en bd, se introducen los usuarios, etc.

    Alguna idea?

    Gracias.

    ResponderEliminar
  4. Hola tocandolapera :S.

    Pues se me ocurre que puede ser porque hay algún problema en tu archivo de configuración (tal vez, la base de datos no existe, o el username o el password). Lo que puedes hacer es poner un printStackTrace en el método manejaExcepcion o en el catch de getUsuario.

    Prueba y si no, seguimos buscando el problema.

    Saludos.

    ResponderEliminar
  5. Primero que nada quiero agradecerte en nombre mio y de muchos otros que siguiendo tus publicaciones hemos aprendido sobre hibernate e ireport.
    Ahora en el caso de la clase AbstractDAO puedo leer que solo el el metodo almacenaEntidad abres y cierras la sesion con los metodos
    dummy.iniciaOperacion(); dummy.getSession().saveOrUpdate(entidad);
    dummy.getSession().flush();
    y en todos los metodos siguiente no cierras la sesion, ahora mi pregunta quiza un poco tonta es que pasa con estas sesiones se crea una cada ves que invocas el metodo iniciaOperacion()
    o se usa la misma sesion si se abre otra sesion que pasa con las otras que se abren en los demas metodos crud

    saludos cordiales

    edwin alvarenga

    ResponderEliminar
  6. Hola Edwin;

    Muchas gracias por tus comentarios, que bueno que el blog ha servido :D.

    En realidad en cada uno de los métodos se cierra la sesión, en el bloque finally, donde se invoca dummy.terminaOperacion(). En realidad es ese el método el que se encarga de cerrar la sesión y está en el bloque finally, justamente para asegurar que esta se cerrará, ya sea que haya una excepción o no ;).

    El método "flush" de la sesión lo que hace es indicarle a Hibernate que queremos que la operación indicada se realice en ese instante, en vez de esperar a que este lo haga por su cuenta (el cual por cierto es uno de los mecanismos que se supone que tiene).

    De todas formas, contestando a tu pregunta: si no cierras una conexión corres el riesgo de dos cosas. La primera es que la operación que estas intentado realizar nunca ocurra, ya que la sesión seguirá abierta y Hibernate no sabrá en que momento la terminarás.

    El segundo riesgo en realidad es más grande. Cuando abres una sesión y tu apliación está usando un pool de conexiones, tomas una conexión de este pool. Si no cierras la sesión, la conexión nunca será regresada al pool, y por lo tanto llegará el momento en el que no tendrás más conexiones disponibles, y ya no podrás hacer operaciones en tu base de datos (lo cual se te avisará con su correspondiente excepción). En caso de que no estes usando un pool, de todas formas, Hibernate irá realizando conexiones a tu base de datos. Los manejadores de bases de datos tiene un número límitado de conexiones que pueden tener abiertas de forma simultanea; si no terminas la sesión ocurrirá lo mismo, se terminaran las conexiones que puedes hacer a tu base de datos y ya no podrás realizar otra operación.

    Espero que esto resuelva tu duda. Cualquier otra pregunta que tengas, no dudes en hacerla y con todo gusto trataré de responderla.

    Saludos.

    ResponderEliminar
  7. Gracias por tu pronta respuesta, ahora tengo claro que es lo que sucede en la clase AbstractDAO, solo una peticion si no es mucha molestia, sera que en un futuro podrias considerar seguir con esta saga de tutoriales explicados con peras y manzanas.
    un tutorial trabajar hibernate con spring, utilizando HibernateTemplate


    saludos cordiales

    ResponderEliminar
  8. Que buenos tutoriales yo no puedo creer todabia que este tipo de informacion sea gratis jajajaja te pasas Alex un espectaculo todo lo que haces..!!!

    Graciasss :'(

    ResponderEliminar
  9. Una vez más, muchas gracias por esta gran ayuda Alex!

    Solo quería comentar que me quemé la cabeza casi 2 horas con la excepción que también le dió a uno que comentó mas arriba ( y hace un año)...

    el error es una jodida "s" demás en la consulta que tu haces!!!!! pusiste "Ususario" donde va "Usuario" ^-^ !!!! y como es una simple cadena de texto no me daba cuenta por que no me lo marcaba el IDE!!!!!!

    Igual aprendí la lección: probar SIEMPRE toda consulta hql por separado en algun hql editor... :D

    Saludos!

    ResponderEliminar
  10. buenicimos los tutos..la verdad un genio..tengo una duda, se pueden manejar logins y usuarios a nivel de base de datos desde la aplicaion??

    ResponderEliminar
  11. Saludos desde Mallorca y gracias por tu gran labor.

    Una pregunta :

    Como hago la llamada desde el Main , al método de AbstractDAO , public static List getListaEntidades(Class claseEntidad) ....

    Si por ejemplo quisiera obtener una lista de entidades Usuario?

    Muchas gracias y saludos!!

    ResponderEliminar