Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 18. SQL natif

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.

## 18.1. Utiliser une requête `SQLQuery`

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.

### 18.1.1. Requêtes scalaires

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 :

- la chaîne de requêtes SQL

- les colonnes et les types retournés

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.

### 18.1.2. Requêtes d'entités

Les requêtes précédentes ne retournaient que des valeurs scalaires, en ne retournant que les valeurs brutes de l'ensemble de résultats. Ce qui suit montre comment récupérer des entités depuis une requête native SQL, grâce à `addEntity()`.

    sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);

    sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);

Cette requête spécifie :

- la chaîne de requêtes SQL

- L'entité retournée par la requête

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.

Si l'entité est mappée avec un `many-to-one` 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 `many-to-one` vers `Dog`:

    sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);

Ceci permet à cat.getDog() de fonctionner normalement.

### 18.1.3. Gérer les associations et collections

Il est possible de charger agressivement `Dog` pour éviter le chargement de proxies c'est-à-dire un aller-retour supplémentaire vers la base de données. Ceci est effectué via la méthode `addJoin()`, qui vous permet de joindre une association ou collection.

    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");

Dans cet exemple, les `Cat` retournés auront leur propriété `dog` entièrement initialisée 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 le chemin de la propriété cible de la jointure. Il est possible de faire la même jointure agressive pour les collections, par ex. si le `Cat` a un un-à-plusieurs vers `Dog`.

    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");

À ce stade, nous arrivons aux limites de ce qui est possible avec les requêtes natives sans modifier les requêtes SQL 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.

### 18.1.4. Retour d'entités multiples

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 = m.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.*}, {m.*}  FROM CATS c, CATS m WHERE c.MOTHER_ID = m.ID")

     .addEntity("cat", Cat.class)

     .addEntity("mother", Cat.class)

Cette requête spécifie :

- la requête SQL, avec des réceptacles pour que Hibernate injecte les alias de colonnes

- les entités retournées par la requête

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

#### 18.1.4.1. Références d'alias et de propriété

Pour la plupart des cas précédents, l'injection d'alias est requise, mais pour les requêtes relatives à des mappings plus complexes, comme les propriétés composites, les discriminants d'héritage, les collections etc., il y a des alias spécifiques à utiliser pour permettre à Hibernate l'injection des alias appropriés.

Le tableau suivant montre les diverses possibilité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.

Tableau 18.1. Nom d'injection d'alias

Description | Syntaxe | Exemple
--- | --- | ---
Une propriété simple | `{[aliasname].[propertyname]` | `A_NAME as {item.name}`
Une propriété composite | `{[aliasname].[componentname].[propertyname]}` | `CURRENCY as {item.amount.currency}, VALUE as {item.amount.value}`
Discriminateur d'une entité | `{[aliasname].class}` | `DISC as {item.class}`
Toutes les propriétés d'une entité | `{[aliasname].*}` | `{item.*}`
La clé d'une collection | `{[aliasname].key}` | `ORGID as {coll.key}`
L'id d'une collection | `{[aliasname].id}` | `EMPID as {coll.id}`
L'élément d'une collection | `{[aliasname].element}` | `XID as {coll.element}`
Propriété de l'élément dans une collection | `{[aliasname].element.[propertyname]}` | `NAME as {coll.element.name}`
Toutes les propriétés d'un élément dans la collection | `{[aliasname].element.*}` | `{coll.element.*}`
All properties of the collection | `{[aliasname].*}` | `{coll.*}`

### 18.1.5. Retour d'entités non gérées

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.

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

            .setResultTransformer(Transformers.aliasToBean(CatDTO.class))

Cette requête spécifie :

- la chaîne de requêtes SQL

- un transformateur de résultat

La requête précédente retournera la liste de `CatDTO` qui ont été instanciés et dans lesquels les valeurs de NAME et BIRTHNAME auront été injectées dans leurs propriétés ou champs correspondants.

### 18.1.6. Gérer l'héritage

Les requêtes natives SQL qui interrogent des entités mappées en tant que part d'un héritage doivent inclure toutes les propriétés de la classe de base et de toutes ses sous classes.

### 18.1.7. Paramètres

Les requêtes natives SQL supportent aussi bien les paramètres de position que les paramètres nommés :

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

## 18.2. Requêtes SQL nommées

Named SQL queries can also be defined in the mapping document and called in exactly the same way as a named HQL query (see Section 11.4.1.7, « Externaliser des requêtes nommées »). In this case, you do not need to call `addEntity()`.

Exemple 18.1. Named sql query using the <sql-query> maping element

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

Exemple 18.2. Execution of a named 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,

Exemple 18.3. Named sql query with association

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

Exemple 18.4. Named query returning a 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()`.

Exemple 18.5. <resultset> mapping used to externalize mapping information

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

Exemple 18.6. Programmatically specifying the result mapping information

    List cats = sess.createSQLQuery(

            "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"

        )

        .setResultSetMapping("catAndKitten")

        .list();

So far we have only looked at externalizing SQL queries using Hibernate mapping files. The same concept is also available with anntations and is called named native queries. You can use `@NamedNativeQuery` (`@NamedNativeQueries`) in conjunction with `@SqlResultSetMapping` (`@SqlResultSetMappings`). Like `@NamedQuery`, `@NamedNativeQuery` and `@SqlResultSetMapping` can be defined at class level, but their scope is global to the application. Lets look at a view examples.

Exemple 18.7, « Named SQL query using @NamedNativeQuery together with @SqlResultSetMapping » shows how a `resultSetMapping` parameter is defined in `@NamedNativeQuery`. It represents the name of a defined `@SqlResultSetMapping`. The resultset mapping declares the entities retrieved by this native query. Each field of the entity is bound to an SQL alias (or column name). All fields of the entity including the ones of subclasses and the foreign key columns of related entities have to be present in the SQL query. Field definitions are optional provided that they map to the same column name as the one declared on the class property. In the example 2 entities, `Night` and `Area`, are returned and each property is declared and associated to a column name, actually the column name retrieved by the query.

In Exemple 18.8, « Implicit result set mapping » the result set mapping is implicit. We only describe the entity class of the result set mapping. The property / column mappings is done using the entity mapping values. In this case the model property is bound to the model_txt column.

Finally, if the association to a related entity involve a composite primary key, a `@FieldResult` element should be used for each foreign key column. The `@FieldResult` name is composed of the property name for the relationship, followed by a dot ("."), followed by the name or the field or property of the primary key. This can be seen in Exemple 18.9, « Using dot notation in @FieldResult for specifying associations ».

Exemple 18.7. Named SQL query using `@NamedNativeQuery` together with `@SqlResultSetMapping`

    @NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "

        + " night.night_date, area.id aid, night.area_id, area.name "

        + "from Night night, Area area where night.area_id = area.id",

                      resultSetMapping="joinMapping")

    @SqlResultSetMapping(name="joinMapping", entities={

        @EntityResult(entityClass=Night.class, fields = {

            @FieldResult(name="id", column="nid"),

            @FieldResult(name="duration", column="night_duration"),

            @FieldResult(name="date", column="night_date"),

            @FieldResult(name="area", column="area_id"),

            discriminatorColumn="disc"

        }),

        @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {

            @FieldResult(name="id", column="aid"),

            @FieldResult(name="name", column="name")

        })

        }

    )

Exemple 18.8. Implicit result set mapping

    @Entity

    @SqlResultSetMapping(name="implicit",

                         entities=@EntityResult(entityClass=SpaceShip.class))

    @NamedNativeQuery(name="implicitSample",

                      query="select * from SpaceShip",

                      resultSetMapping="implicit")

    public class SpaceShip {

        private String name;

        private String model;

        private double speed;

        @Id

        public String getName() {

            return name;

        }

        public void setName(String name) {

            this.name = name;

        }

        @Column(name="model_txt")

        public String getModel() {

            return model;

        }

        public void setModel(String model) {

            this.model = model;

        }

        public double getSpeed() {

            return speed;

        }

        public void setSpeed(double speed) {

            this.speed = speed;

        }

    }

Exemple 18.9. Using dot notation in @FieldResult for specifying associations

    @Entity

    @SqlResultSetMapping(name="compositekey",

            entities=@EntityResult(entityClass=SpaceShip.class,

                fields = {

                        @FieldResult(name="name", column = "name"),

                        @FieldResult(name="model", column = "model"),

                        @FieldResult(name="speed", column = "speed"),

                        @FieldResult(name="captain.firstname", column = "firstn"),

                        @FieldResult(name="captain.lastname", column = "lastn"),

                        @FieldResult(name="dimensions.length", column = "length"),

                        @FieldResult(name="dimensions.width", column = "width")

                        }),

            columns = { @ColumnResult(name = "surface"),

                        @ColumnResult(name = "volume") } )

    @NamedNativeQuery(name="compositekey",

        query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip",

        resultSetMapping="compositekey")

    } )

    public class SpaceShip {

        private String name;

        private String model;

        private double speed;

        private Captain captain;

        private Dimensions dimensions;

        @Id

        public String getName() {

            return name;

        }

        public void setName(String name) {

            this.name = name;

        }

        @ManyToOne(fetch= FetchType.LAZY)

        @JoinColumns( {

                @JoinColumn(name="fname", referencedColumnName = "firstname"),

                @JoinColumn(name="lname", referencedColumnName = "lastname")

                } )

        public Captain getCaptain() {

            return captain;

        }

        public void setCaptain(Captain captain) {

            this.captain = captain;

        }

        public String getModel() {

            return model;

        }

        public void setModel(String model) {

            this.model = model;

        }

        public double getSpeed() {

            return speed;

        }

        public void setSpeed(double speed) {

            this.speed = speed;

        }

        public Dimensions getDimensions() {

            return dimensions;

        }

        public void setDimensions(Dimensions dimensions) {

            this.dimensions = dimensions;

        }

    }

    @Entity

    @IdClass(Identity.class)

    public class Captain implements Serializable {

        private String firstname;

        private String lastname;

        @Id

        public String getFirstname() {

            return firstname;

        }

        public void setFirstname(String firstname) {

            this.firstname = firstname;

        }

        @Id

        public String getLastname() {

            return lastname;

        }

        public void setLastname(String lastname) {

            this.lastname = lastname;

        }

    }

## Astuce

If you retrieve a single entity using the default mapping, you can specify the `resultClass` attribute instead of `resultSetMapping`:

    @NamedNativeQuery(name="implicitSample", query="select * from SpaceShip", resultClass=SpaceShip.class)

    public class SpaceShip {

In some of your native queries, you'll have to return scalar values, for example when building report queries. You can map them in the `@SqlResultsetMapping` through `@ColumnResult`. You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though).

Exemple 18.10. Scalar values via `@ColumnResult`

    @SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))

    @NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar")

An other query hint specific to native queries has been introduced: `org.hibernate.callable` which can be true or false depending on whether the query is a stored procedure or not.

### 18.2.1. Utilisation de return-property pour spécifier explicitement les noms des colonnes/alias

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.

### 18.2.2. Utilisation de procédures stockées pour les requêtes

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.

#### 18.2.2.1. Règles/limitations lors de l'utilisation des procédures stockées

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 :

- La procédure doit retourner un ensemble de résultats. Le premier paramètre d'une procédure doit être un `OUT` qui retourne un ensemble de résultats. Ceci est effectué en retournant un `SYS_REFCURSOR` dans Oracle 9 ou 10. Dans Oracle vous avez besoin de définir un type `REF CURSOR`, consultez la documentation Oracle.

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

- 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ésultats comme valeur de retour. Tout le reste sera ignoré.

- Si vous pouvez activer `SET NOCOUNT ON` dans votre procédure, elle sera probablement plus efficace, mais ce n'est pas une obligation.

## 18.3. SQL personnalisé pour créer, mettre à jour et effacer

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.6, « Column transformers: read and write expressions ». Exemple 18.11, « Custom CRUD via annotations » shows how to define custom SQL operatons using annotations.

Exemple 18.11. Custom CRUD via annotations

    @Entity

    @Table(name="CHAOS")

    @SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)")

    @SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?")

    @SQLDelete( sql="DELETE CHAOS WHERE id = ?")

    @SQLDeleteAll( sql="DELETE CHAOS")

    @Loader(namedQuery = "chaos")

    @NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where id= ?", resultClass = Chaos.class)

    public class Chaos {

        @Id

        private Long id;

        private Long size;

        private String name;

        private String nickname;

`@SQLInsert`, `@SQLUpdate`, `@SQLDelete`, `@SQLDeleteAll` respectively override the INSERT, UPDATE, DELETE, and DELETE all statement. The same can be achieved using Hibernate mapping files and the `<sql-insert>`, `<sql-update>` and `<sql-delete>` nodes. This can be seen in Exemple 18.12, « Custom CRUD XML ».

Exemple 18.12. Custom CRUD XML

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

If you expect to call a store procedure, be sure to set the `callable` attribute to `true`. In annotations as well as in xml.

To check that the execution happens correctly, Hibernate allows you to define one of those three strategies:

- none: no check is performed: the store procedure is expected to fail upon issues

- count: use of rowcount to check that the update is successful

- param: like COUNT but using an output parameter rather that the standard mechanism

To define the result check style, use the `check` parameter which is again available in annoations as well as in xml.

You can use the exact same set of annotations respectively xml nodes to override the collection related statements -see Exemple 18.13, « Overriding SQL statements for collections using annotations ».

Exemple 18.13. Overriding SQL statements for collections using annotations

    @OneToMany

    @JoinColumn(name="chaos_fk")

    @SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")

    @SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")

    private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();

## Astuce

The parameter order is important and is defined by the order Hibernate handles properties. You can see the expected order by enabling debug logging for the `org.hibernate.persister.entity` level. With this level enabled Hibernate will print out the static SQL that is used to create, update, delete etc. entities. (To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static sql)

Overriding SQL statements for secondary tables is also possible using `@org.hibernate.annotations.Table` and either (or all) attributes `sqlInsert`, `sqlUpdate`, `sqlDelete`:

Exemple 18.14. Overriding SQL statements for secondary tables

    @Entity

    @SecondaryTables({

        @SecondaryTable(name = "`Cat nbr1`"),

        @SecondaryTable(name = "Cat2"})

    @org.hibernate.annotations.Tables( {

        @Table(appliesTo = "Cat", comment = "My cat table" ),

        @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT,

            sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") )

    } )

    public class Cat implements Serializable {

The previous example also shows that you can give a comment to a given table (primary or secondary): This comment will be used for DDL generation.

## Astuce

The SQL is directly executed in your database, so you can use any dialect you like. This will, however, reduce the portability of your mapping if you use database specific SQL.

Last but not least, stored procedures are in most cases required to return the number of rows inserted, updated and deleted. Hibernate always registers the first statement parameter as a numeric output parameter for the CUD operations:

Exemple 18.15. Stored procedures and their return value

    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;

## 18.4. SQL personnalisé pour le chargement

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.6, « Column transformers: 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>

The annotation equivalent `<loader>` is the @Loader annotation as seen in Exemple 18.11, « Custom CRUD via annotations ».

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 17. Requêtes par critères
- Niveau supérieur
- Sommaire
- SuivantChapitre 19. Filtrer les données