477 lines
20 KiB
XML
477 lines
20 KiB
XML
<chapter id="querysql" revision="2">
|
|
<title>SQL Nativo</title>
|
|
|
|
<para>
|
|
Puedes también expresar consultas en el dialecto SQL nativo de tu base de datos. Esto es útil si quieres
|
|
utilizar aspectos específicos de base de datos tal como consejos (hints) de consulta o la palabra clave
|
|
<literal>CONNECT</literal> en Oracle. Provee además una clara ruta de migración desde una aplicación
|
|
basada en SQL/JDBC directo a Hibernate.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate3 te permite especificar SQL escrito a mano (incluyendo procedimientos almacenados) para todas
|
|
las operaciones de creación, actualización, borrado y carga.
|
|
</para>
|
|
|
|
<sect1 id="querysql-creating">
|
|
<title>Creando una <literal>Query</literal> de SQL nativo</title>
|
|
|
|
<para>
|
|
Las consultas SQL se controlan por medio de la interface <literal>SQLQuery</literal>, que se obtiene
|
|
llamando a <literal>Session.createSQLQuery()</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[List cats = sess.createSQLQuery("select {cat.*} from cats cat")
|
|
.addEntity("cat", Cat.class)
|
|
.setMaxResults(50)
|
|
.list();]]></programlisting>
|
|
|
|
<para>
|
|
Esta consulta especificada:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
la cadena de consulta SQL, con un lugar para que Hibernate inyecte los alias de columnas
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
la entidad devuelta por la consulta, y sus alias de tablas SQL
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
El método <literal>addEntity()</literal> asocia alias de tablas SQL con clases de entidad,
|
|
y determina la forma del conjunto resultado de la consulta.
|
|
</para>
|
|
|
|
<para>
|
|
El método <literal>addJoin()</literal> puede ser usado para cargar asociaciones a otras entidades y
|
|
colecciones.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[List cats = sess.createSQLQuery(
|
|
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
|
|
)
|
|
.addEntity("cat", Cat.class)
|
|
.addJoin("kitten", "cat.kittens")
|
|
.list();]]></programlisting>
|
|
|
|
<para>
|
|
Una consulta SQL nativa podría devolver un valor escalar simple o una combinación de escalares y entidades.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
|
|
.addScalar("maxWeight", Hibernate.DOUBLE);
|
|
.uniqueResult();]]></programlisting>
|
|
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="querysql-aliasreferences">
|
|
<title>Alias y referencias de propiedad</title>
|
|
|
|
<para>
|
|
La notación <literal>{cat.*}</literal> usada arriba es un atajo para "todas las propiedades".
|
|
Alternativamente, puedes listar las columnas explícitamente, pero incluso en este caso dejamos
|
|
que Hibernate inyecte los alias de columnas SQL para cada propiedad. El lugar para un alias de columna
|
|
es sólo el nombre de propiedad cualificado por el alias de la tabla. En el siguiente ejemplo,
|
|
recuperamos <literal>Cat</literal>s de una tabla diferente (<literal>cat_log</literal>) a una
|
|
declarada en los metadatos de mapeo. Nota que podríamos incluso usar los alias de propiedad en la
|
|
cláusula where si quisieramos.
|
|
</para>
|
|
<para>
|
|
La sintáxis <literal>{}</literal> <emphasis>no</emphasis> es requerida para consultas con nombre.
|
|
Ver <xref linkend="querysql-namedqueries"/>
|
|
</para>
|
|
|
|
<programlisting><![CDATA[String sql = "select cat.originalId as {cat.id}, " +
|
|
"cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +
|
|
"cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +
|
|
"from cat_log cat where {cat.mate} = :catId"
|
|
|
|
List loggedCats = sess.createSQLQuery(sql)
|
|
.addEntity("cat", Cat.class)
|
|
.setLong("catId", catId)
|
|
.list();]]></programlisting>
|
|
|
|
<para>
|
|
<emphasis>Nota:</emphasis> si listas cada propiedad explícitamente, ¡debes incluir todas las
|
|
propiedades de la clase <emphasis>y sus subclases</emphasis>!
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="querysql-namedqueries" revision="2">
|
|
<title>Consultas SQL con nombre</title>
|
|
|
|
<para>
|
|
Las consultas SQL con nombre pueden definirse en el documento de mapeo y llamadas exactamente
|
|
en la misma forma en que a una consulta HQL con nombre. En este caso, <emphasis>no</emphasis>
|
|
necesitamos llamar a <literal>addEntity()</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<programlisting><![CDATA[List people = sess.getNamedQuery("persons")
|
|
.setString("namePattern", namePattern)
|
|
.setMaxResults(50)
|
|
.list();]]></programlisting>
|
|
|
|
<para>
|
|
Los elementos <literal><return-join></literal> y <literal><load-collection></literal>
|
|
se usan para unir asociaciones y definir consultas que inicialicen colecciones, respectivamente.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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},
|
|
adddress.STREET AS {address.street},
|
|
adddress.CITY AS {address.city},
|
|
adddress.STATE AS {address.state},
|
|
adddress.ZIP AS {address.zip}
|
|
FROM PERSON person
|
|
JOIN ADDRESS adddress
|
|
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
|
|
WHERE person.NAME LIKE :namePattern
|
|
</sql-query>]]></programlisting>
|
|
|
|
<para>
|
|
Una consulta SQL con nombre puede devolver un valor escalar. Debes especificar el alias de columna y
|
|
tipo Hibernate usando el elementp <literal><return-scalar></literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<sect2 id="propertyresults">
|
|
<title>Usando return-property para especificar explícitamente nombres de columna/alias</title>
|
|
|
|
<para>
|
|
Con <literal><return-property></literal> puedes decirle explícitamente a Hibernate qué
|
|
alias de columna usar, en vez de usar la sintáxis <literal>{}</literal> para dejar que Hibernate
|
|
inyecte sus propios alias.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
<literal><return-property></literal> también trabaja con múltiples columnas. Esto resuelve una
|
|
limitación de la sintáxis <literal>{}</literal>, la cual no puede permitir un control fino de propiedades
|
|
multi-columna.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Nota que en este ejemplo hemos usado <literal><return-property></literal> en combinación con
|
|
la sintáxis <literal>{}</literal> para inyección, permitiendo a los usuarios elejir cómo quieren
|
|
referirse a las columnas y propiedades.
|
|
</para>
|
|
|
|
<para>
|
|
Si tu mapeo tiene un discriminador debes usar <literal><return-discriminator></literal>
|
|
para especificar la columna discriminadora.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="sp_query">
|
|
<title>Usando procedimientos almacenados para consultar</title>
|
|
|
|
<para>
|
|
Hibernate3 introduce soporte para consultas vía procedimientos almacenados. Los procedimientos
|
|
almacenados deben devolver un conjunto resultado como el primer parámetro de salida para ser
|
|
capaces de funcionar con Hibernate. Un ejemplo de uno procedimiento almacenado en Oracle 9
|
|
o superior es así:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[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;]]></programlisting>
|
|
|
|
<para>
|
|
Para usar esta consulta en Hibernate necesitas mapearla por medio de una consulta con nombre.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Nota que los procedimientos almacenados sólo devuelven escalares y entidades.
|
|
No están soportados <literal><return-join></literal> y <literal><load-collection></literal>.
|
|
</para>
|
|
|
|
<sect3 id="querysql-limits-storedprocedures">
|
|
<title>Reglas/limitaciones para usar procedimientos almacenados</title>
|
|
|
|
<para>
|
|
Para usar procedimientos almacenados con Hibernate los procedimientos tienen que seguir algunas reglas.
|
|
Si no siguen esas reglas no son usables por Hibernate. Si aún quisieras usar estos procedimientos
|
|
tendrías que ejecutarlos por medio de <literal>session.connection()</literal>. Las reglas son
|
|
diferentes para cada base de datos, ya que los vendedores de base de datos tienen diferentes
|
|
semánticas/sintáxis de procedimientos almacenados.
|
|
</para>
|
|
|
|
<para>
|
|
Las consultas de procedimientos almacenados no pueden ser paginadas con
|
|
<literal>setFirstResult()/setMaxResults()</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Para Oracle se aplican las siguientes reglas:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
El procedimiento debe devolver un conjunto resultado. Esto se hace devolviendo un
|
|
<literal>SYS_REFCURSOR</literal> en Oracle 9 o 10. En Oracle necesitas definir un
|
|
tipo <literal>REF CURSOR</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
La forma recomendada es <literal>{ ? = call procName(<parameters>) }</literal> o
|
|
<literal>{ ? = call procName }</literal> (esto es más una regla de Oracle que una regla de Hibernate).
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Para Sybase o MS SQL server se aplican las siguientes reglas:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
El procedimiento debe devolver un conjunto resultado. Nota que ya que estos servidores pueden
|
|
y devolverán múltiples conjuntos resultados y cuentas de actualización, Hibernate iterará
|
|
los resultados y tomará el primer resultado que sea un conjunto resultado como su valor
|
|
a devolver. Todo lo demás será descartado.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Si habilitas <literal>SET NOCOUNT ON</literal> en tu procedimiento será probablemente más
|
|
eficiente, pero esto no es un requerimiento.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</sect3>
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="querysql-cud">
|
|
<title>SQL personalizado para crear, actualizar y borrar</title>
|
|
|
|
<para>
|
|
Hibernate3 puede usar sentencias SQL personalizadas para las operaciones de
|
|
crear, actualizar y borrar. Los persistidores de clases y colecciones en Hibernate
|
|
ya contienen un conjunto de cadenas generadas en tiempo de configuración (insertsql,
|
|
deletesql, updatesql, etc.). Las etiquetas de mapeo <literal><sql-insert></literal>,
|
|
<literal><sql-delete></literal>, y <literal><sql-update></literal> sobrescriben
|
|
estas cadenas:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
El SQL se ejecuta directamente en tu base de datos, de modo que eres libre de usar cualquier
|
|
dialecto que quieras. Esto reducirá, por supuesto, la portabilidad de tu mapeo si usas SQL
|
|
específico de la base de datos.
|
|
</para>
|
|
|
|
<para>
|
|
Los procedimientos almacenados son soportados si está establecido el atributo
|
|
<literal>callable</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
El orden de los parámetros posicionales son actualmente vitales, ya que deben estar en la
|
|
misma secuencia en que las espera Hibernate.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes ver el orden esperado habilitando el registro de depuración para el nivel
|
|
<literal>org.hibernate.persister.entity</literal>. Con este nivel habilitado, Hibernate
|
|
imprimirá el SQL estático que se usa para crear, actualizar, borrar, etc. las entidades.
|
|
(Para ver la secuencia esperada, recuerda no incluir tu SQL personalizado en los ficheros
|
|
de mapeo ya que sobrescribirán el sql estático generado por Hibernate.)
|
|
</para>
|
|
|
|
<para>
|
|
Los procedimientos almacenados son, en la mayoría de los casos (léase, mejor hacerlo que no hacerlo),
|
|
obligados a devolver el número de filas insertadas/actualizadas/borradas, ya que Hibernate tiene algunas
|
|
comprobaciones en tiempo de ejecución del éxito de la sentencia. Hibernate siempre registra el primer
|
|
parámetro de la sentencia como un parámetro de salida numérico para las operaciones CUD:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[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;]]></programlisting>
|
|
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="querysql-load">
|
|
<title>SQL personalizado para carga</title>
|
|
|
|
<para>
|
|
Puedes también declarar tu propias consultas SQL (o HQL) para cargar entidades:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Esto es sólo una declaración de consulta con nombrem como se ha discutido anteriormente.
|
|
Puedes hacer referencia a esta consulta con nombre en un mapeo de clase:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Person">
|
|
<id name="id">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="name" not-null="true"/>
|
|
<loader query-ref="person"/>
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Esto incluso funciona con procedimientos almacenados.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes incluso definit una consulta para la carga de colecciones:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="employments" inverse="true">
|
|
<key/>
|
|
<one-to-many class="Employment"/>
|
|
<loader query-ref="employments"/>
|
|
</set>]]></programlisting>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Podrías incluso definir un cargador de entidades que cargue una colección por
|
|
recuperación por unión (join fetching):
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |