Hibernate.orgCommunity Documentation

Chapitre 17. SQL natif

17.1. Utiliser une requête SQLQuery
17.1.1. Requêtes scalaires
17.1.2. Requêtes d'entités
17.1.3. Gérer les associations et collections
17.1.4. Retour d'entités multiples
17.1.5. Retour d'entités non gérées
17.1.6. Gérer l'héritage
17.1.7. Paramètres
17.2. Requêtes SQL nommées
17.2.1. Utilisation de return-property pour spécifier explicitement les noms des colonnes/alias
17.2.2. Utilisation de procédures stockées pour les requêtes
17.3. SQL personnalisé pour créer, mettre à jour et effacer
17.4. SQL personnalisé pour le chargement

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é CONNECT d'Oracle. Cette fonctionnalité offre par ailleurs un moyen de migration plus propre et doux d'une application basée directement sur SQL/JDBC vers Hibernate.

Hibernate3 vous permet de spécifier du SQL écrit à la main (y compris les procédures stockées) pour toutes les opérations de création, mise à jour, suppression et chargement.

L'exécution des requêtes en SQL natif est contrôlée par l'interface SQLQuery, qui est obtenue en appelant Session.createSQLQuery(). Ce qui suit décrit comment utiliser cette API pour les requêtes.

La requête SQL la plus basique permet de récupérer une liste de (valeurs) scalaires.

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

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

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 final et le type des valeurs scalaires retournées.

Pour éviter l'overhead lié à ResultSetMetadata ou simplement pour être plus explicite dans ce qui est retourné, vous pouvez utiliser addScalar().

sess.createSQLQuery("SELECT * FROM CATS")

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

Cette requête spécifie :

Cela retournera toujours un tableau d'objets, mais sans utiliser le ResultSetMetdata. Il récupérera à la place explicitement les colonnes ID, NAME et BIRTHDATE comme étant respectivement de type Long, String et Short, depuis l'ensemble de résultats sous-jacent. Cela signifie aussi que seules ces trois colonnes seront retournées même si la requête utilise * et pourait retourner plus que les trois colonnes listées.

Il est possible de ne pas définir l'information sur le type pour toutes ou une partie des scalaires.

sess.createSQLQuery("SELECT * FROM CATS")

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

Il s'agit essentiellement de la même requête que précédemment, mais le ResultSetMetaData est utilisé pour décider des types de NAME et BIRTHDATE alors que le type de ID est explicitement spécifié.

Les java.sql.Types retournés par le ResultSetMetaData sont mappés aux types Hibernate via le Dialect. Si un type spécifique n'est pas mappé ou est mappé à un type non souhaité, il est possible de le personnaliser en invoquant registerHibernateType dans le Dialect.

Jusqu'à présent, les colonnes de l'ensemble de résultats sont supposées être les mêmes que les noms de colonnes spécifiés dans les documents 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.

L'injection d'alias de colonne est requise pour la requête suivante (qui risque de ne pas fonctionner) :

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

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

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 noms 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" et "NAME").

La forme suivante n'est pas vulnérable à la duplication des noms de colonnes :

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

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

Cette requête spécifie :

Les notations {cat.*} et {mother.*} utilisées ci-dessus sont un équivalent à 'toutes les propriétés'. Alternativement, vous pouvez lister les colonnes explicitement, mais même dans ce cas, nous laissons Hibernate injecter les alias de colonne pour chaque propriété. Le paramètre fictif 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 Cats et leur mère depuis une table différente (cat_log) de celle déclarée dans les mappages. Notez que nous pouvons aussi utiliser les alias de propriété dans la clause where si désiré.

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()

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'une requête HQL nommée. Dans ce cas, nous n'avons pas besoin d'appeler 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();

Les éléments <return-join> et <load-collection> sont respectivement utilisés pour lier des associations et définir des requêtes qui initialisent des collections,


<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
>

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 <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
>

Vous pouvez externaliser les informations de mapping des résultats dans un élément <resultset> pour soit les réutiliser dans différentes requêtes nommées, soit à travers l'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
>

Vous pouvez également utiliser les informations de mapping de l'ensemble de résultats dans vos fichiers hbm directement dans le code java.

List cats = sess.createSQLQuery(

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

Avec <return-property> vous pouvez explicitement dire à Hibernate quels alias de colonne utiliser, plutôt que d'employer la syntaxe {} pour laisser Hibernate injecter ses propres alias. Par exemple :


<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> fonctionne aussi avec de multiples colonnes. Cela résout une limitation de la syntaxe {} qui ne permet pas une fine granularité des propriétés multi-colonnes.


<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
>

Notez que dans cet exemple nous avons utilisé <return-property> en combinaison avec la syntaxe {} pour l'injection. Cela autorise les utilisateurs à choisir comment ils veulent référencer les colonnes et les propriétés.

Si votre mapping a un discriminant vous devez utiliser <return-discriminator> pour spécifier la colonne discriminante.

Hibernate 3 introduit le support des requêtes via les procédures stockées et les fonctions. La documentation suivante est valable pour les deux. Les procédures stockées/fonctions doivent retourner un ensemble de résultats en tant que premier paramètre sortant (out-parameter") pour être capable de fonctionner avec Hibernate. Voici un exemple d'une telle procédure stockée en Oracle 9 et version supérieure :


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;

Pour utiliser cette requête dans Hibernate vous avez besoin de la mapper via une requête nommée.


<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
>

Notez que les procédures stockées ne retournent, pour le moment, que des scalaires et des entités. <return-join> et <load-collection> ne sont pas supportés.

Pour 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 néanmoins, vous désirez utiliser ces procédures vous devez les exécuter via session.connection(). 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.

Les requêtes de procédures stockées ne peuvent pas être paginées avec setFirstResult()/setMaxResults().

La forme d'appel recommandée est le SQL92 standard : { ? = call functionName(<parameters>) } or { ? = call procedureName(<parameters>}. La syntaxe d'appel native n'est pas supportée.

Pour Oracle les règles suivantes sont applicables :

Pour Sybase ou MS SQL server les règles suivantes sont applicables :

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 Section 5.7, « Column read and write expressions ».

The class and collection persisters in Hibernate already contain a set of configuration time generated strings (insertsql, deletesql, updatesql etc.). The mapping tags <sql-insert>, <sql-delete>, and <sql-update> override these strings:


<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
>

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 mappage si vous utilisez du SQL spécifique à votre base de données.

Les procédures stockées sont supportées si l'attribut callable est paramétré :


<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
>

L'ordre des paramètres de position est essentiel, car ils doivent être dans la séquence attendue par Hibernate.

Vous pouvez voir l'ordre attendu en activant la journalisation de débogage pour le niveau org.hibernate.persister.entity. 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 mappage car cela surchargera le SQL statique généré par Hibernate.

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 vérifie plusieurs fois le succès de l'expression. Hibernate inscrit toujours le premier paramètre de l'expression comme un paramètre de sortie numérique pour les opérations 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 Section 5.7, « Column read and write expressions » 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
>

Ceci est juste une déclaration de requête nommée, comme vu précédemment. Vous pouvez référencer cette requête nommée dans un mappage de classe :


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

Ceci fonctionne même avec des procédures stockées.

Vous pouvez même définir une requête pour le chargement d'une collection :


<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
>

Vous pourriez même définir un chargeur d'entité qui charge une collection par jointure :


<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
>