Hibernate.orgCommunity Documentation
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:
la cadena de consulta SQL
las columnas y tipos que se devuelven
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.
Todas las consultas anteriores eran sobre los valores escalraes devueltos, basicamente devolviendo los valores "crudos" desde el grupo resultado. Lo siguiente muestra como obtener los objetos entidades desde una consulta sql nativa por medio de addEntity()
.
sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
Se especifica esta consulta:
la cadena de consulta SQL
la entidad devuelta por la consulta
Asumiendo que Cat es mapeado como una clase con las columnas IDENTIFICACION, NOMBRE y FECHA DE NACIMIENTO las consultas anteriores devolverán una Lista en donde cada elemento es una entidad Cat.
Si la entidad es mapeada con una many-to-one
a otra entidad tambien se necesita que devuelva esto cuando realice una consulta nativa, de otra manera, aparecerá un error "no se encontró la columna" específico a la base de datos. Se devolverán automáticamente las columnas adicionales cuando se utiliza la anotación *, pero preferimos ser tan explícitos así como lo muestra el siguiente ejemplo para una many-to-one
a un Dog
:
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
Esto permitirá que cat.getDog() funcione apropiadamente.
Es posible unir de manera temprana en el Dog
para evitar el posible viaje de ida y vuelta para iniciar el proxy. Esto se hace por medio del método addJoin()
, el cual le permite unirse en una asociación o colección.
sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dog");
En este ejemplo los Cat
s retornados tendrán su propiedad dog
completamente iniciada sin ningún viaje extra de ida y vuelta a la base de datos. Observe que agregó un nombre alias ("cat") para poder especificar la ruta de la propiedad de destino de la unión. Es posible hacer la misma unión temprana para colecciones, por ejemplo, si el Cat
tuviese en lugar un Dog
uno-a-muchos.
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dogs");
En este punto estamos alcanzando los límites de lo que es posible con las consultas nativas sin empezar a mejorar las consultas sql para hacerlas utilizables en Hibernate. Los problemas empiezan a surgir cuando las entidades múltiples retornadas son del mismo tipo o cuando no son suficientes los nombres de las columnas/alias predeterminados.
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 cadena de consultas SQL, con un espacio reservado para que Hibernate inserte alias de columnas
las entidades devueltas por la 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()
Para la mayoría de los casos, se necesita la inyección del alias anterior. Para las consultas relacionadas con mapeos más complejos como propiedades compuestas, discriminadores de herencia, colecciones, etc, existen alias especificos a utilizar para permitir que Hibernate inyecte los alias apropiados.
La siguiente tabla muestra las diferentes maneras de utilizar la inyección de alias. Note que los nombres alias en el resultado son simplemente ejemplos; cada alias tendrá un nombre único y probablemente diferente cuando se utilice.
Tabla 17.1. Nombres con inyección alias
Descripción | Sintaxis | Ejemplo |
---|---|---|
Una propiedad simple | {[aliasname].[propertyname] | A_NAME as {item.name} |
Una propiedad compuesta | {[aliasname].[componentname].[propertyname]} | CURRENCY as {item.amount.currency}, VALUE as {item.amount.value} |
Discriminador de una entidad | {[aliasname].class} | DISC as {item.class} |
Todas las propiedades de una entidad | {[aliasname].*} | {item.*} |
Una clave de colección | {[aliasname].key} | ORGID as {coll.key} |
La identificación -id- de una colección | {[aliasname].id} | EMPID as {coll.id} |
El elemento de una colección | {[aliasname].element} | XID as {coll.element} |
propiedad del elemento en la colección | {[aliasname].element.[propertyname]} | NAME as {coll.element.name} |
Todas las propiedades del elemeto en la colección | {[aliasname].element.*} | {coll.element.*} |
Todas las propiedades de la colección | {[aliasname].*} | {coll.*} |
Es posible aplicar un ResultTransformer para consultas SQL nativas, permitiéndole retornar entidades no-administradas.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
Se especifica esta consulta:
la cadena de consulta SQL
un transformador de resultado
La consulta anterior devolverá una lista de CatDTO
a la cual se ha instanciado e inyectado los valores de NOMBRE y FECHA DE NACIMIENTO en su propiedades o campos correspondientes.
Las consultas SQL nativas, las cuales consultan por entidades que son mapeadas como parte de una herencia tienen que incluir todas las propiedades para la clase base y todas sus subclases.
Las consultas SQL nativas soportan parámetros nombrados así como posicionales:
Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
List pusList = query.setString(0, "Pus%").list();
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);
List pusList = query.setString("name", "Pus%").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:
Una función tiene que retornar un grupo de resultados. El primer parámetro de un procedimiento tiene que ser un OUT
que retorna un grupo de resultados. Esto se hace utilizando un tipo SYS_REFCURSOR
en Oracle 9 o 10. En Oracle necesita definir un tipo REF CURSOR
. Consulte la documentación de Oracle para obtener mayor información.
Para Sybase o el servidor MS SQL aplican las siguientes reglas:
El procedimiento tiene que retornar un grupo de resultados. Observe que debido a que estos servidores pueden retornar grupos de resultados múltiples y cuentas actualizadas, Hibernate iterará los resultados y tomará el primer resultado que sea un grupo resultados como su valor retornado. Todo lo demás será descartado.
Si puede habilitar SET NOCOUNT ON
en su procedimiento probablemente será más eficiente, pero no es un requerimiento.
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
>
Copyright © 2004 Red Hat, Inc.