751 lines
31 KiB
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><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>List people = sess.getNamedQuery("persons")
|
|
.setString("namePattern", namePattern)
|
|
.setMaxResults(50)
|
|
.list();</programlisting>
|
|
|
|
<para>
|
|
Les éléments <literal><return-join></literal> et
|
|
<literal><load-collection></literal> sont respectivement utilisés pour lier
|
|
des associations et définir des requêtes qui initialisent des collections.
|
|
</para>
|
|
|
|
<programlisting><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>
|
|
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><return-scalar></literal> :</para>
|
|
|
|
<programlisting><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>
|
|
|
|
<para>
|
|
Vous pouvez externaliser les informations de mapping des résultats dans un
|
|
élément <literal><resultset></literal> pour soit les réutiliser
|
|
dans différentes requêtes nommées, soit à travers l'API
|
|
<literal>setResultSetMapping()</literal>.
|
|
</para>
|
|
|
|
<programlisting><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},
|
|
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>
|
|
|
|
<sect2 id="propertyresults">
|
|
<title>Utilisation de return-property pour spécifier explicitement les noms des colonnes/alias</title>
|
|
|
|
<para>
|
|
Avec <literal><return-property></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><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> 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><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>
|
|
Notez que dans cet exemple nous avons utilisé <literal><return-property></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><return-discriminator></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><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>
|
|
Notez que les procédures stockées retournent, pour le moment, seulement des
|
|
scalaires et des entités. <literal><return-join></literal> et
|
|
<literal><load-collection></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><sql-insert></literal>,
|
|
<literal><sql-delete></literal>, et
|
|
<literal><sql-update></literal> surchargent ces chaînes de caractères :</para>
|
|
|
|
<programlisting><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>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><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>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><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>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><class name="Person">
|
|
<id name="id">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="name" not-null="true"/>
|
|
<loader query-ref="person"/>
|
|
</class></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><set name="employments" inverse="true">
|
|
<key/>
|
|
<one-to-many class="Employment"/>
|
|
<loader query-ref="employments"/>
|
|
</set></programlisting>
|
|
|
|
<programlisting><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>Vous pourriez même définir un chargeur d'entité qui charge une collection par jointure :</para>
|
|
|
|
<programlisting><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> |