1242 lines
56 KiB
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><hibernate-mapping></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><class></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><version></literal> ou
|
|
<literal><timestamp></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><many-to-one></literal> ou <literal><many-to-many></literal>. Les
|
|
cascades sont souvent utiles pour des associations
|
|
<literal><one-to-one></literal> et <literal><one-to-many></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><one-to-many></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>
|