Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 11. Travailler avec des objets

Hibernate est une solution de mappage objet/relationnel complète qui ne masque pas seulement au développeur les détails du système de gestion de base de données sous-jacent, mais offre aussi la gestion d'état des objets. C'est, contrairement à la gestion de `statements` SQL dans les couches de persistance habituelles JDBC/SQL, une vue orientée objet très naturelle de la persistance dans les applications Java.

En d'autres termes, les développeurs d'applications Hibernate devraient toujours réfléchir à l'état de leurs objets, et pas nécessairement à l'exécution des expressions SQL. Cette part est prise en charge par Hibernate et importante seulement aux développeurs d'applications lors du réglage de la performance de leur système.

## 11.1. États des objets Hibernate

Hibernate définit et prend en charge les états d'objets suivants :

- Éphémère (transient) - un objet est éphémère s'il a juste été instancié en utilisant l'opérateur `new`. Il n'a aucune représentation persistante dans la base de données et aucune valeur d'identifiant n'a été assignée. Les instances éphémères seront détruites par le ramasse-miettes si l'application n'en conserve aucune référence. Utilisez la `Session` d'Hibernate pour rendre un objet persistant (et laisser Hibernate s'occuper des expressions SQL qui ont besoin d'être exécutées pour cette transistion).

- Persistant - une instance persistante a une représentation dans la base de données et une valeur d'identifiant. Elle pourrait avoir juste été sauvegardée ou chargée, pourtant, elle est par définition dans la portée d'une `Session`. Hibernate détectera tout changement effectué sur un objet dans l'état persistant et synchronisera l'état avec la base de données lors de la fin de l'unité de travail. Les développeurs n'exécutent pas d'expressions `UPDATE` ou `DELETE` manuelles lorsqu'un objet devrait être rendu éphémère.

- Détaché - une instance détachée est un objet qui a été persistant, mais dont la `Session` a été fermée. La référence à l'objet est encore valide, bien sûr, et l'instance détachée pourrait même être modifiée dans cet état. Une instance détachée peut être rattachée à une nouvelle `Session` ultérieurement, la rendant (et toutes les modifications avec) de nouveau persistante. Cette fonctionnalité rend possible un modèle de programmation pour de longues unités de travail qui requièrent un temps de réflexion de l'utilisateur. Nous les appelons des conversations, c'est-à-dire une unité de travail du point de vue de l'utilisateur.

Nous allons maintenant approfondir le sujet des états et des transitions d'état (et des méthodes Hibernate qui déclenchent une transition).

## 11.2. Rendre des objets persistants

Newly instantiated instances of a persistent class are considered transient by Hibernate. We can make a transient instance persistent by associating it with a session:

    DomesticCat fritz = new DomesticCat();

    fritz.setColor(Color.GINGER);

    fritz.setSex('M');

    fritz.setName("Fritz");

    Long generatedId = (Long) sess.save(fritz);

Si `Cat` a un identifiant généré, l'identifiant est généré et assigné au `cat` lorsque `save()` est appelé. Si `Cat` a un identifiant `assigned`, ou une clef composée, l'identifiant devrait être assigné à l'instance de `cat` avant d'appeler `save()`. Vous pouvez aussi utiliser `persist()` à la place de `save()`, avec la sémantique définie plus tôt dans la première ébauche d'EJB3.

- `persist()` rend une instance éphémère persistante. Toutefois, il ne garantit pas que la valeur d'identificateur soit affectée à l'instance permanente immédiatement, l'affectation peut se produire au moment de flush. `Persist()` garantit également qu'il ne s'exécutera pas un énoncé `INSERT` s'il est appelée en dehors des limites de transaction. C'est utile pour les longues conversations dans un contexte de session/persistance étendu.

- `save()` garantit le retour d'un identifiant. Si une instruction INSERT doit être exécutée pour obtenir l'identifiant (par exemple, le générateur "identity", et non pas "sequence"), cet INSERT se produit immédiatement, que vous soyez à l'intérieur ou à l'extérieur d'une transaction. C'est problématique dans une conversation longue dans un contexte de session/persistance étendu.

Alternativement, vous pouvez assigner l'identifiant en utilisant une version surchargée de `save()`.

    DomesticCat pk = new DomesticCat();

    pk.setColor(Color.TABBY);

    pk.setSex('F');

    pk.setName("PK");

    pk.setKittens( new HashSet() );

    pk.addKitten(fritz);

    sess.save( pk, new Long(1234) );

Si l'objet que vous rendez persistant a des objets associés (par exemple, la collection `kittens` dans l'exemple précédent), ces objets peuvent être rendus persistants dans n'importe quel ordre désiré, à moins que vous ayez une contrainte `NOT NULL` sur la colonne de la clé étrangère. Il n'y a jamais de risque de violer une contrainte de cl. étrangère. Cependant, vous pourriez violer une contrainte `NOT NULL` si vous appeliez `save()` sur les objets dans le mauvais ordre.

Habituellement, vous ne vous préoccupez pas de ce détail, puisque vous utiliserez très probablement la fonctionnalité de persistance transitive de Hibernate pour sauvegarder les objets associés automatiquement. Alors, même les violations de contrainte `NOT NULL` n'ont plus lieu - Hibernate prendra soin de tout. La persistance transitive est traitée plus loin dans ce chapitre.

## 11.3. Chargement d'un objet

Les méthodes `load()` de `Session` vous donnent un moyen de récupérer une instance persistante si vous connaissez déjà son identifiant. `load()` prend un objet de classe et chargera l'état dans une instance nouvellement instanciée de cette classe, dans un état persistant.

    Cat fritz = (Cat) sess.load(Cat.class, generatedId);

    // you need to wrap primitive identifiers

    long id = 1234;

    DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );

Alternativement, vous pouvez charger un état dans une instance donnée :

    Cat cat = new DomesticCat();

    // load pk's state into cat

    sess.load( cat, new Long(pkId) );

    Set kittens = cat.getKittens();

Notez que `load()` lèvera une exception irrécupérable s'il n'y a pas de ligne correspondante dans la base de données. Si la classe est mappée avec un proxy, `load()` retourne juste un proxy non initialisé et n'accède en fait pas à la base de données jusqu'à ce que vous invoquiez une méthode du proxy. Ce comportement est très utile si vous souhaitez créer une association vers un objet sans réellement le charger à partir de la base de données. Cela permet aussi à de multiples instances d'être chargées comme un lot si `batch-size` est défini pour le mapping de la classe.

Si vous n'êtes pas certain qu'une ligne correspondante existe, vous utiliserez la méthode `get()`, laquelle accède à la base de données immédiatement et retourne null s'il n'y a pas de ligne correspondante.

    Cat cat = (Cat) sess.get(Cat.class, id);

    if (cat==null) {

        cat = new Cat();

        sess.save(cat, id);

    }

    return cat;

Vous pouvez même charger un objet en employant un SQL `SELECT ... FOR UPDATE`, en utilisant un `LockMode`. Voir la documentation de l'API pour plus d'informations.

    Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);

Notez que n'importe quelle instance associée ou collection contenue ne sont pas sélectionnées par `FOR UPDATE`, à moins que vous ne décidiez de spécifier `lock` ou `all` en tant que style de cascade pour l'association.

Il est possible de re-charger un objet et toutes ses collections à tout moment, en utilisant la méthode `refresh()`. C'est utile lorsque des "triggers" de base de données sont utilisés pour initialiser certaines propriétés de l'objet.

    sess.save(cat);

    sess.flush(); //force the SQL INSERT

    sess.refresh(cat); //re-read the state (after the trigger executes)

How much does Hibernate load from the database and how many SQL `SELECT`s will it use? This depends on the fetching strategy. This is explained in Section 21.1, « Stratégies de chargement ».

## 11.4. Requêtage

Si vous ne connaissez par les identifiants des objets que vous recherchez, vous avez besoin d'une requête. Hibernate supporte un langage de requêtes orientées objet, facile à utiliser mais puissant. Pour la création de requêtes par programmation, Hibernate supporte une fonction de requêtage sophistiquée Criteria et Example (QBC et QBE). Vous pouvez aussi exprimer votre requête dans le SQL natif de votre base de données, avec un support optionnel de Hibernate pour la conversion des ensembles de résultats en objets.

### 11.4.1. Exécution de requêtes

Les requêtes HQL et SQL natives sont représentées avec une instance de `org.hibernate.Query`. L'interface offre des méthodes pour la liaison des paramètres, la gestion des ensembles de résultats, et pour l'exécution de la requête réelle. Vous obtenez toujours une `Query` en utilisant la `Session` courante :

    List cats = session.createQuery(

        "from Cat as cat where cat.birthdate < ?")

        .setDate(0, date)

        .list();

    List mothers = session.createQuery(

        "select mother from Cat as cat join cat.mother as mother where cat.name = ?")

        .setString(0, name)

        .list();

    List kittens = session.createQuery(

        "from Cat as cat where cat.mother = ?")

        .setEntity(0, pk)

        .list();

    Cat mother = (Cat) session.createQuery(

        "select cat.mother from Cat as cat where cat = ?")

        .setEntity(0, izi)

        .uniqueResult();]]

    Query mothersWithKittens = (Cat) session.createQuery(

        "select mother from Cat as mother left join fetch mother.kittens");

    Set uniqueMothers = new HashSet(mothersWithKittens.list());

Une requête est généralement exécutée en invoquant `list()`, le résultat de la requête sera chargée complètement dans une collection en mémoire. Les instances d'entités récupérées par une requête sont dans un état persistant. La méthode `uniqueResult()` offre un raccourci si vous savez que votre requête retournera un seul objet. Notez que les requêtes qui utilisent le chargement agressif de collections retournent habituellement des copies des objets racine (mais avec leurs collections initialisées). Vous pouvez simplement filtrer ces copies via un `Set`.

#### 11.4.1.1. Itération de résultats

Parfois, vous serez en mesure d'obtenir de meilleures performances en exécutant la requête avec la méthode `iterate()`. En général, ce sera uniquement le cas si vous attendez que les instances réelles d'entité retournées par la requête, soient déjà chargées dans la session ou le cache de second niveau. Si elles ne sont pas déjà cachées, `iterate()` sera plus lent que `list()` et pourrait nécessiter plusieurs accès à la base de données pour une simple requête, généralement 1 pour le select initial qui retourne seulement les identifiants, et n selects supplémentaires pour initialiser les instances réelles.

    // fetch ids

    Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();

    while ( iter.hasNext() ) {

        Qux qux = (Qux) iter.next();  // fetch the object

        // something we couldnt express in the query

        if ( qux.calculateComplicatedAlgorithm() ) {

            // delete the current instance

            iter.remove();

            // dont need to process the rest

            break;

        }

    }

#### 11.4.1.2. Requêtes qui retournent des tuples

Les requêtes d'Hibernate retournent parfois des tuples d'objets, auquel cas chaque tuple est retourné comme un tableau :

    Iterator kittensAndMothers = sess.createQuery(

                "select kitten, mother from Cat kitten join kitten.mother mother")

                .list()

                .iterator();

    while ( kittensAndMothers.hasNext() ) {

        Object[] tuple = (Object[]) kittensAndMothers.next();

        Cat kitten = (Cat) tuple[0];

        Cat mother = (Cat) tuple[1];

        ....

    }

#### 11.4.1.3. Résultats scalaires

Certaines requêtes peuvent spécifier une propriété de classe dans la clause `select`. Elles peuvent même appeler des fonctions d'aggrégat SQL. Les propriétés ou les aggrégats sont considérés comme des résultats "scalaires" (et non des entités dans un état persistant).

    Iterator results = sess.createQuery(

            "select cat.color, min(cat.birthdate), count(cat) from Cat cat " +

            "group by cat.color")

            .list()

            .iterator();

    while ( results.hasNext() ) {

        Object[] row = (Object[]) results.next();

        Color type = (Color) row[0];

        Date oldest = (Date) row[1];

        Integer count = (Integer) row[2];

        .....

    }

#### 11.4.1.4. Lier des paramètres

Des méthodes de `Query` sont fournies pour lier des valeurs à des paramètres nommés ou à des paramètres de style JDBC `?`. Contrairement à JDBC, les numéros des paramètres de Hibernate commencent à zéro. Les paramètres nommés sont des identifiants de la forme `:nom` dans la chaîne de caractères de la requête. Les avantages des paramètres nommés sont :

- les paramètres nommés sont insensibles à l'ordre dans lequel ils apparaissent dans la chaîne de la requête

- ils peuvent apparaître plusieurs fois dans la même requête

- ils sont auto-documentés

    //named parameter (preferred)

    Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");

    q.setString("name", "Fritz");

    Iterator cats = q.iterate();

    //positional parameter

    Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");

    q.setString(0, "Izi");

    Iterator cats = q.iterate();

    //named parameter list

    List names = new ArrayList();

    names.add("Izi");

    names.add("Fritz");

    Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");

    q.setParameterList("namesList", names);

    List cats = q.list();

#### 11.4.1.5. Pagination

Si vous avez besoin de spécifier des liens sur votre ensemble de résultats (le nombre maximum de lignes et/ou la première ligne que vous voulez récupérer) vous utiliserez des méthodes de l'interface `Query` :

    Query q = sess.createQuery("from DomesticCat cat");

    q.setFirstResult(20);

    q.setMaxResults(10);

    List cats = q.list();

Hibernate sait comment traduire cette requête de limite en SQL natif pour votre SGBD.

#### 11.4.1.6. Itération "scrollable"

Si votre connecteur JDBC supporte les `ResultSet` s "scrollables", l'interface `Query` peut être utilisée pour obtenir un objet `ScrollableResults`, qui permettra une navigation flexible dans les résultats de la requête.

    Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +

                                "order by cat.name");

    ScrollableResults cats = q.scroll();

    if ( cats.first() ) {

        // find the first name on each page of an alphabetical list of cats by name

        firstNamesOfPages = new ArrayList();

        do {

            String name = cats.getString(0);

            firstNamesOfPages.add(name);

        }

        while ( cats.scroll(PAGE_SIZE) );

        // Now get the first page of cats

        pageOfCats = new ArrayList();

        cats.beforeFirst();

        int i=0;

        while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );

    }

    cats.close()

Notez qu'une connexion ouverte (et un curseur) est requise pour cette fonctionnalité, utilisez `setMaxResult()`/`setFirstResult()` si vous avez besoin d'une fonctionnalité de pagination hors ligne.

#### 11.4.1.7. Externaliser des requêtes nommées

Queries can also be configured as so called named queries using annotations or Hibernate mapping documents. `@NamedQuery` and `@NamedQueries` can be defined at the class level as seen in Exemple 11.1, « Defining a named query using @NamedQuery » . However their definitions are global to the session factory/entity manager factory scope. A named query is defined by its name and the actual query string.

Exemple 11.1. Defining a named query using `@NamedQuery`

    @Entity

    @NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")

    public class Night {

        ...

    }

    public class MyDao {

        doStuff() {

            Query q = s.getNamedQuery("night.moreRecentThan");

            q.setDate( "date", aMonthAgo );

            List results = q.list();

            ...

        }

        ...

    }

Using a mapping document can be configured using the `<query>` node. Remember to use a `CDATA` section if your query contains characters that could be interpreted as markup.

Exemple 11.2. Defining a named query using `<query>`

    <query name="ByNameAndMaximumWeight"><![CDATA[

        from eg.DomesticCat as cat

            where cat.name = ?

            and cat.weight > ?

    ] ]></query>

Parameter binding and executing is done programatically as seen in Exemple 11.3, « Parameter binding of a named query ».

Exemple 11.3. Parameter binding of a named query

    Query q = sess.getNamedQuery("ByNameAndMaximumWeight");

    q.setString(0, name);

    q.setInt(1, minWeight);

    List cats = q.list();

Notez que le code réel du programme est indépendant du langage de requête utilisé, vous pouvez aussi définir des requêtes SQL natives dans les méta-données, ou migrer des requêtes existantes vers Hibernate en les plaçant dans les fichiers de mapping.

Notez aussi que la déclaration d'une requête dans un élément `<hibernate-mapping>` nécessite un nom globalement unique pour la requête, alors que la déclaration d'une requête dans un élément `<class>` est rendue unique de manière automatique par la mise en préfixe du nom entièrement qualifié de la classe, par exemple `eg.Cat.ByNameAndMaximumWeight`.

### 11.4.2. Filtrer des collections

Un filtre de collection est un type spécial de requête qui peut être appliqué à une collection persistante ou à un tableau. La chaîne de requêtes peut se référer à `this`, correspondant à l'élément de la collection courant.

    Collection blackKittens = session.createFilter(

        pk.getKittens(),

        "where this.color = ?")

        .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )

        .list()

    );

La collection retournée est considérée comme un bag, et c'est une copie de la collection donnée. La collection originale n'est pas modifiée. C'est contraire à l'implication du nom "filtre"; mais cohérent avec le comportement attendu.

Observez que les filtres ne nécessitent pas une clause `from` (bien qu'ils puissent en avoir une si besoin est). Les filtres ne sont pas limités à retourner des éléments de la collection eux-mêmes.

    Collection blackKittenMates = session.createFilter(

        pk.getKittens(),

        "select this.mate where this.color = eg.Color.BLACK.intValue")

        .list();

Même une requête de filtre vide est utile, par exemple pour charger un sous-ensemble d'éléments dans une énorme collection :

    Collection tenKittens = session.createFilter(

        mother.getKittens(), "")

        .setFirstResult(0).setMaxResults(10)

        .list();

### 11.4.3. Requêtes par critères

HQL est extrêmement puissant, mais certains développeurs préfèrent construire des requêtes dynamiquement, en utilisant l'API orientée objet, plutôt que de construire des chaînes de requêtes. Hibernate fournit une API intuitive de requête `Criteria` pour ces cas :

    Criteria crit = session.createCriteria(Cat.class);

    crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );

    crit.setMaxResults(10);

    List cats = crit.list();

The `Criteria` and the associated `Example` API are discussed in more detail in Chapitre 17, Requêtes par critères.

### 11.4.4. Requêtes en SQL natif

Vous pouvez exprimer une requête en SQL, en utilisant `createSQLQuery()` et laisser Hibernate s'occuper du mappage des résultats vers des objets. Notez que vous pouvez à tout moment, appeler `session.connection()` et utiliser directement la `Connection` JDBC. Si vous choisissez d'utiliser l'API Hibernate, vous devez mettre les alias SQL entre accolades :

    List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")

        .addEntity("cat", Cat.class)

    .list();

    List cats = session.createSQLQuery(

        "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +

               "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +

        "FROM CAT {cat} WHERE ROWNUM<10")

        .addEntity("cat", Cat.class)

    .list()

SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in Chapitre 18, SQL natif.

## 11.5. Modifier des objets persistants

Les instances persistantes transactionnelles (c'est-à-dire des objets chargés, sauvegardés, créés ou requêtés par la `Session`) peuvent être manipulés par l'application et tout changement vers l'état persistant sera persisté lorsque la `Session` est "flushée" (traité plus tard dans ce chapitre). Il n'est pas nécessaire d'appeler une méthode particulière (comme `update()`, qui a un but différent) pour rendre vos modifications persistantes. Donc la manière la plus directe de mettre à jour l'état d'un objet est de le charger avec `load()`, et puis de le manipuler directement, tant que la `Session` est ouverte :

    DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );

    cat.setName("PK");

    sess.flush();  // changes to cat are automatically detected and persisted

Parfois ce modèle de programmation est inefficace puisqu'il nécessiterait un SQL `SELECT` (pour charger l'objet) et un SQL `UPDATE` (pour persister son état mis à jour) dans la même session. Ainsi Hibernate offre une autre approche, en utilisant des instances détachées.

## 11.6. Modifier des objets détachés

Beaucoup d'applications ont besoin de récupérer un objet dans une transaction, de l'envoyer à la couche interfacée avec l'utilisateur pour les manipulations, et de sauvegarder les changements dans une nouvelle transaction. Les applications qui utilisent cette approche dans un environnement à haute concurrence utilisent généralement des données versionnées pour assurer l'isolation des "longues" unités de travail.

Hibernate supporte ce modèle en permettant pour le rattachement d'instances détachées en utilisant des méthodes `Session.update()` ou `Session.merge()` :

    // in the first session

    Cat cat = (Cat) firstSession.load(Cat.class, catId);

    Cat potentialMate = new Cat();

    firstSession.save(potentialMate);

    // in a higher layer of the application

    cat.setMate(potentialMate);

    // later, in a new session

    secondSession.update(cat);  // update cat

    secondSession.update(mate); // update mate

Si le `Cat` avec l'identifiant `catId` avait déjà été chargé par `secondSession` lorsque l'application a essayé de le rattacher, une exception aurait été levée.

Utilisez `update()` si vous êtes sûr que la session ne contient pas déjà une instance persistante avec le même identifiant, et `merge()` si vous voulez fusionner vos modifications n'importe quand sans considérer l'état de la session. En d'autres termes, `update()` est généralement la première méthode que vous devez appeler dans une session fraîche, pour vous assurer que le rattachement de vos instances détachées est la première opération qui est exécutée.

The application should individually `update()` detached instances that are reachable from the given detached instance only if it wants their state to be updated. This can be automated using transitive persistence. See Section 11.11, « Persistance transitive » for more information.

La méthode `lock()` permet aussi à une application de ré-associer un objet avec une nouvelle session. Cependant, l'instance détachée doit être non modifiée.

    //just reassociate:

    sess.lock(fritz, LockMode.NONE);

    //do a version check, then reassociate:

    sess.lock(izi, LockMode.READ);

    //do a version check, using SELECT ... FOR UPDATE, then reassociate:

    sess.lock(pk, LockMode.UPGRADE);

Notez que `lock()` peut être utilisé avec différents `LockMode` s, voir la documentation de l'API et le chapitre sur la gestion des transactions pour plus d'informations. Le rattachement n'est pas le seul cas d'utilisation pour `lock()`.

Other models for long units of work are discussed in Section 13.3, « Contrôle de concurrence optimiste ».

## 11.7. Détection automatique d'un état

Les utilisateurs d'Hibernate ont demandé une méthode dont l'intention générale serait soit de sauvegarder une instance éphémère en générant un nouvel identifiant, soit mettre à jour/rattacher les instances détachées associées à l'identifiant courant. La méthode `saveOrUpdate()` implémente cette fonctionnalité.

    // in the first session

    Cat cat = (Cat) firstSession.load(Cat.class, catID);

    // in a higher tier of the application

    Cat mate = new Cat();

    cat.setMate(mate);

    // later, in a new session

    secondSession.saveOrUpdate(cat);   // update existing state (cat has a non-null id)

    secondSession.saveOrUpdate(mate);  // save the new instance (mate has a null id)

L'usage et la sémantique de `saveOrUpdate()` semble être confuse pour les nouveaux utilisateurs. Premièrement, aussi longtemps que vous n'essayez pas d'utiliser des instances d'une session dans une autre, vous ne devriez pas avoir besoin d'utiliser `update()`, `saveOrUpdate()`, ou `merge()`. Certaines applications n'utiliseront jamais ces méthodes.

Généralement `update()` ou `saveOrUpdate()` sont utilisées dans le scénario suivant :

- l'application charge un objet dans la première session

- l'objet est passé à la couche utilisateur

- certaines modifications sont effectuées sur l'objet

- l'objet est retourné à la couche logique métier

- l'application persiste ces modifications en appelant `update()` dans une seconde session

`saveOrUpdate()` s'utilise dans le cas suivant :

- si l'objet est déjà persistant dans cette session, ne rien faire

- si un autre objet associé à la session a le même identifiant, lever une exception

- si l'objet n'a pas de propriété d'identifiant, appeler `save()`

- si l'identifiant de l'objet a une valeur assignée à un objet nouvellement instancié, appeler `save()`

- si l'objet est versionné (par `<version>` ou `<timestamp>`), et la valeur de la propriété de version est la même valeur que celle assignée à un objet nouvellement instancié, appeler `save()`

- sinon mettre à jour l'objet avec `update()`

et `merge()` est très différent :

- s'il y a une instance persistante avec le même identifiant couramment associé à la session, copier l'état de l'objet donné dans l'instance persistante

- s'il n'y a pas d'instance persistante associée à cette session, essayer de le charger à partir de la base de données, ou créer une nouvelle instance persistante

- l'instance persistante est retournée

- l'instance donnée ne devient pas associée à la session, elle reste détachée

## 11.8. Suppression d'objets persistants

`Session.delete()` supprimera l'état d'un objet de la base de données. Bien sûr, votre application pourrait encore conserver une référence vers un objet effacé. Il est préférable de penser à `delete()` comme rendant une instance persistante éphémère.

    sess.delete(cat);

Vous pouvez effacer des objets dans l'ordre que vous voulez, sans risque de violations de contrainte de clef étrangère. Il est encore possible de violer une contrainte `NOT NULL` sur une colonne de clef étrangère en effaçant des objets dans le mauvais ordre, par exemple si vous effacez le parent, mais oubliez d'effacer les enfants.

## 11.9. Réplication d'objets entre deux entrepôts de données

Il est occasionnellement utile de pouvoir prendre un graphe d'instances persistantes et de les rendre persistantes dans un entrepôt différent, sans regénérer les valeurs des identifiants.

    //retrieve a cat from one database

    Session session1 = factory1.openSession();

    Transaction tx1 = session1.beginTransaction();

    Cat cat = session1.get(Cat.class, catId);

    tx1.commit();

    session1.close();

    //reconcile with a second database

    Session session2 = factory2.openSession();

    Transaction tx2 = session2.beginTransaction();

    session2.replicate(cat, ReplicationMode.LATEST_VERSION);

    tx2.commit();

    session2.close();

Le `ReplicationMode` détermine comment `replicate()` traitera les conflits avec des lignes existantes dans la base de données.

- `ReplicationMode.IGNORE` - ignore l'objet s'il y a une ligne existante dans la base de données avec le même identifiant

- `ReplicationMode.OVERWRITE` - écrase n'importe quelle ligne existante dans la base de données avec le même identifiant

- `ReplicationMode.EXCEPTION` - lève une exception s'il y a une ligne dans la base de données avec le même identifiant

- `ReplicationMode.LATEST_VERSION` - écrase la ligne si son numéro de version est plus petit que le numéro de version de l'objet, sinon ignore l'objet

Les cas d'utilisation de cette fonctionnalité incluent la réconciliation de données entrées dans différentes base de données, l'extension des informations de configuration du système durant une mise à jour du produit, retour en arrière sur les changements effectués durant des transactions non-ACID, et plus.

## 11.10. Flush de la session

De temps en temps la `Session` exécutera les expressions SQL requises pour synchroniser l'état de la connexion JDBC avec l'état des objets retenus en mémoire. Ce processus, flush, survient par défaut aux points suivants :

- avant certaines exécutions de requête

- lors d'un appel à `org.hibernate.Transaction.commit()`

- lors d'un appel à `Session.flush()`

Les expressions SQL sont effectuées dans l'ordre suivant :

1. insertion des entités, dans le même ordre que celui des objets correspondants sauvegardés par l'appel à `Session.save()`

2. mise à jour des entités

3. suppression des collections

4. suppression, mise à jour et insertion des éléments des collections

5. insertion des collections

6. suppression des entités, dans le même ordre que celui des objets correspondants qui ont été supprimés par l'appel de `Session.delete()`

Une exception est que des objets utilisant la génération `native` d'identifiants sont insérés lorsqu'ils sont sauvegardés.

Excepté lorsque vous appelez `flush()` explicitement, il n'y a absolument aucune garantie à propos de quand la `Session` exécute les appels JDBC, seulement sur l'ordre dans lequel ils sont exécutés. Cependant, Hibernate garantit que `Query.list(..)` ne retournera jamais de données périmées, ni des données fausses.

It is possible to change the default behavior so that flush occurs less frequently. The `FlushMode` class defines three different modes: only flush at commit time when the Hibernate `Transaction` API is used, flush automatically using the explained routine, or never flush unless `flush()` is called explicitly. The last mode is useful for long running units of work, where a `Session` is kept open and disconnected for a long time (see Section 13.3.2, « Les sessions longues et le versionnage automatique. »).

    sess = sf.openSession();

    Transaction tx = sess.beginTransaction();

    sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state

    Cat izi = (Cat) sess.load(Cat.class, id);

    izi.setName(iznizi);

    // might return stale data

    sess.find("from Cat as cat left outer join cat.kittens kitten");

    // change to izi is not flushed!

    ...

    tx.commit(); // flush occurs

    sess.close();

During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in Chapitre 13, Transactions et Accès concurrents.

## 11.11. Persistance transitive

Il est assez pénible de sauvegarder, supprimer, ou rattacher des objets un par un, surtout si vous traitez un graphe d'objets associés. Un cas courant est une relation parent/enfant. Considérez l'exemple suivant :

Si les enfants de la relation parent/enfant étaient des types de valeur (par exemple, une collection d'adresses ou de chaînes de caractères), leur cycle de vie dépendrait du parent et aucune action ne serait requise pour "cascader" facilement les changements d'état. Si le parent est sauvegardé, les objets enfants de type de valeur sont sauvegardés également, si le parent est supprimé, les enfants sont supprimés, etc. Ceci fonctionne même pour des opérations telles que la suppression d'un enfant de la collection ; Hibernate le détectera et étant donné que les objets de type de valeur ne peuvent pas avoir de références partagées, il supprimera l'enfant de la base de données.

Maintenant considérez le même scénario avec un parent dont les objets enfants sont des entités, et non des types de valeur (par exemple, des catégories et des objets, ou un parent et des chatons). Les entités ont leur propre cycle de vie, supportent les références partagées (donc supprimer une entité de la collection ne signifie pas qu'elle peut être supprimée), et il n'y a par défaut pas de cascade d'état d'une entité vers n'importe quelle entité associée. Hibernate n'implémente pas la persistance par accessibilité par défaut.

Pour chaque opération basique de la session Hibernate - incluant `persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()` - il y a un style de cascade correspondant. Respectivement, les styles de cascade s'appellent `persist, merge, save-update, delete, lock, refresh, evict, replicate`. Si vous voulez qu'une opération soit cascadée le long d'une association, vous devez l'indiquer dans le document de mappage. Par exemple :

    <one-to-one name="person" cascade="persist"/>

Les styles de cascade peuvent être combinés :

    <one-to-one name="person" cascade="persist,delete,lock"/>

Vous pouvez même utiliser `cascade="all"` pour spécifier que toutes les opérations devraient être cascadées le long de l'association. La valeur par défaut `cascade="none"` spécifie qu'aucune opération ne sera cascadée.

In case you are using annotatons you probably have noticed the `cascade` attribute taking an array of `CascadeType` as a value. The cascade concept in JPA is very is similar to the transitive persistence and cascading of operations as described above, but with slightly different semantics and cascading types:

- `CascadeType.PERSIST`: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed

- `CascadeType.MERGE`: cascades the merge operation to associated entities if merge() is called or if the entity is managed

- `CascadeType.REMOVE`: cascades the remove operation to associated entities if delete() is called

- `CascadeType.REFRESH:` cascades the refresh operation to associated entities if refresh() is called

- `CascadeType.DETACH:` cascades the detach operation to associated entities if detach() is called

- `CascadeType.ALL`: all of the above

## Note

CascadeType.ALL also covers Hibernate specific operations like save-update, lock etc...

A special cascade style, `delete-orphan`, applies only to one-to-many associations, and indicates that the `delete()` operation should be applied to any child object that is removed from the association. Using annotations there is no `CascadeType.DELETE-ORPHAN` equivalent. Instead you can use the attribute `orphanRemoval as seen in` Exemple 11.4, « @OneToMany with orphanRemoval ». If an entity is removed from a `@OneToMany` collection or an associated entity is dereferenced from a `@OneToOne` association, this associated entity can be marked for deletion if `orphanRemoval` is set to true.

Exemple 11.4. `@OneToMany` with `orphanRemoval`

    @Entity

    public class Customer {

       private Set<Order> orders;

       @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)

       public Set<Order> getOrders() { return orders; }

       public void setOrders(Set<Order> orders) { this.orders = orders; }

       [...]

    }

    @Entity

    public class Order { ... }

    Customer customer = em.find(Customer.class, 1l);

    Order order = em.find(Order.class, 1l);

    customer.getOrders().remove(order); //order will be deleted by cascade

Recommandations :

- It does not usually make sense to enable cascade on a many-to-one or many-to-many association. In fact the `@ManyToOne` and `@ManyToMany` don't even offer a `orphanRemoval` attribute. Cascading is often useful for one-to-one and one-to-many associations.

- If the child object's lifespan is bounded by the lifespan of the parent object, make it a life cycle object by specifying `cascade="all,delete-orphan"(`@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)`)`.

- Sinon, vous pourriez ne pas avoir besoin de cascade du tout. Mais si vous pensez que vous travaillerez souvent avec le parent et les enfants ensemble dans la même transaction, et que vous voulez vous éviter quelques frappes, considérez l'utilisation de `cascade="persist,merge,save-update"`.

Mapper une association (soit une simple association valuée, soit une collection) avec `cascade="all"` marque l'association comme une relation de style parent/enfant où la sauvegarde/mise à jour/suppression du parent entraîne la sauvegarde/mise à jour/suppression de l'enfant ou des enfants.

Furthermore, a mere reference to a child from a persistent parent will result in save/update of the child. This metaphor is incomplete, however. A child which becomes unreferenced by its parent is not automatically deleted, except in the case of a one-to-many association mapped with `cascade="delete-orphan"`. The precise semantics of cascading operations for a parent/child relationship are as follows:

- Si un parent est passé à `persist()`, tous les enfant sont passés à `persist()`

- Si un parent est passé à `merge()`, tous les enfants sont passés à `merge()`

- Si un parent est passé à `save()`, `update()` ou `saveOrUpdate()`, tous les enfants sont passés à `saveOrUpdate()`

- Si un enfant détaché ou éphémère devient référencé par un parent persistant, il est passé à `saveOrUpdate()`

- Si un parent est supprimé, tous les enfants sont passés à `delete()`

- Si un enfant est déréférencé par un parent persistant, rien de spécial n'arrive - l'application devrait explicitement supprimer l'enfant si nécessaire - à moins que `cascade="delete-orphan"` soit paramétré, auquel cas l'enfant "orphelin" est supprimé.

Enfin, la cascade des opérations peut être effectuée sur un graphe donné lors de l'appel de l'opération or lors du flush suivant. Toutes les opérations, lorsqu'elles sont cascadées, le sont sur toutes les entités associées accessibles lorsque l'opération est exécutée. Cependant `save-upate` et `delete-orphan` sont cascadés à toutes les entités associées accessibles lors du flush de la `Session`.

## 11.12. Utilisation des méta-données

Hibernate requiert un modèle de méta-niveau très riche de toutes les entités et types valués. De temps en temps, ce modèle est très utile à l'application elle même. Par exemple, l'application pourrait utiliser les méta-données de Hibernate pour implémenter un algorithme de copie en profondeur "intelligent" qui comprendrait quels objets devraient être copiés (par exemple les types de valeur mutables) et lesquels ne devraient pas l'être (par exemple les types de valeurs immutables et, éventuellement, les entités associées).

Hibernate expose les méta-données via les interfaces `ClassMetadata` et `CollectionMetadata` et la hiérarchie `Type`. Les instances des interfaces de méta-données peuvent être obtenues à partir de la `SessionFactory`.

    Cat fritz = ......;

    ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);

    Object[] propertyValues = catMeta.getPropertyValues(fritz);

    String[] propertyNames = catMeta.getPropertyNames();

    Type[] propertyTypes = catMeta.getPropertyTypes();

    // get a Map of all properties which are not collections or associations

    Map namedValues = new HashMap();

    for ( int i=0; i<propertyNames.length; i++ ) {

        if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {

            namedValues.put( propertyNames[i], propertyValues[i] );

        }

    }

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 10. Mapping d'héritage de classe
- Niveau supérieur
- Sommaire
- SuivantChapitre 12. Read-only entities