Hibernate.orgCommunity Documentation

Capítulo 17. SQL Nativo

17.1. Uso de una SQLQuery
17.1.1. Consultas escalares
17.1.2. Consultas de entidades
17.1.3. Manejo de asociaciones y colecciones
17.1.4. Devolución de entidades múltiples
17.1.5. Devolución de entidades no-administradas
17.1.6. Manejo de herencias
17.1.7. Parámetros
17.2. Consultas SQL nombradas
17.2.1. Utilización de la propiedad return para especificar explícitamente los nombres de columnas/alias
17.2.2. Utilización de procedimientos para consultas
17.3. Personalice SQL para crear, actualizar y borrar
17.4. Personalice SQL para cargar

También puede expresar sus consultas en el dialecto SQL nativo de su base de datos. Esto es útil si quiere utilizar las características especificas de la base de datos tales como hints de consulta o la palabra clave CONNECT en Oracle. También proporciona una ruta de migración limpia desde una aplicación basada en SQL/JDBC a Hibernate.

Hibernate3 le permite especificar SQL escrito a mano, incluyendo procedimientos almacenados para todas las operaciones create, update, delete y load.

La ejecución de consultas SQL nativas se controla por medio de la interfaz SQLQuery, la cual se obtiene llamando a Session.createSQLQuery(). Las siguientes secciones describen cómo utilizar esta API para consultas.

La consulta SQL más básica es para obtener a una lista de escalares (valores).

sess.createSQLQuery("SELECT * FROM CATS").list();

sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();

Estas retornarán una lista de objetos arrays (Object[]) con valores escalares para cada columna en la tabla CATS. Hibernate utilizará ResultSetMetadata para deducir el orden real y los tipos de los valores escalares retornados.

Para evitar los gastos generales de la utilización de ResultSetMetadata o simplemente para ser más explícito en lo que se devuelve se puede utilizar addScalar():

sess.createSQLQuery("SELECT * FROM CATS")

 .addScalar("ID", Hibernate.LONG)
 .addScalar("NAME", Hibernate.STRING)
 .addScalar("BIRTHDATE", Hibernate.DATE)

Se especifica esta consulta:

Esto retornará objetos arrays, pero no utilizará ResultSetMetdata sino que obtendrá explícitamente las columnas de IDENTIFICACION, NOMBRE y FECHA DE NACIMIENTO respectivamente como Larga, Cadena y Corta del grupo de resultados subyacente. Esto también significa que sólamente estas tres columnas serán retornadass aunque la consulta este utilizando * y pueda devolver más de las tres columnas enumeradas.

Es posible dejar afuera la información de tipo para todos o algunos de los escalares.

sess.createSQLQuery("SELECT * FROM CATS")

 .addScalar("ID", Hibernate.LONG)
 .addScalar("NAME")
 .addScalar("BIRTHDATE")

Esto es esencialmente la misma consulta que antes, pero ahora se utiliza ResultSetMetaData para determinar el tipo de NOMBRE y FECHA DE NACIMIENTO, mientras que el tipo de IDENTIFICACION se especifica explícitamente.

El dialecto controla la manera en que los java.sql.Types retornados de ResultSetMetaData se mapean a los tipos de Hibernate. Si un tipo en especial no se encuentra mapeado o no resulta en el tipo esperado es posible personalizarlo por medio de llamadas a registerHibernateType en el dialecto.

Hasta ahora se ha asumido que los nombres de las columnas del grupo de resultados son las mismas que los nombres de columnas especificados en el documento de mapeo. Esto puede llegar a ser problemático para las consultas SQL que unen múltiples tablas ya que los mismos nombres de columnas pueden aparecer en más de una tabla.

Se necesita una inyección de alias en las columnas en la siguiente consulta (que con mucha probabilidad fallará):

sess.createSQLQuery("SELECT c.*, m.*  FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")

 .addEntity("cat", Cat.class)
 .addEntity("mother", Cat.class)

La intención de esta consulta es retornar dos instancias Cat por fila: un gato y su mamá. Sin embargo, esto fallará debido a que hay un conflicto de nombres;las instancias se encuentran mapeadas a los mismos nombres de columna. También en algunas bases de datos los alias de las columnas retornadas serán con mucha probabilidad de la forma "c.IDENTIFICACION", "c.NOMBRE", etc, los cuales no son iguales a las columnas especificadas en los mapeos ("IDENTIFICACION" y "NOMBRE").

La siguiente forma no es vulnerable a la duplicación de nombres de columnas:

sess.createSQLQuery("SELECT {cat.*}, {mother.*}  FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")

 .addEntity("cat", Cat.class)
 .addEntity("mother", Cat.class)

Se especifica esta consulta:

La anotación {cat.*} y {mother.*} que se utilizó anteriormente es la abreviatura para "todas las propiedades". Opcionalmente puede enumerar las columnas explícitamente, pero inclusive en este caso Hibernate inyecta los alias de columnas SQL para cada propiedad. El espacio para un alias de columna es sólamente el nombre calificado de la propiedad del alias de la tabla. En el siguiente ejemplo, recuperamos Cats y sus madres desde una tabla diferente (cat_log) a la declarada en los meta datos de mapeo. Inclusive puede utilizar los alias de propiedad en la cláusula where.

String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +

         "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
         "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql)
        .addEntity("cat", Cat.class)
        .addEntity("mother", Cat.class).list()

Las consultas SQL nombradas se pueden definir en el documento de mapeo y se pueden llamar de la misma manera que una consulta HQL nombrada. En este caso, no necesitamos llamar a addEntity().


<sql-query name="persons">
    <return alias="person" class="eg.Person"/>
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex}
    FROM PERSON person
    WHERE person.NAME LIKE :namePattern
</sql-query
>
List people = sess.getNamedQuery("persons")

    .setString("namePattern", namePattern)
    .setMaxResults(50)
    .list();

El elemento <return-join> se utiliza para unir asociaciones y el elemento <load-collection> se usa para definir consultas, las cuales dan inicio a colecciones.


<sql-query name="personsWith">
    <return alias="person" class="eg.Person"/>
    <return-join alias="address" property="person.mailingAddress"/>
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex},
           address.STREET AS {address.street},
           address.CITY AS {address.city},
           address.STATE AS {address.state},
           address.ZIP AS {address.zip}
    FROM PERSON person
    JOIN ADDRESS address
        ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
    WHERE person.NAME LIKE :namePattern
</sql-query
>

Una consulta SQL nombrada puede devolver un valor escalar. Tiene que declarar el alias de la columna y el tipo de Hibernate utilizando el elemento <return-scalar>:


<sql-query name="mySqlQuery">
    <return-scalar column="name" type="string"/>
    <return-scalar column="age" type="long"/>
    SELECT p.NAME AS name,
           p.AGE AS age,
    FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query
>

Puede externalizar el grupo de resultados mapeando información en un elemento <resultset>, el cual le permitirá reutilizarlos a través de consultas nombradas o por medio de la API setResultSetMapping().


<resultset name="personAddress">
    <return alias="person" class="eg.Person"/>
    <return-join alias="address" property="person.mailingAddress"/>
</resultset>

<sql-query name="personsWith" resultset-ref="personAddress">
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex},
           address.STREET AS {address.street},
           address.CITY AS {address.city},
           address.STATE AS {address.state},
           address.ZIP AS {address.zip}
    FROM PERSON person
    JOIN ADDRESS address
        ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
    WHERE person.NAME LIKE :namePattern
</sql-query
>

Opcionalmente, puede utilizar el grupo de resultados mapeando la información en sus archivos hbm directamente en código java.

List cats = sess.createSQLQuery(

        "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
    )
    .setResultSetMapping("catAndKitten")
    .list();

Con <return-property> usted puede decirle a Hibernate explícitamente qué alias de columnas se deben utilizar, en vez de utilizar la sintaxis {} para dejar que Hibernate inyecte sus propios alias. Por ejemplo:


<sql-query name="mySqlQuery">
    <return alias="person" class="eg.Person">
        <return-property name="name" column="myName"/>
        <return-property name="age" column="myAge"/>
        <return-property name="sex" column="mySex"/>
    </return>
    SELECT person.NAME AS myName,
           person.AGE AS myAge,
           person.SEX AS mySex,
    FROM PERSON person WHERE person.NAME LIKE :name
</sql-query>

<return-property> también funciona con columnas múltiples. Esto resuelve una limitación con la sintaxis {}, la cual no puede permitir control muy detallado de propiedades multi-columnas.


<sql-query name="organizationCurrentEmployments">
    <return alias="emp" class="Employment">
        <return-property name="salary">
            <return-column name="VALUE"/>
            <return-column name="CURRENCY"/>
        </return-property>
        <return-property name="endDate" column="myEndDate"/>
    </return>
        SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
        STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
        REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
        FROM EMPLOYMENT
        WHERE EMPLOYER = :id AND ENDDATE IS NULL
        ORDER BY STARTDATE ASC
</sql-query
>

En este ejemplo utilizamos <return-property> en combinación junto con la sintaxis {} para inyección. Esto le permite a los usuarios escoger cómo quieren referirse a la columna y a las propiedades.

Si su mapeo tiene un discriminador usted tiene que utilizar <return-discriminator> para especificar la columna discriminadora.

Hibernate 3 brinda soporte para consultas por medio de procedimientos almacenados y funciones. La mayoría de la siguiente documentación es igual para ambos. La función/procedimiento almacenado tiene que retornar un grupo de resultados como el primer parámetro de salida para poder trabajar con Hibernate. A continuación hay un ejemplo de tal función almacenada en Oracle 9 y posteriores:


CREATE OR REPLACE FUNCTION selectAllEmployments
    RETURN SYS_REFCURSOR
AS
    st_cursor SYS_REFCURSOR;
BEGIN
    OPEN st_cursor FOR
 SELECT EMPLOYEE, EMPLOYER,
 STARTDATE, ENDDATE,
 REGIONCODE, EID, VALUE, CURRENCY
 FROM EMPLOYMENT;
      RETURN  st_cursor;
 END;

Para utilizar esta consulta en Hibernate u.d necesita mapearla por medio de una consulta nombrada.


<sql-query name="selectAllEmployees_SP" callable="true">
    <return alias="emp" class="Employment">
        <return-property name="employee" column="EMPLOYEE"/>
        <return-property name="employer" column="EMPLOYER"/>
        <return-property name="startDate" column="STARTDATE"/>
        <return-property name="endDate" column="ENDDATE"/>
        <return-property name="regionCode" column="REGIONCODE"/>
        <return-property name="id" column="EID"/>
        <return-property name="salary">
            <return-column name="VALUE"/>
            <return-column name="CURRENCY"/>
        </return-property>
    </return>
    { ? = call selectAllEmployments() }
</sql-query
>

Los procedimientos almacenados actualmente sólo retornan escalares y entidades. No se soporta <return-join> ni <load-collection> .

Para utilizar procedimientos almacenados con Hibernate, debe seguir ciertas reglas de funciones/procedimientos. Si no siguen esas reglas entonces no se pueden utilizar con Hibernate. Si todavía quiere utilizar estos procedimientos tiene que ejecutarlos por medio de session.connection(). Las reglas son diferentes para cada base de datos debido a que los vendedores de la base de datos tienen diferentes sintaxis/semántica de procedimientos almacenados.

Las consultas de procedimientos almacenados no se pueden llamar con setFirstResult()/setMaxResults().

La forma de la llamada recomendada es SQL92 estándar: { ? = call functionName(<parameters>) } o { ? = call procedureName(<parameters>}. No se soporta la sintaxis de llamadas nativas.

Para Oracle aplican las siguientes reglas:

Para Sybase o el servidor MS SQL aplican las siguientes reglas:

Hibernate3 can use custom SQL for create, update, and delete operations. The SQL can be overridden at the statement level or inidividual column level. This section describes statement overrides. For columns, see Sección 5.7, “Expresiones de lectura y escritura de columnas”.

Los persistentes de clase y de colección en Hibernate ya contienen un grupo de cadenas generadas en tiempo de configuración (insertsql, deletesql, updatesql, etc.). Las etiquetas de mapeo <sql-insert>, <sql-delete> y <sql-update> sobrescriben estas cadenas:


<class name="Person">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name" not-null="true"/>
    <sql-insert
>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
    <sql-update
>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
    <sql-delete
>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class
>

El SQL se ejecuta directamente en su base de datos asi que usted es libre de utilizar cualquier dialecto que desee. Esto reducirá la portabilidad de su mapeo si utiliza una base de datos especifica de SQL.

Los procedimientos almacenados se encuentran soportados si el atributo callable está configurado:


<class name="Person">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name" not-null="true"/>
    <sql-insert callable="true"
>{call createPerson (?, ?)}</sql-insert>
    <sql-delete callable="true"
>{? = call deletePerson (?)}</sql-delete>
    <sql-update callable="true"
>{? = call updatePerson (?, ?)}</sql-update>
</class
>

El orden de los parámetros posicionales es vital ya que se deben encontrar en la misma secuencia en que Hibernate los espera.

Puede ver el orden esperado habilitando el registro de depuración para el nivel org.hibernate.persister.entity. Con este nivel habilitado Hibernate imprimirá el SQL estático que se utiliza para crear, actualizar, borrar, etc, entidades, ( para ver la secuencia esperada, recuerde no incluir su SQL personalizado en los archivos de mapeo ya que eso sobrescribirá el sql estático generado por Hibernate).

En la mayoría de los casos se requiere que los procedimientos almacenados retornen el número de filas insertadas, actualizadas y borradas ya que Hibernate tiene algunas verificaciones en tiempo de ejecución para el éxito de la declaración. Hibernate siempre registra el primer parámetro de la declaración como un parámetro de la salida numérica para las operaciones CUD:

CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
    RETURN NUMBER IS
BEGIN

    update PERSON
    set
        NAME = uname,
    where
        ID = uid;

    return SQL%ROWCOUNT;

END updatePerson;

You can also declare your own SQL (or HQL) queries for entity loading. As with inserts, updates, and deletes, this can be done at the individual column level as described in Sección 5.7, “Expresiones de lectura y escritura de columnas” or at the statement level. Here is an example of a statement level override:


<sql-query name="person">
    <return alias="pers" class="Person" lock-mode="upgrade"/>
    SELECT NAME AS {pers.name}, ID AS {pers.id}
    FROM PERSON
    WHERE ID=?
    FOR UPDATE
</sql-query
>

Esta es tan sólo una declaración de consulta nombrada, como se discutió anteriormente. Puede referenciar esta consulta nombrada en un mapeo de clase:


<class name="Person">
    <id name="id">
        <generator class="increment"/>
    </id>
    <property name="name" not-null="true"/>
    <loader query-ref="person"/>
</class
>

Esto funciona inclusive con procedimientos almacenados.

Puede incluso definir una consulta para la carga de colección:


<set name="employments" inverse="true">
    <key/>
    <one-to-many class="Employment"/>
    <loader query-ref="employments"/>
</set
>

<sql-query name="employments">
    <load-collection alias="emp" role="Person.employments"/>
    SELECT {emp.*}
    FROM EMPLOYMENT emp
    WHERE EMPLOYER = :id
    ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query
>

También puede definir un cargador de entidad que cargue una colección con una unión temprana:


<sql-query name="person">
    <return alias="pers" class="Person"/>
    <return-join alias="emp" property="pers.employments"/>
    SELECT NAME AS {pers.*}, {emp.*}
    FROM PERSON pers
    LEFT OUTER JOIN EMPLOYMENT emp
        ON pers.ID = emp.PERSON_ID
    WHERE ID=?
</sql-query
>