hibernate-orm/reference/fr/modules/query_sql.xml

751 lines
31 KiB
XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<chapter id="querysql" revision="2">
<title>SQL natif</title>
<para>
Vous pouvez aussi écrire vos requêtes dans le dialecte SQL natif de votre base de données.
Ceci est utile si vous souhaitez utiliser les fonctionnalités spécifiques de votre base de
données comme le mot clé <literal>CONNECT</literal> d'Oracle. Cette fonctionnalité offre par ailleurs un moyen
de migration plus propre et doux d'une application basée sur SQL/JDBC vers
une application Hibernate.
</para>
<para>Hibernate3 vous permet de spécifier du SQL écrit à la main (incluant les procédures stockées)
pour toutes les opérations de création, mise à jour, suppression et chargement.</para>
<sect1 id="querysql-creating" revision="4">
<title>Utiliser une <literal>SQLQuery</literal></title>
<para>L'exécution des requêtes en SQL natif est contrôlée par l'interface <literal>SQLQuery</literal>,
laquelle est obtenue en appelant <literal>Session.createSQLQuery()</literal>.
Dans des cas extrêmement simples, nous pouvons utiliser la forme suivante :
</para>
<sect2>
<title>Requêtes scalaires</title>
<para>La requête SQL la plus basique permet de récupérer une liste de (valeurs) scalaires.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
]]></programlisting>
<para>Ces deux requêtes retourneront un tableau d'objets (Object[]) avec
les valeurs scalaires de chacune des colonnes de la table CATS.
Hibernate utilisera le ResultSetMetadata pour déduire l'ordre et le type
des valeurs scalaires retournées.</para>
<para>Pour éviter l'overhead lié à <literal>ResultSetMetadata</literal> ou simplement pour
être plus explicite dans ce qui est retourné, vous pouvez utiliser <literal>addScalar()</literal>.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>la chaîne de caractère SQL</para>
</listitem>
<listitem>
<para>les colonnes et les types retournés</para>
</listitem>
</itemizedlist>
<para>Cela retournera toujours un tableau d'objets, mais sans utiliser le
<literal>ResultSetMetdata</literal>, mais récupèrera explicitement les colonnes
ID, NAME and BIRTHDATE column étant de respectivement de type Long, String et Short,
depuis le resultset sous jacent. Cela signifie aussi que seules ces colonnes seront
retournées même si la requête utilise <literal>*</literal>
et aurait pu retourner plus que les trois colonnes listées.</para>
<para>Il est possible de ne pas définir l'information sur le type pour toutes ou partie
des calaires.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
]]></programlisting>
<para>Il s'agit essentiellement de la même requête que précédemment, mais
le <literal>ResultSetMetaData</literal> est utilisé pour décider des types de NAME
et BIRTHDATE alors que le type de ID est explicitement spécifié.</para>
<para>Les java.sql.Types retournés par le ResultSetMetaData sont mappés aux type Hibernate
via le Dialect. Si un type spécifique n'est pas mappé ou est mappé à un type non souhaité, il
est possible de personnaliser en invoquant <literal>registerHibernateType</literal> dans
le Dialect.</para>
</sect2>
<sect2>
<title>Requêtes d'entités</title>
<para>Les requêtes précédentes ne retournaient que des valeurs scalaires,
retournant basiquement que les valeurs brutes du resultset. Ce qui suit montre
comment récupérer des entités depuis une requête native SQL, grâce à
<literal>addEntity()</literal>.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>La chaîne de caractère de requête SQL</para>
</listitem>
<listitem>
<para>L'entité retournée par la requête</para>
</listitem>
</itemizedlist>
<para>Avec Cat mappé comme classe avec les colonnes ID, NAME
et BIRTHDATE, les requêtes précédentes retournent toutes deux une liste
où chaque élément est une entité Cat.</para>
<para>Si l'entité est mappée avec un <literal>many-to-one</literal> vers
une autre entité, il est requis de retourner aussi cette entité en exécutant
la requête native, sinon une erreur "column not found" spécifique à la base de
données sera soulevée. Les colonnes additionnelles seront automatiquement
retournées en utilisant la notation *, mais nous préférons être explicites
comme dans l'exemple suivant avec le <literal>many-to-one</literal> vers
<literal>Dog</literal>:</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
]]></programlisting>
<para>Ceci permet à cat.getDog() de fonctionner normalement.</para>
</sect2>
<sect2>
<title>Gérer les associations et collections</title>
<para>Il est possible de charger agressivement <literal>Dog</literal> pour
éviter le chargement de proxy qui signifie aller retour supplémentaire vers
la base de données. Ceci est faisable via la méthode <literal>addJoin()</literal>,
qui vous permet de joindre une association ou collection.</para>
<programlisting><![CDATA[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");
]]></programlisting>
<para>Dans cet exemple, les <literal>Cat</literal> retournés auront leur
propriété <literal>dog</literal> entièrement initialisées sans aucun aller/retour
supplémentaire vers la base de données. Notez que nous avons ajouté un alias
("cat") pour être capable de spécifier la propriété cible de la jointure.
Il est possible de faire la même jointure aggressive pour les collections, e.g. si le
<literal>Cat</literal> a un one-to-many vers <literal>Dog</literal>.</para>
<programlisting><![CDATA[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");
]]></programlisting>
<p>Nous arrivons aux limites de ce qui est possible avec les requêtes natives
sans les modifier pour les rendre utilisables par Hibernate; les problèmes
surviennent lorsque nous essayons de retourner des entités du même type ou
lorsque les alias/colonnes par défaut ne sont plus suffisants..</p>
</sect2>
<sect2>
<title>Retour d'entités multiples</title>
<para>Jusqu'à présent, les colonnes du resultset sont supposées être les mêmes
que les colonnes spécifiées dans les fichiers de mapping. Ceci peut
être problématique pour les requêtes SQL qui effectuent de multiples
jointures vers différentes tables, puisque les mêmes colonnes peuvent
apparaître dans plus d'une table.</para>
<para>L'injection d'alias de colonne est requis pour la requête suivante
(qui risque de ne pas fonctionner):</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
]]></programlisting>
<para>Le but de cette requête est de retourner deux instances de Cat par ligne,
un chat et sa mère. Cela échouera puisqu'il y a conflit de nom puisqu'ils sont
mappés au même nom de colonne et que sur certaines base de données, les alias
de colonnes retournés seront plutôt de la forme
"c.ID", "c.NAME", etc. qui ne sont pas égaux aux colonnes spécifiées dans les
mappings ("ID" and "NAME").</para>
<para>La forme suivante n'est pas vulnérable à la duplication des noms de colonnes:</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT {cat.*}, {mother.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>la requête SQL, avec des réceptacles pour qu'Hibernate injecte les alias de colonnes</para>
</listitem>
<listitem>
<para>les entités retournés par la requête</para>
</listitem>
</itemizedlist>
<para>Les notations {cat.*} et {mother.*} utilisées sont un équivalent à 'toutes les propriétés'.
Alternativement, vous pouvez lister les colonnes explicitement, mais même pour ce cas, nous
laissons Hibernate injecter les alias de colonne pour chaque propriété.
Le réceptable pour un alias de colonne est simplement le nom de la propriété
qualifié par l'alias de la table. Dans l'exemple suivant, nous récupérons
les chats et leur mère depuis une table différentes (cat_log) de celle déclarée
dans les mappings. Notez que nous pouvons aussi utiliser les alias de propriété
dans la clause where si nous le voulons.</para>
<programlisting><![CDATA[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()
]]></programlisting>
<sect3 id="querysql-aliasreferences" revision="2">
<title>Références d'alias et de propriété</title>
<para>Pour la plupart des cas précédents, l'injection d'alias est requis,
mais pour les requêtes relatives à des mappings plus complexes, comme
les propriétés composite, les discriminants d'héritage, les collections etc., il
y a des alias spécifiques à utiliser pour permettre à Hibernate l'injection
des bons alias.</para>
<para>Le tableau suivant montre les diverses possiblités d'utilisation
d'injection d'alias. Note: les noms d'alias dans le résultat sont des
exemples, chaque alias aura un nom unique et probablement différent lorsqu'ils
seront utilisés.</para>
<table frame="topbot" id="aliasinjection-summary">
<title>Nom d'injection d'alias</title>
<tgroup cols="3">
<colspec colwidth="1*" />
<colspec colwidth="1*" />
<colspec colwidth="2.5*" />
<thead>
<row>
<entry>Description</entry>
<entry>Syntaxe</entry>
<entry>Exemple</entry>
</row>
</thead>
<tbody>
<row>
<entry>Une propriété simple</entry>
<entry><literal>{[aliasname].[propertyname]</literal></entry>
<entry><literal>A_NAME as {item.name}</literal></entry>
</row>
<row>
<entry>Une propriété composite</entry>
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
<entry><literal>CURRENCY as {item.amount.currency}, VALUE as
{item.amount.value}</literal></entry>
</row>
<row>
<entry>Discriminateur d'une entité</entry>
<entry><literal>{[aliasname].class}</literal></entry>
<entry><literal>DISC as {item.class}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'une entité</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{item.*}</literal></entry>
</row>
<row>
<entry>La clé d'une collection</entry>
<entry><literal>{[aliasname].key}</literal></entry>
<entry><literal>ORGID as {coll.key}</literal></entry>
</row>
<row>
<entry>L'id d'une collection</entry>
<entry><literal>{[aliasname].id}</literal></entry>
<entry><literal>EMPID as {coll.id}</literal></entry>
</row>
<row>
<entry>L'élément d'une collection</entry>
<entry><literal>{[aliasname].element}</literal></entry>
<entry><literal>XID as {coll.element}</literal></entry>
</row>
<row>
<entry>Propriété d'un élément de collection</entry>
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
<entry><literal>NAME as {coll.element.name}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'un élément de collection</entry>
<entry><literal>{[aliasname].element.*}</literal></entry>
<entry><literal>{coll.element.*}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'une collection</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{coll.*}</literal></entry>
</row>
</tbody>
</tgroup>
</table>
</sect3>
</sect2>
<sect2>
<title>Retour d'objet n'étant pas des entités</title>
<para>Il est possible d'appliquer un ResultTransformer à une requête native SQL. Ce qui permet, par exemple, de
retourner des entités non gérées.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>une requête SQL</para>
</listitem>
<listitem>
<para>un transformateur de résultat</para>
</listitem>
</itemizedlist>
<para>
La requête précédente retournera une liste de <literal>CatDTO</literal> qui auront été instanciés
et dans lesquelles les valeurs de NAME et BIRTHNAME auront été injectées dans les propriétés ou champs
correspondants.
</para>
</sect2>
<sect2>
<title>Gérer l'héritage</title>
<para>Les requêtes natives SQL pour les entités prenant part à un héritage
doivent inclure toutes les propriétés de la classe de base et de toutes
ses sous classes.</para>
</sect2>
<sect2>
<title>Paramètres</title>
<para>Les requêtes natives SQL supportent aussi les paramètres nommés:</para>
<programlisting><![CDATA[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(); ]]></programlisting>
</sect2>
</sect1>
<sect1 id="querysql-namedqueries" revision="3">
<title>Requêtes SQL nommées</title>
<para>
Les requêtes SQL nommées peuvent être définies dans le document de mapping
et appelées exactement de la même manière qu'un requête HQL nommée. Dans ce
cas, nous <emphasis>n'avons pas besoin</emphasis> d'appeler <literal>addEntity()</literal>.
</para>
<programlisting>&lt;sql-query name="persons"&gt;
&lt;return alias="person" class="eg.Person"/&gt;
SELECT person.NAME AS {person.name},
person.AGE AS {person.age},
person.SEX AS {person.sex}
FROM PERSON person
WHERE person.NAME LIKE :namePattern
&lt;/sql-query&gt;</programlisting>
<programlisting>List people = sess.getNamedQuery("persons")
.setString("namePattern", namePattern)
.setMaxResults(50)
.list();</programlisting>
<para>
Les éléments <literal>&lt;return-join&gt;</literal> et
<literal>&lt;load-collection&gt;</literal> sont respectivement utilisés pour lier
des associations et définir des requêtes qui initialisent des collections.
</para>
<programlisting>&lt;sql-query name="personsWith"&gt;
&lt;return alias="person" class="eg.Person"/&gt;
&lt;return-join alias="address" property="person.mailingAddress"/&gt;
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
&lt;/sql-query&gt;</programlisting>
<para>
Une requête SQL nommée peut retourner une valeur scalaire. Vous devez
spécifier l'alias de colonne et le type Hibernate utilisant l'élément
<literal>&lt;return-scalar&gt;</literal> :</para>
<programlisting>&lt;sql-query name="mySqlQuery"&gt;
&lt;return-scalar column="name" type="string"/&gt;
&lt;return-scalar column="age" type="long"/&gt;
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
&lt;/sql-query&gt;</programlisting>
<para>
Vous pouvez externaliser les informations de mapping des résultats dans un
élément <literal>&lt;resultset&gt;</literal> pour soit les réutiliser
dans différentes requêtes nommées, soit à travers l'API
<literal>setResultSetMapping()</literal>.
</para>
<programlisting>&lt;resultset name="personAddress"&gt;
&lt;return alias="person" class="eg.Person"/&gt;
&lt;return-join alias="address" property="person.mailingAddress"/&gt;
&lt;/resultset&gt;
&lt;sql-query name="personsWith" resultset-ref="personAddress"&gt;
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
&lt;/sql-query&gt;</programlisting>
<sect2 id="propertyresults">
<title>Utilisation de return-property pour spécifier explicitement les noms des colonnes/alias</title>
<para>
Avec <literal>&lt;return-property&gt;</literal> vous pouvez explicitement dire
à Hibernate quels alias de colonne utiliser, plutot que d'employer la syntaxe
<literal>{}</literal> pour laisser Hibernate injecter ses propres alias.
</para>
<programlisting>&lt;sql-query name="mySqlQuery"&gt;
&lt;return alias="person" class="eg.Person"&gt;
&lt;return-property name="name" column="myName"/&gt;
&lt;return-property name="age" column="myAge"/&gt;
&lt;return-property name="sex" column="mySex"/&gt;
&lt;/return&gt;
SELECT person.NAME AS myName,
person.AGE AS myAge,
person.SEX AS mySex,
FROM PERSON person WHERE person.NAME LIKE :name
&lt;/sql-query&gt;
</programlisting>
<para>
<literal>&lt;return-property&gt;</literal> fonctionne aussi avec de
multiple colonnes. Cela résout une limitation de la syntaxe <literal>{}</literal>
qui ne peut pas permettre une bonne granularité des propriétés multi-colonnes.
</para>
<programlisting>&lt;sql-query name="organizationCurrentEmployments"&gt;
&lt;return alias="emp" class="Employment"&gt;
&lt;return-property name="salary"&gt;
&lt;return-column name="VALUE"/&gt;
&lt;return-column name="CURRENCY"/&gt;
&lt;/return-property&gt;
&lt;return-property name="endDate" column="myEndDate"/&gt;
&lt;/return&gt;
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
&lt;/sql-query&gt;</programlisting>
<para>
Notez que dans cet exemple nous avons utilisé <literal>&lt;return-property&gt;</literal>
en combinaison avec la syntaxe <literal>{}</literal> pour l'injection. Cela autorise les
utilisateurs à choisir comment ils veulent référencer les colonnes et les propriétés.
</para>
<para>
Si votre mapping a un discriminant vous devez utiliser
<literal>&lt;return-discriminator&gt;</literal> pour spécifier la colonne
discriminante.
</para>
</sect2>
<sect2 id="sp_query" revision="1">
<title>Utilisation de procédures stockées pour les requêtes</title>
<para>
Hibernate 3 introduit le support des requêtes via procédures stockées et les fonctions.
La documentation suivante est valable pour les deux.
Les procédures stockées/fonctions doivent retourner l'ensemble de résultats en tant que
premier paramètre sortant (NdT: "out-parameter") pour être capable de fonctionner
avec Hibernate. Un exemple d'une telle procédure stockée en Oracle 9 et
version supérieure :
</para>
<programlisting>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>Pour utiliser cette requête dans Hibernate vous avez besoin de la mapper via une requête nommée.</para>
<programlisting>&lt;sql-query name="selectAllEmployees_SP" callable="true"&gt;
&lt;return alias="emp" class="Employment"&gt;
&lt;return-property name="employee" column="EMPLOYEE"/&gt;
&lt;return-property name="employer" column="EMPLOYER"/&gt;
&lt;return-property name="startDate" column="STARTDATE"/&gt;
&lt;return-property name="endDate" column="ENDDATE"/&gt;
&lt;return-property name="regionCode" column="REGIONCODE"/&gt;
&lt;return-property name="id" column="EID"/&gt;
&lt;return-property name="salary"&gt;
&lt;return-column name="VALUE"/&gt;
&lt;return-column name="CURRENCY"/&gt;
&lt;/return-property&gt;
&lt;/return&gt;
{ ? = call selectAllEmployments() }
&lt;/sql-query&gt;</programlisting>
<para>
Notez que les procédures stockées retournent, pour le moment, seulement des
scalaires et des entités. <literal>&lt;return-join&gt;</literal> et
<literal>&lt;load-collection&gt;</literal> ne sont pas supportés.
</para>
<sect3 id="querysql-limits-storedprocedures" revision="1">
<title>Règles/limitations lors de l'utilisation des procédures stockées</title>
<para>
Pur utiliser des procédures stockées avec Hibernate, les procédures doivent
suivre certaines règles. Si elles ne suivent pas ces règles, elles ne sont pas
utilisables avec Hibernate. Si vous voulez encore utiliser ces procédures vous
devez les exécuter via <literal>session.connection()</literal>. Les règles
sont différentes pour chaque base de données, puisque les vendeurs de base
de données ont des sémantiques/syntaxes différentes pour les procédures stockées.
</para>
<para>Les requêtes de procédures stockées ne peuvent pas être paginées avec
<literal>setFirstResult()/setMaxResults()</literal>.</para>
<para>Pour Oracle les règles suivantes s'appliquent :</para>
<itemizedlist spacing="compact">
<listitem>
<para>
La procédure doit retourner un ensemble de résultats. Le
prmeier paramètre d'une procédure doit être un <literal>OUT</literal>
qui retourne un ensemble de résultats. Ceci est fait en
retournant un <literal>SYS_REFCURSOR</literal> dans Oracle 9 ou 10. Dans
Oracle vous avez besoin de définir un type <literal>REF CURSOR</literal>.</para>
</listitem>
</itemizedlist>
<para>Pour Sybase ou MS SQL server les règles suivantes s'appliquent :</para>
<itemizedlist spacing="compact">
<listitem>
<para>La procédure doit retourner un ensemble de résultats. Notez que comme
ces serveurs peuvent retourner de multiples ensembles de résultats et mettre à jour
des compteurs, Hibernate itérera les résultats et prendra le premier résultat qui est
un ensemble de résultat comme valeur de retour. Tout le reste sera ignoré.</para>
</listitem>
<listitem>
<para>Si vous pouvez activer <literal>SET NOCOUNT ON</literal> dans votre procédure,
elle sera probablement plus efficace, mais ce n'est pas une obligation.</para>
</listitem>
</itemizedlist>
</sect3>
</sect2>
</sect1>
<sect1 id="querysql-cud">
<title>SQL personnalisé pour créer, mettre à jour et effacer</title>
<para>
Hibernate3 peut utiliser des expression SQL personnalisées pour des opérations de création,
de mise à jour, et de suppression. Les objets persistants les classes et les collections
dans Hibernate contiennent déjà un ensemble de chaînes de caractères générées lors de la
configuration (insertsql, deletesql, updatesql, etc). Les tages de mapping
<literal>&lt;sql-insert&gt;</literal>,
<literal>&lt;sql-delete&gt;</literal>, et
<literal>&lt;sql-update&gt;</literal> surchargent ces chaînes de caractères :</para>
<programlisting>&lt;class name="Person"&gt;
&lt;id name="id"&gt;
&lt;generator class="increment"/&gt;
&lt;/id&gt;
&lt;property name="name" not-null="true"/&gt;
&lt;sql-insert&gt;INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )&lt;/sql-insert&gt;
&lt;sql-update&gt;UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?&lt;/sql-update&gt;
&lt;sql-delete&gt;DELETE FROM PERSON WHERE ID=?&lt;/sql-delete&gt;
&lt;/class&gt;</programlisting>
<para>Le SQL est directement exécuté dans votre base de données, donc vous êtes libre d'utiliser
le dialecte que vous souhaitez. Cela réduira bien sûr la portabilité de votre mapping si vous
utilisez du SQL spécifique à votre base de données.</para>
<para>Les procédures stockées sont supportées si l'attribut <literal>callable</literal> est paramétré :</para>
<programlisting>&lt;class name="Person"&gt;
&lt;id name="id"&gt;
&lt;generator class="increment"/&gt;
&lt;/id&gt;
&lt;property name="name" not-null="true"/&gt;
&lt;sql-insert callable="true"&gt;{call createPerson (?, ?)}&lt;/sql-insert&gt;
&lt;sql-delete callable="true"&gt;{? = call deletePerson (?)}&lt;/sql-delete&gt;
&lt;sql-update callable="true"&gt;{? = call updatePerson (?, ?)}&lt;/sql-update&gt;
&lt;/class&gt;</programlisting>
<para>L'ordre des paramètres positionnels est actuellement vital, car ils doivent être dans la
même séquence qu'Hibernate les attend.</para>
<para>
Vous pouvez voir l'ordre attendu en activant les journaux de debug pour le
niveau <literal>org.hibernate.persister.entity</literal> level. Avec ce niveau activé,
Hibernate imprimera le SQL statique qui est utilisé pour créer, mettre à jour,
supprimer, etc. des entités. (Pour voir la séquence attendue, rappelez-vous de ne pas
inclure votre SQL personnalisé dans les fichiers de mapping de manière à surcharger le
SQL statique généré par Hibernate.)</para>
<para>Les procédures stockées sont dans la plupart des cas (lire : il vaut mieux le faire)
requises pour retourner le nombre de lignes insérées/mises à jour/supprimées, puisque
Hibernate fait quelques vérifications de succès lors de l'exécution de l'expression.
Hibernate inscrit toujours la première expression comme un paramètre de sortie numérique pour les
opérations CUD :</para>
<programlisting>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 personnalisé pour le chargement</title>
<para>Vous pouvez aussi déclarer vos propres requêtes SQL (ou HQL) pour le chargement d'entité :</para>
<programlisting>&lt;sql-query name="person"&gt;
&lt;return alias="pers" class="Person" lock-mode="upgrade"/&gt;
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
&lt;/sql-query&gt;</programlisting>
<para>Ceci est juste une déclaration de requête nommée, comme vu plus tôt. Vous pouvez référencer
cette requête nommée dans un mapping de classe :</para>
<programlisting>&lt;class name="Person"&gt;
&lt;id name="id"&gt;
&lt;generator class="increment"/&gt;
&lt;/id&gt;
&lt;property name="name" not-null="true"/&gt;
&lt;loader query-ref="person"/&gt;
&lt;/class&gt;</programlisting>
<para>Ceci fonctionne même avec des procédures stockées.</para>
<para>Vous pouvez même définir une requête pour le chargement d'une collection :</para>
<programlisting>&lt;set name="employments" inverse="true"&gt;
&lt;key/&gt;
&lt;one-to-many class="Employment"/&gt;
&lt;loader query-ref="employments"/&gt;
&lt;/set&gt;</programlisting>
<programlisting>&lt;sql-query name="employments"&gt;
&lt;load-collection alias="emp" role="Person.employments"/&gt;
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
&lt;/sql-query&gt;</programlisting>
<para>Vous pourriez même définir un chargeur d'entité qui charge une collection par jointure :</para>
<programlisting>&lt;sql-query name="person"&gt;
&lt;return alias="pers" class="Person"/&gt;
&lt;return-join alias="emp" property="pers.employments"/&gt;
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
&lt;/sql-query&gt;</programlisting>
</sect1>
</chapter>