hibernate-orm/doc/reference/fr/modules/session_api.xml

1242 lines
56 KiB
XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<chapter id="objectstate">
<title>Travailler avec des objets</title>
<para>
Hibernate est une solution de mapping objet/relationnel complète qui ne masque pas
seulement au développpeur les détails du système de gestion de base de données sous-jacent,
mais offre aussi <emphasis>la gestion d'état</emphasis> des objets. C'est, contrairement
à la gestion de <literal>statements</literal> SQL dans les couches de persistance
habituelles JDBC/SQL, une vue orientée objet très naturelle de la persistance dans les
applications Java.
</para>
<para>
En d'autres mots, les développeurs d'applications Hibernate devrait toujours
réfléchir à <emphasis>l'état</emphasis> de leurs objets, et pas nécessairement à
l'exécution des expressions SQL. Cette part est prise en charge pas Hibernate et
seulement importante pour les développeurs d'applications lors du réglage de la
performance de leur système.
</para>
<sect1 id="objectstate-overview">
<title>États des objets Hibernate</title>
<para>
Hibernate définit et comprend les états suivants :
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Éphémère</emphasis> (NdT : transient) - un objet est éphémère s'il a juste
été instancié en utilisant l'opérateur <literal>new</literal>. 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 <literal>Session</literal>
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).
</para>
</listitem>
<listitem>
<para>
<emphasis>Persistant</emphasis> - 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 <literal>Session</literal>.
Hibernate détectera n'importe quels changements effectués sur un objet dans l'état
persistant et synchronisera l'état avec la base de données lors de la fin l'unité de travail.
Les développeurs n'exécutent pas d'expressions <literal>UPDATE</literal> ou
<literal>DELETE</literal> manuelles lorsqu'un objet devrait être rendu éphémère.
</para>
</listitem>
<listitem>
<para>
<emphasis>Détaché</emphasis> - une instance détachée est un objet qui a été persistant,
mais dont sa <literal>Session</literal> 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 réattachée à une nouvelle <literal>Session</literal>
plus tard dans le temps, 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 <emphasis>conversations</emphasis>,
c'est-à-dire une unité de travail du point de vue de l'utilisateur.
</para>
</listitem>
</itemizedlist>
<para>
Nous alons maintenant dicuster des états et des transitions d'état (et des méthodes
d'Hibernate qui déclenchent une transition) plus en détails.
</para>
</sect1>
<sect1 id="objectstate-makingpersistent" revision="1">
<title>Rendre des objets persistants</title>
<para>
Les instances nouvellement instanciées d'une classe persistante sont considérées
<emphasis>éphémères</emphasis> par Hibernate. Nous pouvons rendre une instance
éphémère <emphasis>persistante</emphasis> en l'associant avec une session :
</para>
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
<para>
Si <literal>Cat</literal> a un identifiant généré, l'identifiant est généré et assigné
au <literal>cat</literal> lorsque <literal>save()</literal> est appelée. Si <literal>Cat</literal>
a un identifiant <literal>assigned</literal>, ou une clef composée, l'identifiant
devrait être assigné à l'instance de <literal>cat</literal> avant d'appeler <literal>save()</literal>.
Vous pouvez aussi utiliser <literal>persist()</literal> à la place de<literal>save()</literal>,
avec la sémantique définie plus tôt dans le brouillon d'EJB3.
</para>
<para>
Alternativement, vous pouvez assigner l'identifiant en utilisant une version
surchargée de <literal>save()</literal>.
</para>
<programlisting><![CDATA[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) );]]></programlisting>
<para>
Si l'objet que vous rendez persistant a des objets associés (par exemple,
la collection <literal>kittens</literal> dans l'exemple précédent), ces objets
peuvent être rendus persistants dans n'importe quel ordre que vous souhaitez
à moins que vous ayez une contrainte <literal>NOT NULL</literal> sur la
colonne de la clef étrangère. Il n'y a jamais de risque de violer une
contrainte de clef étrangère. Cependant, vous pourriez violer une contrainte
<literal>NOT NULL</literal> si vous appeliez <literal>save()</literal> sur
les objets dans le mauvais ordre.
</para>
<para>
Habituellement, vous ne vous préoccupez pas de ce détail, puisque vous
utiliserez très probablement la fonctionnalité de <emphasis>persistance
transitive</emphasis> d'Hibernate pour sauvegarder les objets associés
automatiquement. Alors, même les violations de contrainte <literal>NOT NULL</literal>
n'ont plus lieu - Hibernate prendra soin de tout. La persistance transitive est
traitée plus loin dans ce chapitre.
</para>
</sect1>
<sect1 id="objectstate-loading">
<title>Chargement d'un objet</title>
<para>
Les méthodes <literal>load()</literal> de <literal>Session</literal> vous donnent
un moyen de récupérer une instance persistante si vous connaissez déjà son identifiant.
<literal>load()</literal> prend un objet de classe et chargera l'état dans une instance
nouvellement instanciée de cette classe, dans un état persistant.
</para>
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
<programlisting><![CDATA[// vous avez besoin d'envelopper les identiants primitifs
long pkId = 1234;
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
<para>
Alternativement, vous pouvez charger un état dans une instance donnée :
</para>
<programlisting><![CDATA[Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();]]></programlisting>
<para>
Notez que <literal>load()</literal> 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, <literal>load()</literal> 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 <literal>batch-size</literal> est défini pour le mapping de la classe.
</para>
<para>
Si vous n'êtes pas certain qu'une ligne correspondante existe, vous devriez
utiliser la méthode <literal>get()</literal>, laquelle accède à la base de
données immédiatement et retourne null s'il n'y a pas de ligne correspondante.
</para>
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;]]></programlisting>
<para>
Vous pouvez même charger un objet en employant un <literal>SELECT ... FOR UPDATE</literal> SQL,
en utilisant un <literal>LockMode</literal>. Voir la documentation de l'API pour plus d'informations.
</para>
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
<para>
Notez que n'importe quelles instances associées ou collections contenues
<emphasis>ne sont pas</emphasis> sélectionnées par <literal>FOR UPDATE</literal>,
à moins que vous ne décidiez de spécifier <literal>lock</literal> ou <literal>all</literal>
en tant que style de cascade pour l'association.
</para>
<para>
Il est possible de re-charger un objet et toutes ses collections à n'importe quel moment,
en utilisant la méthode <literal>refresh()</literal>. C'est utile lorsque des "triggers" de
base de données sont utilisés pour initiliser certains propriétés de l'objet.
</para>
<programlisting><![CDATA[sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
<para>
Une question importante apparaît généralement à ce point : combien (NdT : de données) Hibernate
charge-t-il de la base de données et combient de <literal>SELECT</literal>s utilisera-t-il ?
Cela dépent de la <emphasis>stratégie de récupération</emphasis> et cela est expliqué dans
<xref linkend="performance-fetching"/>.
</para>
</sect1>
<sect1 id="objectstate-querying" revision="1">
<title>Requêtage</title>
<para>
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é Criteria et Example (QBC et QBE).
Vous pouvez aussi exprimez votre requête dans le SQL natif de votre base de données,
avec un support optionnel d'Hibernate pour la conversion des ensembles de résultats en
objets.
</para>
<sect2 id="objectstate-querying-executing" revision="1">
<title>Exécution de requêtes</title>
<para>
Les requêtes HQL et SQL natives sont représentées avec une instance de <literal>org.hibernate.Query</literal>.
L'interface offre des méthodes pour la liaison des paramètres, la gestion des ensembles de resultats, et pour
l'exécution de la requête réelle. Vous obtenez toujours une <literal>Query</literal> en utilisant la
<literal>Session</literal> courante :
</para>
<programlisting><![CDATA[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());]]></programlisting>
<para>
Une requête est généralement exécutée en invoquant <literal>list()</literal>,
le résultat de la requête sera chargée complètement dans une collection en mémoire.
Les intances d'entités recupérées par une requête sont dans un état persistant.
La méthode <literal>uniqueResult()</literal> offre un raccourci si vous
savez que votre requête retournera seulement un seul objet.
</para>
<sect3 id="objectstate-querying-executing-iterate">
<title>Itération de résultats</title>
<para>
Occasionnellement, vous pourriez être capable d'obtenir de meilleures
performances en exécutant la requête avec la méthode <literal>iterate()</literal>.
Ce sera généralement seulement le cas si vous espérez que les intances 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 cachées, <literal>iterate()</literal>
sera plus lent que <literal>list()</literal> et pourrait nécessiter plusieurs
accès à la base de données pour une simple requête, généralement <emphasis>1</emphasis>
pour le select initial qui retourne seulement les identifiants, et <emphasis>n</emphasis>
selects supplémentaires pour initialiser les instances réelles.
</para>
<programlisting><![CDATA[// 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;
}
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-tuples">
<title>Requêtes qui retournent des tuples</title>
<para>
Les requêtes d'Hibernate retournent parfois des tuples d'objets, auquel cas chaque tuple
est retourné comme un tableau :
</para>
<programlisting><![CDATA[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 = tuple[0];
Cat mother = tuple[1];
....
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-scalar" revision="1">
<title>Résultats scalaires</title>
<para>
Des requêtes peuvent spécifier une propriété d'une classe dans la clause <literal>select</literal>.
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 pas des entités dans un état persistant).
</para>
<programlisting><![CDATA[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];
.....
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-parameters">
<title>Lier des paramètres</title>
<para>
Des méthodes de <literal>Query</literal> sont fournies pour lier des
valeurs à des paramètres nommés ou à des paramètres de style JDBC <literal>?</literal>.
<emphasis>Contrairement à JDBC, les numéros des paramètres d'Hibernate commencent à zéro.</emphasis>
Les paramètres nommés sont des identifiants de la forme <literal>:nom</literal> dans la chaîne de
caractères de la requête. Les avantages des paramètres nommés sont :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
les paramètres nommés sont insensibles à l'ordre de leur place dans la chaîne
de la requête
</para>
</listitem>
<listitem>
<para>
ils peuvent apparaître plusieurs fois dans la même requête
</para>
</listitem>
<listitem>
<para>
ils sont auto-documentés
</para>
</listitem>
</itemizedlist>
<programlisting><![CDATA[//paramètre nomme (préféré)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();]]></programlisting>
<programlisting><![CDATA[//paramètre positionnel
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();]]></programlisting>
<programlisting><![CDATA[//liste de paramètres nommés
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();]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-pagination">
<title>Pagination</title>
<para>
Si vous avez besoin de spécifier des liens sur votre ensemble de résultats (le nombre
maximum de lignes que vous voulez récupérez et/ou la première ligne que vous voulez récupérer)
vous devriez utiliser des méthodes de l'interface <literal>Query</literal> :
</para>
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();]]></programlisting>
<para>
Hibernate sait comment traduite cette requête de limite en SQL natif pour votre SGBD.
</para>
</sect3>
<sect3 id="objectstate-querying-executing-scrolling">
<title>Itération "scrollable"</title>
<para>
Si votre connecteur JDBC supporte les <literal>ResultSet</literal>s "scrollables",
l'interface <literal>Query</literal> peut être utilisée pour obtenir un objet
<literal>ScrollableResults</literal>, lequel permet une navigation flexible dans les
résultats de la requête.
</para>
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// trouve le premier nom sur chaque page d'une liste alphabétique de noms de chats
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Maintenant, obtiens la première page de chats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()]]></programlisting>
<para>
Notez qu'une connexion ouverte (et un curseur) est requise pour cette fonctionnalité,
utilisez <literal>setMaxResult()</literal>/<literal>setFirstResult()</literal> si vous
avez besoin d'une fonctionnalité de pagination hors ligne.
</para>
</sect3>
<sect3 id="objectstate-querying-executing-named" revision="1">
<title>Externaliser des requêtes nommées</title>
<para>
Vous pouvez aussi définir des requêtes nommées dans le document de mapping.
(Souvenez-vous d'utiliser une section <literal>CDATA</literal> si votre requête
contient des caractères qui pourraient être interprétés comme des éléments XML.)
</para>
<programlisting><![CDATA[<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>]]></programlisting>
<para>
La liaison de paramètres et l'exécution sont fait par programmation :
</para>
<programlisting><![CDATA[Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();]]></programlisting>
<para>
Notez que le code réel du programme est indépendant du langage de requête qui est
utilisé, vous pouvez aussi définir des requêtes SQL nativez dans les méta-données, ou
migrer des requêtes existantes vers Hibernate en les plaçant dans les fichiers de mapping.
</para>
<para>
Notez aussi que la déclaration d'une requête dans un élément <literal>&lt;hibernate-mapping&gt;</literal>
nécessite un nom globalement unique pour la requête, alors que la déclaration d'une requête
dans une élément <literal>&lt;class&gt;</literal> est rendue unique de manière automatique par
la mise en préfixe du nom entièrement de la classe, par exemple
<literal>eg.Cat.ByNameAndMaximumWeight</literal>.
</para>
</sect3>
</sect2>
<sect2 id="objectstate-filtering" revision="1">
<title>Filtrer des collections</title>
<para>
Un <emphasis>filtre</emphasis> 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ête peut se référer à
<literal>this</literal>, correspondant à l'élément de la collection courant.
</para>
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);]]></programlisting>
<para>
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).
</para>
<para>
Observez que les filtres ne nécessitent pas une clause <literal>from</literal> (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.
</para>
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();]]></programlisting>
<para>
Même une requête de filtre vide est utile, par exemple pour charger un sous-ensemble
d'éléments dans une énorme collection :
</para>
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();]]></programlisting>
</sect2>
<sect2 id="objecstate-querying-criteria" revision="1">
<title>Requêtes Criteria</title>
<para>
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 construire
des chaînes de requêtes. Hibernate fournit une API intuitive de requête <literal>Criteria</literal>
pour ces cas :
</para>
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();]]></programlisting>
<para>
Les APIs <literal>Criteria</literal> et <literal>Example</literal> associé sont
traitées plus en détail dans <xref linkend="querycriteria"/>.
</para>
</sect2>
<sect2 id="objectstate-querying-nativesql" revision="2">
<title>Requêtes en SQL natif</title>
<para>
Vous pouvez exprimer une requête en SQL, en utilisant <literal>createSQLQuery()</literal>
et laisser Hibernate s'occuper du mapping des résultats vers des objets. Notez que vous
pouvez n'importe quand appeler <literal>session.connection()</literal> et utiliser
directement la <literal>Connection</literal> JDBC. Si vous choisissez d'utiliser
l'API Hibernate, vous devez mettre les alias SQL entre accolades :
</para>
<programlisting><![CDATA[List cats = session.createSQLQuery(
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list();]]></programlisting>
<programlisting><![CDATA[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",
"cat",
Cat.class
).list()]]></programlisting>
<para>
Les requêtes SQL peuvent contenir des paramètres nommés et positionnels, comme des
requêtes Hibernate. Plus d'informations à propos des requêtes SQL natives dans Hibernate
peuvent être trouvées dans <xref linkend="querysql"/>.
</para>
</sect2>
</sect1>
<sect1 id="objectstate-modifying" revision="1">
<title>Modifier des objets persistants</title>
<para>
Les <emphasis>instances persistantes transactionnelles</emphasis> (c'est-à-dire des objets
chargés, sauvegardés, créés ou requêtés par la <literal>Session</literal>) peuvent être
manipulées par l'application et n'importe quel changement vers l'état persistant sera
persisté lorsque la <literal>Session</literal> est <emphasis>"flushée"</emphasis> (traité
plus tard dans ce chapitre). Il n'y a pas besoin d'appeler une méthode particulière
(comme <literal>update()</literal>, 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 <literal>load()</literal>, et puis le manipuler directement, tant que la
<literal>Session</literal> est ouverte :
</para>
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
<para>
Parfois ce modèle de programmation est inefficace puisqu'il nécessiterait un
<literal>SELECT</literal> SQL (pour charger l'objet) et un <literal>UPDATE</literal>
SQL (pour persister son état mis à jour) dans la même session. Aussi Hibernate offre
une autre approche, en utilisant des instances détachées.
</para>
<para>
<emphasis>Notez que Hibernate n'offre par sa propre API pour l'exécution directe
d'expressions <literal>UPDATE</literal> ou <literal>DELETE</literal>. Hibernate
est un service de <emphasis>gestion d'état</emphasis>, vous n'avez pas à penser
aux <emphasis>expressions</emphasis> pour l'utiliser. JDBC est une API parfaite
pour exécuter des expressions SQL, vous pouvez obtenir une <literal>Connection</literal>
JDBC n'importe quand en appelant <literal>session.connection()</literal>. En outre,
la notion d'opérations de masse entre en conflit avec le mapping objet/relationnel
pour les applications orientées processus de transactions en ligne. Les futures
versions d'Hibernate peuvent cependant fournir des fonctions d'opération de masse.
Voir <xref linkend="batch"/> pour les astuces possibles d'opérations groupées.</emphasis>
</para>
</sect1>
<sect1 id="objectstate-detached" revision="2">
<title>Modifier des objets détachés</title>
<para>
Beaucoup d'applications ont besoin de récupérer un objet dans une transaction,
l'envoyer à la couche interfacée avec l'utilisateur pour les manipulations, puis
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 pour les "longues"
unités de travail.
</para>
<para>
Hibernate supporte ce modèle en permettant pour le réattachement d'instances détachées
l'utilisation des méthodes <literal>Session.update()</literal> ou <literal>Session.merge()</literal> :
</para>
<programlisting><![CDATA[// dans la première session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// dans une couche plus haute de l'application
cat.setMate(potentialMate);
// plus tard, dans une nouvelle session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate]]></programlisting>
<para>
Si le <literal>Cat</literal> avec l'identifiant <literal>catId</literal> avait déjà
été chargé par <literal>secondSession</literal> lorsque l'application a essayé de le
réattacher, une exception aurait été levée.
</para>
<para>
Utilisez <literal>update()</literal> si vous êtes sure que la session ne contient pas
déjà une instance persistante avec le même identifiant, et <literal>merge()</literal>
si vous voulez fusionner vos modifications n'importe quand sans considérer l'état de
la session. En d'autres mots, <literal>update()</literal> est généralement la première méthode
que vous devriez appeler dans une session fraîche, pour s'assurer que le réattachement
de vos instances détachées est la première opération qui est exécutée.
</para>
<para>
L'application devrait individuellement <literal>update()</literal> (NdT : mettre à jour)
les instances détachées accessibles depuis l'instance détachée donnée si et
<emphasis>seulement</emphasis> si elle veut que leur état soit aussi mis à jour. Ceci
peut être automatisé bien sûr, en utilisant la <emphasis>persistance transitive</emphasis>,
voir <xref linkend="objectstate-transitive"/>.
</para>
<para>
La méthode <literal>lock()</literal> permet aussi à une application de réassocier un
objet avec une nouvelle session. Pourtant, l'instance détachée doit être non modifiée !
</para>
<programlisting><![CDATA[//réassocie :
sess.lock(fritz, LockMode.NONE);
//fait une vérification de version, puis réassocie :
sess.lock(izi, LockMode.READ);
//fait une vérification de version, en utilisant SELECT ... FOR UPDATE, puis réassocie :
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
<para>
Notez que <literal>lock()</literal> peut être utilisé avec différents
<literal>LockMode</literal>s, voir la documentation de l'API documentation et le chapitre
sur la gestion des transactions pour plus d'informations. Le réattachement n'est pas le seul
cas d'utilisation pour <literal>lock()</literal>.
</para>
<para>
D'autres modèles pour de longues unités de travail sont traités dans <xref linkend="transactions-optimistic"/>.
</para>
</sect1>
<sect1 id="objectstate-saveorupdate">
<title>Détection automatique d'un état</title>
<para>
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/réattacher les instances détachées associées à l'identifiant courant.
La méthode <literal>saveOrUpdate()</literal> implémente cette fonctionnalité.
</para>
<programlisting><![CDATA[// dans la première session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// dans une partie plus haute de l'application
Cat mate = new Cat();
cat.setMate(mate);
// plus tard, dans une nouvelle session
secondSession.saveOrUpdate(cat); // met à jour un état existant (cat a un identifiant non-null)
secondSession.saveOrUpdate(mate); // sauvegarde les nouvelles instances (mate a un identiant null)]]></programlisting>
<para>
L'usage et la sémantique de <literal>saveOrUpdate()</literal> 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 <literal>update()</literal>,
<literal>saveOrUpdate()</literal>, ou <literal>merge()</literal>. Certaines applications
n'utiliseront jamais ces méthodes.
</para>
<para>
Généralement <literal>update()</literal> ou <literal>saveOrUpdate()</literal> sont utilisées dans
le scénario suivant :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
l'application charge un objet dans la première session
</para>
</listitem>
<listitem>
<para>
l'objet est passé à la couche utilisateur
</para>
</listitem>
<listitem>
<para>
certaines modifications sont effectuées sur l'objet
</para>
</listitem>
<listitem>
<para>
l'objet est retourné à la couche logique métier
</para>
</listitem>
<listitem>
<para>
l'application persiste ces modifications en appelant
<literal>update()</literal> dans une seconde sessin
</para>
</listitem>
</itemizedlist>
<para>
<literal>saveOrUpdate()</literal> s'utilise dans le cas suivant :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
si l'objet est déjà persistant dans cette session, ne rien faire
</para>
</listitem>
<listitem>
<para>
si un autre objet associé à la session a le même identifiant, lever une exception
</para>
</listitem>
<listitem>
<para>
si l'objet n'a pas de propriété d'identifiant, appeler <literal>save()</literal>
</para>
</listitem>
<listitem>
<para>
si l'identifiant de l'objet a une valeur assignée à un objet nouvellement instancié,
appeler <literal>save()</literal>
</para>
</listitem>
<listitem>
<para>
si l'objet est versionné (par <literal>&lt;version&gt;</literal> ou
<literal>&lt;timestamp&gt;</literal>), et la valeur de la propriété de version
est la même valeur que celle assignée à un objet nouvellement instancié, appeler
<literal>save()</literal>
</para>
</listitem>
<listitem>
<para>
sinon mettre à jour l'objet avec <literal>update()</literal>
</para>
</listitem>
</itemizedlist>
<para>
et <literal>merge()</literal> est très différent :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
s'il y a une instance persistante avec le même identifiant couramment
associée à la session, copier l'état de l'objet donné dans l'instance persistante
</para>
</listitem>
<listitem>
<para>
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
</para>
</listitem>
<listitem>
<para>
l'instance persistante est retournée
</para>
</listitem>
<listitem>
<para>
l'instance donnée ne devient pas associée à la session, elle reste détachée
</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="objectstate-deleting" revision="1">
<title>Suppression d'objets persistants</title>
<para>
<literal>Session.delete()</literal> 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 mieux de penser à <literal>delete()</literal> comme rendant une instance persistante
éphémère.
</para>
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
<para>
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 <literal>NOT
NULL</literal> sur une colonne de clef étrangère en effaçant des objets dans le
mauvais ordre, par exemple si vous effacer le parent, mais oubliez d'effacer les enfants.
</para>
</sect1>
<sect1 id="objectstate-replicating" revision="1">
<title>Réplication d'objets entre deux entrepôts de données</title>
<para>
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.
</para>
<programlisting><![CDATA[//récupère un cat de la base de données
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
// réconcilie la seconde base de données
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();]]></programlisting>
<para>
Le <literal>ReplicationMode</literal> détermine comment <literal>replicate()</literal>
traitera les conflits avec les lignes existantes dans la base de données.
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>ReplicationMode.IGNORE</literal> - ignore l'objet s'il y a une ligne
existante dans la base de données avec le même identifiant
</para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.OVERWRITE</literal> - écrase n'importe quelle ligne existante
dans la base de données avec le même identifiant
</para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.EXCEPTION</literal> - lève une exception s'il y une ligne dans
la base de données avec le même identifiant
</para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.LATEST_VERSION</literal> - écrase la ligne si son numéro de version
est plus petit que le numéro de version de l'objet, ou ignore l'objet sinon
</para>
</listitem>
</itemizedlist>
<para>
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.
</para>
</sect1>
<sect1 id="objectstate-flushing">
<title>Flush de la session</title>
<para>
De temps en temps la <literal>Session</literal> exécutera les expressions SQL
requises pour syncrhoniser l'état de la connexion JDBC avec l'état des objets
retenus en mémoire. Ce processus, <emphasis>flush</emphasis>, arrive par défaut aux
points suivants :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
lors de certaines exécutions de requête
</para>
</listitem>
<listitem>
<para>
lors d'un appel à <literal>org.hibernate.Transaction.commit()</literal>
</para>
</listitem>
<listitem>
<para>
lors d'un appel à <literal>Session.flush()</literal>
</para>
</listitem>
</itemizedlist>
<para>
Les expressions SQL sont effectuées dans l'ordre suivant :
</para>
<orderedlist spacing="compact">
<listitem>
<para>
insertion des entités, dans le même ordre que celui des
objets correspondants sauvegardés par l'appel à <literal>Session.save()</literal>
</para>
</listitem>
<listitem>
<para>
mise à jours des entités
</para>
</listitem>
<listitem>
<para>
suppression des collections
</para>
</listitem>
<listitem>
<para>
suppression, mise à jour et insertion des éléments des collections
</para>
</listitem>
<listitem>
<para>
insertion des collections
</para>
</listitem>
<listitem>
<para>
suppression des entités, dans le même ordre que celui des objets
correspondants qui ont été supprimés par l'appel à <literal>Session.delete()</literal>
</para>
</listitem>
</orderedlist>
<para>
(Une exception est que des objets utilisant la génération <literal>native</literal>
d'identifiants sont insérés lorsqu'ils sont sauvegardés.)
</para>
<para>
Excepté lorsque vous appelez <literal>flush()</literal> explicitement, il n'y
absolument aucune garantie à propos de <emphasis>quand</emphasis> la <literal>Session</literal>
exécute les appels JDBC, seulement sur l'<emphasis>ordre</emphasis> dans lequel ils sont
exécutés. Cependant, Hibernate garantit que <literal>Query.list(..)</literal> ne
retournera jamais de données périmées, ni des données fausses.
</para>
<para>
Il est possible de changer le comportement par défaut, donc que le flush se produise
moins fréquemment. La classe <literal>FlushMode</literal> définit trois modes différents :
flush seulement lors du commit (et seulement quand l'API <literal>Transaction</literal>
d'Hibernate est utilisée), flush automatiquement en utilisant la procédure expliquée, ou
jamais de flush à moins que <literal>flush()</literal> soit appelée explicitement.
Le dernier mode est utile pour l'exécution de longues unités de travail, où une
<literal>Session</literal> est gardée ouverte et déconnectée pour un long moment
(voir <xref linkend="transactions-optimistic-longsession"/>).
</para>
<programlisting><![CDATA[sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // permet aux requêtes de retourner un état périmé
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// pourrait retourner des données périmées
sess.find("from Cat as cat left outer join cat.kittens kitten");
// le changement pour izi n'est pas flushé !
...
tx.commit(); // le flush se produit]]></programlisting>
<para>
Durant le flush, une exception peut se produire (par exemple, si une opération de la
DML viole une contrainte). Puisque les exceptions de gestion impliquent une certaine
compréhension du comportement transactionnel d'Hibernate, nous le traitons dans
<xref linkend="transactions"/>.
</para>
</sect1>
<sect1 id="objectstate-transitive" revision="1">
<title>Persistance transitive</title>
<para>
Il est assez pénible de sauvegarder, supprimer, ou réattacher des objets
un par un, surtout si vous traitez un graphe d'objets associés. Un cas habituel
est une relation parent/enfant. Considérez l'exemple suivant :
</para>
<para>
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épendraient
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 détectera cela et, puisque les objets de type de valeur ne peuvent pas avoir
des références partagées, supprimera l'enfant de la base de données.
</para>
<para>
Maintenant considérez le même scénario avec un parent et 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 <emphasis>persistance par accessibilité</emphasis> par défaut.
</para>
<para>
Pour chaque opération basique de la session d'Hibernate - incluant <literal>persist(), merge(),
saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()</literal> - il y a un
style de cascade correspondant. Respectivement, les styles de cascade s'appellent <literal>persist,
merge, save-update, delete, lock, refresh, evict, replicate</literal>. Si vous voulez qu'une
opération soit cascadée le long d'une association, vous devez l'indiquer dans le document de
mapping. Par exemple :
</para>
<programlisting><![CDATA[<one-to-one name="person" cascade="persist"/>]]></programlisting>
<para>
Les styles de cascade peuvent être combinés :
</para>
<programlisting><![CDATA[<one-to-one name="person" cascade="persist,delete,lock"/>]]></programlisting>
<para>
Vous pouvez même utiliser <literal>cascade="all"</literal> pour spécifier que <emphasis>toutes</emphasis>
les opérations devraient être cascadées le long de l'association. La valeur par défaut
<literal>cascade="none"</literal> spécifie qu'aucune opération ne sera cascadée.
</para>
<para>
Une style de cascade spécial, <literal>delete-orphan</literal>, s'applique seulement
aux associations un-vers-plusieurs, et indique que l'opération <literal>delete()</literal>
devrait être appliquée à n'importe quel enfant qui est supprimé de l'association.
</para>
<para>
Recommandations :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Cela n'a généralement aucun sens d'activer la cascade sur une association
<literal>&lt;many-to-one&gt;</literal> ou <literal>&lt;many-to-many&gt;</literal>. Les
cascades sont souvent utiles pour des associations
<literal>&lt;one-to-one&gt;</literal> et <literal>&lt;one-to-many&gt;</literal>.
</para>
</listitem>
<listitem>
<para>
Si la durée de vie de l'objet enfant est liée à la durée de vie de l'objet parent,
faites en un <emphasis>objet du cycle de vie</emphasis> en spécifiant
<literal>cascade="all,delete-orphan"</literal>.
</para>
</listitem>
<listitem>
<para>
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
<literal>cascade="persist,merge,save-update"</literal>.
</para>
</listitem>
</itemizedlist>
<para>
Mapper une association (soit une simple association valuée, soit une collection) avec
<literal>cascade="all"</literal> marque l'association comme une relation de style
<emphasis>parent/enfant</emphasis> où la sauvegarde/mise à jour/suppression du parent
entraîne la sauvegarde/mise à jour/suppression de l'enfant ou des enfants.
</para>
<para>
En outre, une simple référence à un enfant d'un parent persistant aura pour conséquence
la sauvegarde/mise à jour de l'enfant. Cette métaphore est cependant incomplète. Un enfant
qui devient non référencé par son parent <emphasis>n'est pas</emphasis> automatiquement
supprimée, excepté dans le cas d'une association <literal>&lt;one-to-many&gt;</literal>
mappée avec <literal>cascade="delete-orphan"</literal>. La sémantique précise des opérations
de cascade pour une relation parent/enfant est la suivante :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Si un parent est passé à <literal>persist()</literal>, tous les enfant sont passés à
<literal>persist()</literal>
</para>
</listitem>
<listitem>
<para>
Si un parent est passé à <literal>merge()</literal>, tous les enfants sont passés à
<literal>merge()</literal>
</para>
</listitem>
<listitem>
<para>
Si un parent est passé à <literal>save()</literal>, <literal>update()</literal> ou
<literal>saveOrUpdate()</literal>, tous les enfants sont passés à <literal>saveOrUpdate()</literal>
</para>
</listitem>
<listitem>
<para>
Si un enfant détaché ou éphémère devient référencé par un parent persistant,
il est passé à <literal>saveOrUpdate()</literal>
</para>
</listitem>
<listitem>
<para>
Si un parent est supprimé, tous les enfants sont passés à <literal>delete()</literal>
</para>
</listitem>
<listitem>
<para>
Si un enfant est déréférencé par un parent persistant, <emphasis>rien de spécial
n'arrive</emphasis> - l'application devrait explicitement supprimer l'enfant si nécessaire -
à moins que <literal>cascade="delete-orphan"</literal> soit paramétré,
au quel cas l'enfant "orphelin" est supprimé.
</para>
</listitem>
</itemizedlist>
<para>
Enfin, la cascade des opérations peut être effectuée sur un graphe donné lors
de l'<emphasis>appel de l'opération</emphasis> or lors du <emphasis>flush</emphasis>
suivant. Toutes les opérations, lorsque cascadées, le sont sur toutes les entités
associées atteignables lorsque l'opétation est exécutée. Cependant
<literal>save-upate</literal> et <literal>delete-orphan</literal> sont cascadées
à toutes les entités associées atteignables lors du flush de la
<literal>Session</literal>.
</para>
</sect1>
<sect1 id="objectstate-metadata">
<title>Utilisation des méta-données</title>
<para>
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 d'Hibernate pour implémenter un algorithme
de copie en profondeur "intelligent" qui comprendrait quels objets devraient copiés
(par exemple les types de valeur mutables) et lesquels ne devraient pas l'être (par exemple
les types de valeurs immutables et, possiblement, les entités associées).
</para>
<para>
Hibernate expose les méta-données via les interfaces <literal>ClassMetadata</literal>
et <literal>CollectionMetadata</literal> et la hiérarchie <literal>Type</literal>.
Les instances des interfaces de méta-données peuvent être obtenues à partir de la
<literal>SessionFactory</literal>.
</para>
<programlisting><![CDATA[Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// récupère une Map de toutes les propriétés qui ne sont pas des collections ou des 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] );
}
}]]></programlisting>
</sect1>
</chapter>