update for 3.2 doc compliance

git-svn-id: https://svn.jboss.org/repos/hibernate/branches/Branch_3_2/Hibernate3/doc@10541 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Anthony Patricio 2006-10-02 18:27:05 +00:00
parent 76d5f33a31
commit ea0327c63d
12 changed files with 573 additions and 283 deletions

View File

@ -33,7 +33,7 @@
<bookinfo>
<title>HIBERNATE - Persistance relationnelle en Java standard</title>
<subtitle>Documentation de référence d'Hibernate</subtitle>
<releaseinfo>3.1final</releaseinfo>
<releaseinfo>3.2final</releaseinfo>
</bookinfo>
<toc/>

View File

@ -270,7 +270,7 @@
</para>
</sect1>
<sect1 id="architecture-current-session" revision="1">
<sect1 id="architecture-current-session" revision="2">
<title>Sessions Contextuelles</title>
<para>
Certaines applications utilisant Hibernate ont besoin d'une sorte de session "contextuelle", où
@ -320,6 +320,14 @@
<literal>org.hibernate.context.ThreadLocalSessionContext</literal> - les sessions
courantes sont associées au thread d'exécution. Voir les javadocs pour les détails.
</para>
</listitem>
<listitem>
<para>
<literal>org.hibernate.context.ManagedSessionContext</literal> - les sessions
courantes sont traquées par l'exécution du thread. Toutefois, vous êtes responsable
de lier et délier une instance de <literal>Session</literal> avec les méthodes
statiques de cette classes, qui n'ouvre, ne flush ou ne ferme jamais de <literal>Session</literal>.
</para>
</listitem>
</itemizedlist>

View File

@ -86,7 +86,8 @@
qui agissent sur le schéma de base de données exporté par l'outil de
génération de schéma. (Par exemple l'attribut <literal>not-null</literal>.)
</para>
<sect2 id="mapping-declaration-doctype" revision="2">
<sect2 id="mapping-declaration-doctype" revision="3">
<title>Doctype</title>
<para>
Tous les mappings XML devraient utiliser le doctype indiqué.
@ -96,6 +97,56 @@
des recherches de la DTD sur Internet, vérifiez votre déclaration de DTD par rapport
au contenu de votre classpath.
</para>
<sect3 id="mapping-declaration-entity-resolution">
<title>EntityResolver</title>
<para>
Comme cité précédemment, Hibernate tentera de trouver les DTDs d'abord dans son classpath. Il
réussit à faire cela en utilisant une implémentation particulière de <literal>org.xml.sax.EntityResolver</literal>
avec le SAXReader qu'il utilise pour lire les fichiers xml. Cet <literal>EntityResolver</literal> particulier
reconnait deux espaces de nommage systemId différents.
</para>
<itemizedlist>
<listitem>
<para>
un <literal>espace de nommage hibernate</literal> est reconnu dès qu'un systemId commence par
<literal>http://hibernate.sourceforge.net/</literal>; alors ces entités sont résolues via le
classloader qui a chargé les classes Hibernate.
</para>
</listitem>
<listitem>
<para>
un <literal>espace de nommage utilisateur</literal> est reconnu dès qu'un systemId utilise
un protocol URL <literal>classpath://</literal>. Le résolveur tentera de résoudre ces entités
via (1) le classloader du contexte du thread courant et (2) le classloader qui a chargé
les classes Hibernate.
</para>
</listitem>
</itemizedlist>
<para>
Un exemple d'utilisation de l'espace de nommage utilisateur:
</para>
<programlisting><![CDATA[<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping>]]></programlisting>
<para>
<literal>types.xml</literal> est une ressource dans le package <literal>your.domain</literal>
et qui contient un <xref linkend="mapping-types-custom">typedef</xref> particulier.
</para>
</sect3>
</sect2>
<sect2 id="mapping-declaration-mapping" revision="3">
<title>hibernate-mapping</title>
@ -1103,7 +1154,7 @@
utilisent des identifiants assignés ou des clefs composées !</emphasis>
</para>
</sect2>
<sect2 id="mapping-declaration-timestamp" revision="3" >
<sect2 id="mapping-declaration-timestamp" revision="4" >
<title>timestamp (optionnel)</title>
<para>
L'élément optionnel <literal>&lt;timestamp&gt;</literal> indique que la table contient des données
@ -1179,7 +1230,9 @@
</programlistingco>
<para>
Notez que <literal>&lt;timestamp&gt;</literal> est équivalent à
<literal>&lt;version type="timestamp"&gt;</literal>.
<literal>&lt;version type="timestamp"&gt;</literal> et
<literal>&lt;timestamp source="db"&gt;</literal> équivaut à
<literal>&lt;version type="dbtimestamp"&gt;</literal>.
</para>
</sect2>
<sect2 id="mapping-declaration-property" revision="4">

View File

@ -33,6 +33,12 @@ session.close();]]></programlisting>
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
<para id="disablebatching" revision="1">
Notez qu'Hibernate désactive, de manière transparente, l'insertion par paquet au
niveau JDBC si vous utilisez un générateur d'identifiant de type
<literal>identity</literal>.
</para>
<para>
Vous pourriez aussi vouloir faire cette sorte de travail dans un traitement où
l'interaction avec le cache de second niveau est complètement désactivé :
@ -150,7 +156,7 @@ session.close();]]></programlisting>
</sect1>
<sect1 id="batch-direct" revision="2">
<sect1 id="batch-direct" revision="3">
<title>Opérations de style DML</title>
<para>
@ -218,8 +224,32 @@ tx.commit();
session.close();]]></programlisting>
<para>
Pour exécuter un <literal>DELETE</literal> HQL, utilisez la même méthode
<literal>Query.executeUpdate()</literal> :
Par défaut, les statements HQL <literal>UPDATE</literal>, n'affectent pas la valeur des propriétés
<xref linkend="mapping-declaration-version">version</xref> ou
<xref linkend="mapping-declaration-timestamp">timestamp</xref>
pour les entités affectées; ceci est compatible avec la spec EJB3. Toutefois,
vous pouvez forcer Hibernate à mettre à jour les valeurs des propriétés
<literal>version</literal> ou <literal>timestamp</literal> en utilisant le <literal>versioned update</literal>.
Pour se faire, ajoutez le mot clé <literal>VERSIONED</literal> après le mot clé <literal>UPDATE</literal>.
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<para>
Notez que les types personnalisés (<literal>org.hibernate.usertype.UserVersionType</literal>)
ne sont pas supportés en conjonction avec le statement <literal>update versioned</literal> statement.
</para>
<para>
Pour exécuter un HQL <literal>DELETE</literal>, utilisez la même méthode<literal>Query.executeUpdate()</literal>:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();

View File

@ -938,7 +938,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</tgroup>
</table>
<table frame="topbot" id="configuration-misc-properties" revision="9">
<table frame="topbot" id="configuration-misc-properties" revision="10">
<title>Propriétés diverses</title>
<tgroup cols="2">
<colspec colname="c1" colwidth="1*"/>
@ -962,7 +962,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<para>
<emphasis role="strong">eg.</emphasis>
<literal>jta</literal> | <literal>thread</literal> |
<literal>custom.Class</literal>
<literal>managed</literal> | <literal>custom.Class</literal>
</para>
</entry>
</row>

View File

@ -8,7 +8,7 @@
fonctionnalités génériques, et d'extensions de fonctionnalités d'Hibernate.
</para>
<sect1 id="objectstate-interceptors" revision="2">
<sect1 id="objectstate-interceptors" revision="3">
<title>Intercepteurs</title>
<para>
@ -115,21 +115,32 @@ public class AuditInterceptor extends EmptyInterceptor {
}]]></programlisting>
<para>
L'intercepteur doit être spécifié quand une session est créée.
Il y a deux types d'intercepteurs: lié à la <literal>Session</literal> et
lié à la <literal>SessionFactory</literal>.
</para>
<para>
Un intercepteur lié à la <literal>Session</literal> est défini
lorsqu'une session est ouverte via l'invocation des méthodes surchargées SessionFactory.openSession()
acceptant un <literal>Interceptor</literal> (comme argument).
</para>
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
<para>
Vous pouvez aussi mettre un intercepteur au niveau global, en utilisant l'objet <literal>Configuration</literal>.
Dans ce cas, l'intercepteur doit être "threadsafe".
Un intercepteur lié a <literal>SessionFactory</literal> est défini avec l'objet <literal>Configuration</literal>
avant la construction de la <literal>SessionFactory</literal>. Dans ce cas, les intercepteurs fournis seront
appliqués à toutes les sessions ouvertes pour cette <literal>SessionFactory</literal>; ceci est vrai
à moins que la session ne soit ouverte en spécifiant l'intercepteur à utiliser.
Les intercepteurs liés à la <literal>SessionFactory</literal> doivent être thread safe, faire attention
à ne pas stocker des états spécifiques de la session puisque plusieurs sessions peuvent utiliser
l'intercepteur de manière concurrente.
</para>
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
</sect1>
<sect1 id="objectstate-events" revision="3">
<sect1 id="objectstate-events" revision="4">
<title>Système d'événements</title>
<para>

View File

@ -6,7 +6,7 @@
Les classes persistantes sont les classes d'une application qui implémentent
les entités d'un problème métier (ex. Client et Commande dans une application
de commerce électronique).
Toutes les instances d'une classe persistante ne sont pas forcément
Toutes les instances d'une classe persistante ne sont pas forcément
dans l'état persistant - au lieu de cela, une instance peut être éphémère (NdT : transient) ou détachée.
</para>
@ -116,7 +116,7 @@ public class Cat {
<para>
<literal>Cat</literal> a un constructeur sans argument. Toutes les classes persistantes doivent avoir un
constructeur par défaut (lequel peut ne pas être public) pour qu'Hibernate puissent les instancier en utilisant
<literal>Constructor.newInstance()</literal>. Nous recommandons fortement d'avoir un constructeur par défaut avec
<literal>Constructor.newInstance()</literal>. Nous recommandons fortement d'avoir un constructeur par défaut avec
au moins une visibilité <emphasis>paquet</emphasis> pour la génération du proxy à l'exécution dans Hibernate.
</para>
</sect2>
@ -148,7 +148,7 @@ public class Cat {
<itemizedlist spacing="compact">
<listitem>
<para>
Les réattachements transitifs pour les objets détachés (mise à jour en cascade ou fusion en cascade) -
Les réattachements transitifs pour les objets détachés (mise à jour en cascade ou fusion en cascade) -
voir <xref linkend="objectstate-transitive"/>
</para>
</listitem>
@ -270,13 +270,13 @@ public class DomesticCat extends Cat {
en comparant la valeur de l'identifiant des deux objets. Si cette valeur est identique, les deux
doivent représenter la même ligne de base de données, ils sont donc égaux (si les deux sont
ajoutés à un <literal>Set</literal>, nous n'aurons qu'un seul élément dans le
<literal>Set</literal>). Malheureusement, nous ne pouvons pas utiliser cette approche avec
<literal>Set</literal>). Malheureusement, nous ne pouvons pas utiliser cette approche avec
des identifiants générés ! Hibernate n'assignera de
valeur d'identifiant qu'aux objets qui sont persistants, une instance nouvellement créée n'aura
donc pas de valeur d'identifiant ! De plus, si une instance est non sauvegardée et actuellement dans un <literal>Set</literal>,
le sauvegarder assignera une valeur d'identifiant à l'objet. Si <literal>equals()</literal> et <literal>hashCode()</literal>
sont basées sur la valeur de l'identifiant, le code de hachage devrait changer, rompant le contrat du <literal>Set</literal>.
Regardez sur le site web d'Hibernate pour une discussion complète de ce problème.
sont basées sur la valeur de l'identifiant, le code de hachage devrait changer, rompant le contrat du <literal>Set</literal>.
Regardez sur le site web d'Hibernate pour une discussion complète de ce problème.
Notez que ceci n'est pas un problème d'Hibernate, mais la sémantique normale de Java pour l'identité d'un objet et l'égalité.
</para>
@ -314,7 +314,7 @@ public class DomesticCat extends Cat {
}]]></programlisting>
<para>
Notez qu'une clef métier ne doit pas être solide comme une clef primaire de base de données
Notez qu'une clef métier ne doit pas être solide comme une clef primaire de base de données
(voir <xref linkend="transactions-basics-identity"/>). Les propriétés
immuables ou uniques sont généralement de bonnes candidates pour une clef métier.
</para>
@ -461,7 +461,7 @@ dynamicSession.close()
</sect1>
<sect1 id="persistent-classes-tuplizers" revision="0">
<sect1 id="persistent-classes-tuplizers" revision="1">
<title>Tuplizers</title>
<para>
@ -473,7 +473,7 @@ dynamicSession.close()
une telle structure de données. Par exemple, pour le mode d'entité POJO, le tuplizer correspondant
sait comment créer le POJO à travers son constructeur et comment accéder aux propriétés du POJO
utilisant les accesseurs de la propriété définie. Il y a deux types de Tuplizers haut niveau,
représenté par les interfaces <literal>org.hibernate.tuple.EntityTuplizer</literal> et
représentés par les interfaces <literal>org.hibernate.tuple.EntityTuplizer</literal> et
<literal>org.hibernate.tuple.ComponentTuplizer</literal>. Les <literal>EntityTuplizer</literal>s
sont responsables de la gestion des contrats mentionnés ci-dessus pour les entités, alors que
les <literal>ComponentTuplizer</literal>s s'occupent des composants.
@ -509,7 +509,7 @@ dynamicSession.close()
public class CustomMapTuplizerImpl
extends org.hibernate.tuple.DynamicMapEntityTuplizer {
extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
// override the buildInstantiator() method to plug in our custom map...
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {

View File

@ -42,8 +42,8 @@
<programlisting><![CDATA[from eg.Cat]]></programlisting>
<para>
qui retourne simplement toutes les instances de la classe <literal>eg.Cat</literal>.
Nous n'avons pas besoin d'habitude de qualifier le nom de la classe,
qui retourne simplement toutes les instances de la classe <literal>eg.Cat</literal>.
Nous n'avons pas besoin d'habitude de qualifier le nom de la classe,
puisque <literal>auto-import</literal> est la valeur par défaut. Donc nous écrivons presque toujours :
</para>
@ -80,7 +80,7 @@
</sect1>
<sect1 id="queryhql-joins" revision="1">
<sect1 id="queryhql-joins" revision="2">
<title>Associations et jointures</title>
<para>
@ -158,7 +158,7 @@
Une jointure "fetchée" (rapportée) n'a généralement pas besoin de se voir assigner
un alias puisque les objets associés n'ont pas à être utilisés dans les autres clauses.
Notez aussi que les objets associés ne sont pas retournés directement dans le résultat de
la requête mais l'on peut y accéder via l'objet parent. La seule raison pour laquelle nous
la requête mais l'on peut y accéder via l'objet parent. La seule raison pour laquelle nous
pourrions avoir besoin d'un alias est si nous récupérions récursivement une collection supplémentaire :
</para>
@ -171,17 +171,18 @@
Notez que la construction de <literal>fetch</literal> ne peut pas être utilisée dans les requêtes appelées par
<literal>scroll()</literal> ou <literal>iterate()</literal>.
<literal>fetch</literal> ne devrait pas non plus être utilisé avec <literal>setMaxResults()</literal> ou
<literal>setFirstResult()</literal>. <literal>fetch</literal> ne peut pas non plus être utilisé avec une
condition <literal>with</literal> ad hoc. Il est
possible de créer un produit cartésien par jointure en récupérant plus d'une collection dans une requête,
donc faites attention dans ce cas. Récupérer par jointure de multiples collections donne aussi parfois
<literal>setFirstResult()</literal>, ces opérations étant basées sur le nombre de résultats qui contient
généralement des doublons dès que des collections sont chargées.
<literal>fetch</literal> ne peut pas non plus être utilisé avec une condition <literal>with</literal> ad hoc. Il est
possible de créer un produit cartésien par jointure en récupérant plus d'une collection dans une requête,
donc faites attention dans ce cas. Récupérer par jointure de multiples collections donne aussi parfois
des résultats inattendus pour des mappings de bag, donc soyez prudent lorsque vous formulez vos requêtes dans de tels cas.
Finalement, notez que <literal>full join fetch</literal> et <literal>right join fetch</literal> ne sont pas utiles en général.
</para>
<para>
Si vous utilisez un chargement retardé pour les propriétés (avec une instrumentation par bytecode), il est possible
de forcer Hibernate à récupérer les propriétés non encore chargées immédiatement (dans la première requête)
Si vous utilisez un chargement retardé pour les propriétés (avec une instrumentation par bytecode), il est possible
de forcer Hibernate à récupérer les propriétés non encore chargées immédiatement (dans la première requête)
en utilisant <literal>fetch all properties</literal>.
</para>
@ -189,7 +190,7 @@
<programlisting><![CDATA[from Document doc fetch all properties where lower(doc.name) like '%cats%']]></programlisting>
</sect1>
<sect1 id="queryhql-joins-forms">
<title>Formes de syntaxes pour les jointures</title>
@ -198,14 +199,14 @@
</para>
<para>
Les requêtes présentes dans la section précédente utilisent la forme <literal>explicite</literal>
Les requêtes présentes dans la section précédente utilisent la forme <literal>explicite</literal>
où le mode clé join est explicitement utilisé dans la clause from. C'est la forme recommandée.
</para>
<para>
La forme <literal>implicite</literal> n'utilise pas le mot clé join.
A la place, les associations sont "déréférencées" en utilisant le notation '.'. Ces
jointures peuvent apparaitre dans toutes les clauses. Les jointures <literal>implicites</literal>
jointures peuvent apparaitre dans toutes les clauses. Les jointures <literal>implicites</literal>
résultent en des inner join dans le SQL généré.
</para>
@ -398,7 +399,7 @@ select count(distinct cat.name), count(cat) from Cat cat]]></programlisting>
<title>La clause where</title>
<para>
La clause <literal>where</literal> vous permet de réduire la liste des instances retournées.
La clause <literal>where</literal> vous permet de réduire la liste des instances retournées.
Si aucun alias n'existe, vous pouvez vous référer aux propriétés par leur nom :
</para>
@ -614,7 +615,7 @@ where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting
<listitem>
<para>
Les fonctions HQL qui s'appliquent expressions représentant des collections : <literal>size(),
minelement(), maxelement(), minindex(), maxindex()</literal>, ainsi que les fonctions spéciales <literal>elements()</literal>
minelement(), maxelement(), minindex(), maxindex()</literal>, ainsi que les fonctions spéciales <literal>elements()</literal>
et <literal>indices</literal> qui peuvent être quantifiées en utilisant <literal>some, all, exists, any, in</literal>.
</para>
</listitem>
@ -822,11 +823,11 @@ order by cat.name asc, cat.weight desc, cat.birthdate]]></programlisting>
</para>
</sect1>
<sect1 id="queryhql-grouping">
<sect1 id="queryhql-grouping" revision="1">
<title>La clause group by</title>
<para>
Si la requête retourne des valeurs aggrégées, celles ci peuvent être groupées par propriété ou composant :
Si la requête retourne des valeurs aggrégées, celles-ci peuvent être groupées par propriété d'une classe retournée ou par composant :
</para>
<programlisting><![CDATA[select cat.color, sum(cat.weight), count(cat)
@ -847,20 +848,24 @@ group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)]]></programlisting>
<para>
Les fonctions SQL et les fonctions d'aggrégations sont permises dans les clauses <literal>having</literal>
et <literal>order by</literal>, si elles sont supportées par la base de données (ce que ne fait pas MySQL par exemple).
Les fonctions SQL et les fonctions d'agrégat sont permises dans les clauses <literal>having</literal>
et <literal>order by</literal>, si elles sont prises en charge par la base de données (ce que ne fait pas MySQL par exemple).
</para>
<programlisting><![CDATA[select cat
from Cat cat
join cat.kittens kitten
group by cat
group by cat.id, cat.name, cat.other, cat.properties
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc]]></programlisting>
<para>
Notez que ni la clause <literal>group by</literal> ni la clause
<literal>order by</literal> ne peuvent contenir d'expressions arithmétiques.
Notez aussi qu'Hibernate ne développe pas une entité faisant partie du regroupement,
donc vous ne pouvez pas écrire <literal>group by cat</literal> si toutes
les propriétés de <literal>cat</literal> sont non-agrégées. Vous devez
lister toutes les propriétés non-agrégées explicitement.
</para>
</sect1>
@ -912,7 +917,7 @@ where not ( cat.name, cat.color ) in (
)]]></programlisting>
<para>
Notez que sur certaines bases de données (mais par Oracle ou HSQL), vous pouvez utiliser des constructeurs de tuples
Notez que sur certaines bases de données (mais par Oracle ou HSQL), vous pouvez utiliser des constructeurs de tuples
dans d'autres contextes, par exemple lors du requêtage de composants ou de types utilisateur composites :
</para>

View File

@ -13,7 +13,7 @@
<para>Hibernate3 vous permet de spécifier du SQL écrit à la main (incluant les procédures stockées)
pour toutes les opérations de création, mise à jour, suppression et chargement.</para>
<sect1 id="querysql-creating" revision="3">
<sect1 id="querysql-creating" revision="4">
<title>Utiliser une <literal>SQLQuery</literal></title>
<para>L'exécution des requêtes en SQL natif est contrôlée par l'interface <literal>SQLQuery</literal>,
@ -21,224 +21,369 @@
Dans des cas extrêmement simples, nous pouvons utiliser la forme suivante :
</para>
<programlisting>List cats = sess.createSQLQuery("select * from cats")
.addEntity(Cat.class)
.list();</programlisting>
<sect2>
<title>Requêtes scalaires</title>
<para>Cette requête a spécifié :</para>
<para>La requête SQL la plus basique permet de récupérer une liste de (valeurs) scalaires.</para>
<itemizedlist>
<listitem>
<para>la requête SQL</para>
</listitem>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
]]></programlisting>
<listitem>
<para>l'entité retournée par la requête</para>
</listitem>
</itemizedlist>
<para>Ces deux requêtes retourneront un tableau d'objets (Object[]) avec
les valeurs scalaires de chacune des colonnes de la table CATS.
Hibernate utilisera le ResultSetMetadata pour déduire l'ordre et le type
des valeurs scalaires retournées.</para>
<para>
Ici, les noms de colonne des résultats sont supposés être les mêmes que les noms de colonne spécifiés dans le
document de mapping. Cela peut être problématique pour des requêtes SQL qui joignent de multiple tables, puisque
les mêmes noms de colonne peuvent apparaître dans plus d'une table. La forme suivante n'est pas vulnérable à la
duplication des noms de colonne :
</para>
<para>Pour éviter l'overhead lié à <literal>ResultSetMetadata</literal> ou simplement pour
être plus explicite dans ce qui est retourné, vous pouvez utiliser <literal>addScalar()</literal>.</para>
<programlisting>List cats = sess.createSQLQuery("select {cat.*} from cats cat")
.addEntity("cat", Cat.class)
.list();</programlisting>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
]]></programlisting>
<para>Cette requête a spécifié :</para>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>la requête SQL, avec un paramètre fictif pour Hibernate pour injecter les alias de colonne</para>
</listitem>
<itemizedlist>
<listitem>
<para>la chaîne de caractère SQL</para>
</listitem>
<listitem>
<para>l'entité retournée par la requête, et son alias de table SQL</para>
</listitem>
</itemizedlist>
<listitem>
<para>les colonnes et les types retournés</para>
</listitem>
</itemizedlist>
<para>
La méthode <literal>addEntity()</literal> associe l'alias de la table SQL
avec la classe de l'entité retournée, et détermine la forme de l'ensemble des résultats de la requête.
</para>
<para>Cela retournera toujours un tableau d'objets, mais sans utiliser le
<literal>ResultSetMetdata</literal>, mais récupèrera explicitement les colonnes
ID, NAME and BIRTHDATE column étant de respectivement de type Long, String et Short,
depuis le resultset sous jacent. Cela signifie aussi que seules ces colonnes seront
retournées même si la requête utilise <literal>*</literal>
et aurait pu retourner plus que les trois colonnes listées.</para>
<para>
La méthode <literal>addJoin()</literal> peut être utilisée pour charger des associations vers d'autres
entités et collections.
</para>
<para>Il est possible de ne pas définir l'information sur le type pour toutes ou partie
des calaires.</para>
<programlisting>List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.addEntity("cat", Cat.class)
.addJoin("kitten", "cat.kittens")
.list();</programlisting>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
]]></programlisting>
<para>
Une requête SQL native pourrait retourner une simple valeur scalaire ou une combinaison de scalaires et d'entités.
</para>
<para>Il s'agit essentiellement de la même requête que précédemment, mais
le <literal>ResultSetMetaData</literal> est utilisé pour décider des types de NAME
et BIRTHDATE alors que le type de ID est explicitement spécifié.</para>
<programlisting>Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();</programlisting>
<para>Les java.sql.Types retournés par le ResultSetMetaData sont mappés aux type Hibernate
via le Dialect. Si un type spécifique n'est pas mappé ou est mappé à un type non souhaité, il
est possible de personnaliser en invoquant <literal>registerHibernateType</literal> dans
le Dialect.</para>
</sect2>
<para>Vous pouvez alternativement décrire les informations de mapping des résultats dans vos fichiers hbm
et les utiliser pour vos requêtes.</para>
<sect2>
<title>Requêtes d'entités</title>
<programlisting>List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();</programlisting>
</sect1>
<para>Les requêtes précédentes ne retournaient que des valeurs scalaires,
retournant basiquement que les valeurs brutes du resultset. Ce qui suit montre
comment récupérer des entités depuis une requête native SQL, grâce à
<literal>addEntity()</literal>.</para>
<sect1 id="querysql-aliasreferences">
<title>Alias et références de propriété</title>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
]]></programlisting>
<para>
La notation <literal>{cat.*}</literal> utilisée au-dessus est un raccourci pour "toutes les propriétés".
Alternativement, vous pouvez lister explicitement les colonnes, mais même ce cas que nous laissons à Hibernate
injecte des alias de colonne SQL pour chaque propriété. Le remplaçant pour un alias de colonne
est juste le nom de la propriété qualifié par l'alias de la table.
Dans l'exemple suivant, nous récupérons des <literal>Cat</literal>s à partir d'une table différente
(<literal>cat_log</literal>) de celle déclarée dans les méta-données de mapping.
Notez que nous pouvons même utiliser les alias de propriété dans la clause "where" si nous le souhaitons.
</para>
<para>Cette requête spécifie:</para>
<para>
La syntaxe <literal>{}</literal> <emphasis>n'est pas</emphasis> requise pour le requêtes nommées. Voir
<xref linkend="querysql-namedqueries" />.
</para>
<itemizedlist>
<listitem>
<para>La chaîne de caractère de requête SQL</para>
</listitem>
<programlisting>String sql = "select cat.originalId as {cat.id}, " +
"cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +
"cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +
"from cat_log cat where {cat.mate} = :catId"
<listitem>
<para>L'entité retournée par la requête</para>
</listitem>
</itemizedlist>
<para>Avec Cat mappé comme classe avec les colonnes ID, NAME
et BIRTHDATE, les requêtes précédentes retournent toutes deux une liste
où chaque élément est une entité Cat.</para>
<para>Si l'entité est mappée avec un <literal>many-to-one</literal> vers
une autre entité, il est requis de retourner aussi cette entité en exécutant
la requête native, sinon une erreur "column not found" spécifique à la base de
données sera soulevée. Les colonnes additionnelles seront automatiquement
retournées en utilisant la notation *, mais nous préférons être explicites
comme dans l'exemple suivant avec le <literal>many-to-one</literal> vers
<literal>Dog</literal>:</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
]]></programlisting>
<para>Ceci permet à cat.getDog() de fonctionner normalement.</para>
</sect2>
<sect2>
<title>Gérer les associations et collections</title>
<para>Il est possible de charger agressivement <literal>Dog</literal> pour
éviter le chargement de proxy qui signifie aller retour supplémentaire vers
la base de données. Ceci est faisable via la méthode <literal>addJoin()</literal>,
qui vous permet de joindre une association ou collection.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dog");
]]></programlisting>
<para>Dans cet exemple, les <literal>Cat</literal> retournés auront leur
propriété <literal>dog</literal> entièrement initialisées sans aucun aller/retour
supplémentaire vers la base de données. Notez que nous avons ajouté un alias
("cat") pour être capable de spécifier la propriété cible de la jointure.
Il est possible de faire la même jointure aggressive pour les collections, e.g. si le
<literal>Cat</literal> a un one-to-many vers <literal>Dog</literal>.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID")
.addEntity("cat", Cat.class)
.addJoin("cat.dogs");
]]></programlisting>
<p>Nous arrivons aux limites de ce qui est possible avec les requêtes natives
sans les modifier pour les rendre utilisables par Hibernate; les problèmes
surviennent lorsque nous essayons de retourner des entités du même type ou
lorsque les alias/colonnes par défaut ne sont plus suffisants..</p>
</sect2>
<sect2>
<title>Retour d'entités multiples</title>
<para>Jusqu'à présent, les colonnes du resultset sont supposées être les mêmes
que les colonnes spécifiées dans les fichiers de mapping. Ceci peut
être problématique pour les requêtes SQL qui effectuent de multiples
jointures vers différentes tables, puisque les mêmes colonnes peuvent
apparaître dans plus d'une table.</para>
<para>L'injection d'alias de colonne est requis pour la requête suivante
(qui risque de ne pas fonctionner):</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
]]></programlisting>
<para>Le but de cette requête est de retourner deux instances de Cat par ligne,
un chat et sa mère. Cela échouera puisqu'il y a conflit de nom puisqu'ils sont
mappés au même nom de colonne et que sur certaines base de données, les alias
de colonnes retournés seront plutôt de la forme
"c.ID", "c.NAME", etc. qui ne sont pas égaux aux colonnes spécifiées dans les
mappings ("ID" and "NAME").</para>
<para>La forme suivante n'est pas vulnérable à la duplication des noms de colonnes:</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT {cat.*}, {mother.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class)
]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>la requête SQL, avec des réceptacles pour qu'Hibernate injecte les alias de colonnes</para>
</listitem>
<listitem>
<para>les entités retournés par la requête</para>
</listitem>
</itemizedlist>
<para>Les notations {cat.*} et {mother.*} utilisées sont un équivalent à 'toutes les propriétés'.
Alternativement, vous pouvez lister les colonnes explicitement, mais même pour ce cas, nous
laissons Hibernate injecter les alias de colonne pour chaque propriété.
Le réceptable pour un alias de colonne est simplement le nom de la propriété
qualifié par l'alias de la table. Dans l'exemple suivant, nous récupérons
les chats et leur mère depuis une table différentes (cat_log) de celle déclarée
dans les mappings. Notez que nous pouvons aussi utiliser les alias de propriété
dans la clause where si nous le voulons.</para>
<programlisting><![CDATA[String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
"BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
"FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
List loggedCats = sess.createSQLQuery(sql)
.addEntity("cat", Cat.class)
.setLong("catId", catId)
.list();</programlisting>
.addEntity("cat", Cat.class)
.addEntity("mother", Cat.class).list()
]]></programlisting>
<sect3 id="querysql-aliasreferences" revision="2">
<title>Références d'alias et de propriété</title>
<para>
<emphasis>À noter :</emphasis> si vous listez chaque propriété explicitement, vous devez inclure
toutes les propriétés de la classe <emphasis>et ses sous-classes</emphasis> !
</para>
<para>Pour la plupart des cas précédents, l'injection d'alias est requis,
mais pour les requêtes relatives à des mappings plus complexes, comme
les propriétés composite, les discriminants d'héritage, les collections etc., il
y a des alias spécifiques à utiliser pour permettre à Hibernate l'injection
des bons alias.</para>
<para>
La table suivante montre les différentes possibilités d'utilisation de l'injection d'alias. À noter : les noms
des alias dans le résultat sont des exemples, chaque alias aura un nom unique et probablement différent lors de l'utilisation.
</para>
<para>Le tableau suivant montre les diverses possiblités d'utilisation
d'injection d'alias. Note: les noms d'alias dans le résultat sont des
exemples, chaque alias aura un nom unique et probablement différent lorsqu'ils
seront utilisés.</para>
<table frame="topbot" id="aliasinjection-summary">
<title>Noms d'injection d'alias</title>
<table frame="topbot" id="aliasinjection-summary">
<title>Nom d'injection d'alias</title>
<tgroup cols="4">
<colspec colwidth="1*" />
<tgroup cols="3">
<colspec colwidth="1*" />
<colspec colwidth="1*" />
<colspec colwidth="1*" />
<colspec colwidth="2.5*" />
<colspec colwidth="2.5*" />
<thead>
<row>
<entry>Description</entry>
<thead>
<row>
<entry>Description</entry>
<entry>Syntaxe</entry>
<entry>Syntaxe</entry>
<entry>Exemple</entry>
</row>
</thead>
<entry>Exemple</entry>
</row>
</thead>
<tbody>
<row>
<entry>Une simple propriété</entry>
<tbody>
<row>
<entry>Une propriété simple</entry>
<entry><literal>{[aliasname].[propertyname]}</literal></entry>
<entry><literal>{[aliasname].[propertyname]</literal></entry>
<entry><literal>A_NAME as {item.name}</literal></entry>
</row>
<entry><literal>A_NAME as {item.name}</literal></entry>
</row>
<row>
<entry>Une propriété composée</entry>
<row>
<entry>Une propriété composite</entry>
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
<entry><literal>CURRENCY as {item.amount.currency}, VALUE as
{item.amount.value}</literal></entry>
</row>
<entry><literal>CURRENCY as {item.amount.currency}, VALUE as
{item.amount.value}</literal></entry>
</row>
<row>
<entry>Discriminant d'une entité</entry>
<row>
<entry>Discriminateur d'une entité</entry>
<entry><literal>{[aliasname].class}</literal></entry>
<entry><literal>{[aliasname].class}</literal></entry>
<entry><literal>DISC as {item.class}</literal></entry>
</row>
<entry><literal>DISC as {item.class}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'une entité</entry>
<row>
<entry>Toutes les propriétés d'une entité</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{item.*}</literal></entry>
</row>
<entry><literal>{item.*}</literal></entry>
</row>
<row>
<entry>Une clef de collection</entry>
<row>
<entry>La clé d'une collection</entry>
<entry><literal>{[aliasname].key}</literal></entry>
<entry><literal>{[aliasname].key}</literal></entry>
<entry><literal>ORGID as {coll.key}</literal></entry>
</row>
<entry><literal>ORGID as {coll.key}</literal></entry>
</row>
<row>
<entry>L'identifiant d'une collection</entry>
<row>
<entry>L'id d'une collection</entry>
<entry><literal>{[aliasname].id}</literal></entry>
<entry><literal>{[aliasname].id}</literal></entry>
<entry><literal>EMPID as {coll.id}</literal></entry>
</row>
<entry><literal>EMPID as {coll.id}</literal></entry>
</row>
<row>
<entry>L'élément d'une collection</entry>
<row>
<entry>L'élément d'une collection</entry>
<entry><literal>{[aliasname].element}</literal></entry>
<entry><literal>{[aliasname].element}</literal></entry>
<entry><literal>XID as {coll.element}</literal></entry>
<entry><literal>XID as {coll.element}</literal></entry>
</row>
<entry></entry>
</row>
<row>
<entry>Propriété d'un élément de collection</entry>
<row>
<entry>Propriété de l'élément dans la collection</entry>
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
<entry><literal>NAME as {coll.element.name}</literal></entry>
</row>
<entry><literal>NAME as {coll.element.name}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'un élément de collection</entry>
<row>
<entry>Toutes les propriétés de l'élément dans la collection</entry>
<entry><literal>{[aliasname].element.*}</literal></entry>
<entry><literal>{[aliasname].element.*}</literal></entry>
<entry><literal>{coll.element.*}</literal></entry>
</row>
<entry><literal>{coll.element.*}</literal></entry>
</row>
<row>
<entry>Toutes les propriétés d'une collection</entry>
<row>
<entry>Toutes les propriétés de la collection</entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{[aliasname].*}</literal></entry>
<entry><literal>{coll.*}</literal></entry>
</row>
</tbody>
</tgroup>
</table>
</sect3>
</sect2>
<sect2>
<title>Retour d'objet n'étant pas des entités</title>
<para>Il est possible d'appliquer un ResultTransformer à une requête native SQL. Ce qui permet, par exemple, de
retourner des entités non gérées.</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>une requête SQL</para>
</listitem>
<listitem>
<para>un transformateur de résultat</para>
</listitem>
</itemizedlist>
<para>
La requête précédente retournera une liste de <literal>CatDTO</literal> qui auront été instanciés
et dans lesquelles les valeurs de NAME et BIRTHNAME auront été injectées dans les propriétés ou champs
correspondants.
</para>
</sect2>
<sect2>
<title>Gérer l'héritage</title>
<para>Les requêtes natives SQL pour les entités prenant part à un héritage
doivent inclure toutes les propriétés de la classe de base et de toutes
ses sous classes.</para>
</sect2>
<sect2>
<title>Paramètres</title>
<para>Les requêtes natives SQL supportent aussi les paramètres nommés:</para>
<programlisting><![CDATA[Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
List pusList = query.setString(0, "Pus%").list();
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);
List pusList = query.setString("name", "Pus%").list(); ]]></programlisting>
</sect2>
<entry><literal>{coll.*}</literal></entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="querysql-namedqueries" revision="3">

View File

@ -223,7 +223,7 @@ sess.refresh(cat); //re-read the state (after the trigger executes)]]></programl
objets.
</para>
<sect2 id="objectstate-querying-executing">
<sect2 id="objectstate-querying-executing" revision="1">
<title>Exécution de requêtes</title>
<para>
@ -251,8 +251,11 @@ List kittens = session.createQuery(
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]></programlisting>
.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.
@ -447,7 +450,7 @@ cats.close()]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-named">
<sect3 id="objectstate-querying-executing-named" revision="1">
<title>Externaliser des requêtes nommées</title>
<para>
@ -476,6 +479,15 @@ List cats = q.list();]]></programlisting>
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>

View File

@ -482,7 +482,7 @@ catch (RuntimeException e) {
</sect2>
<sect2 id="transactions-demarcation-jta" revision="2">
<sect2 id="transactions-demarcation-jta" revision="3">
<title>Utilisation de JTA</title>
<para>Si votre couche de persistance s'exécute dans un serveur d'application (par exemple, derrière un EJB
@ -514,19 +514,26 @@ finally {
sess.close();
}]]></programlisting>
<para>Ou encore, avec la gestion automatique de contexte:</para>
<para>
Si vous souhaitez utiliser une <literal>Session</literal> couplée à la transaction, c'est à dire, utiliser
la fonctionnalité <literal>getCurrentSession()</literal> pour la propagation facile du contexte,
vous devrez utiliser l'API JTA <literal>UserTransaction</literal> directement:
</para>
<programlisting><![CDATA[// BMT idiom with getCurrentSession()
try {
factory.getCurrentSession().beginTransaction();
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
// do some work
...
tx.begin();
factory.getCurrentSession().getTransaction().commit();
// Do some work on Session bound to transaction
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
tx.rollback();
throw e; // or display error message
}]]></programlisting>

View File

@ -18,7 +18,7 @@
les bibliothèques tierces que nous nommons sont pour les JDK 1.4 et 5.0. Vous
pourriez avoir besoin d'autres bibliothèques pour le JDK 1.3.
</para>
<para>
Le code source de ce tutoriel est inclus dans la distribution dans le répertoire
<literal>doc/reference/tutorial/</literal>.
@ -26,7 +26,7 @@
</sect1>
<sect1 id="tutorial-firstapp" revision="1">
<sect1 id="tutorial-firstapp" revision="2">
<title>Partie 1 - Première application Hibernate</title>
<para>
@ -339,7 +339,7 @@ public class Event {
</sect2>
<sect2 id="tutorial-firstapp-configuration" revision="1">
<sect2 id="tutorial-firstapp-configuration" revision="2">
<title>Configuration d'Hibernate</title>
<para>
@ -351,14 +351,14 @@ public class Event {
</para>
<para>
Créez un répertoire appelé <literal>data</literal> à la racine du répertoire de développement -
Créez un répertoire appelé <literal>data</literal> à la racine du répertoire de développement -
c'est là que HSQL DB stockera ses fichiers de données. Démarrez maintenant votre base de données
en exécutant <literal>java -classpath lib/hsqldb.jar org.hsqldb.Server</literal> dans votre répertoire de travail.
en exécutant <literal>java -classpath ../lib/hsqldb.jar org.hsqldb.Server</literal> dans votre répertoire de données.
Vous observez qu'elle démarre et ouvre une socket TCP/IP, c'est là que notre application
se connectera plus tard. Si vous souhaitez démarrez à partir d'une nouvelle base de données
pour ce tutoriel (faites <literal>CTRL + C</literal> dans la fenêtre the window), effacez
le répertoire <literal>data/</literal> et redémarrez HSQL DB à nouveau.
</para>
<para>
@ -533,7 +533,7 @@ Total time: 1 second ]]></programlisting>
<para>
Nous créerons une classe d'aide <literal>HibernateUtil</literal> qui s'occupe du
démarrage et rend la gestion des <literal>Session</literal>s plus facile.
démarrage et rend la gestion des <literal>Session</literal>s plus facile.
Regardons l'implémentation :
</para>
@ -614,7 +614,7 @@ build.xml]]></programlisting>
</sect2>
<sect2 id="tutorial-firstapp-workingpersistence" revision="4">
<sect2 id="tutorial-firstapp-workingpersistence" revision="5">
<title>Charger et stocker des objets</title>
<para>
@ -655,7 +655,9 @@ public class EventManager {
session.save(theEvent);
session.getTransaction().commit();
}]]></programlisting>
}
}]]></programlisting>
<para>
Nous créons un nouvel objet <literal>Event</literal>, et le remettons à Hibernate.
@ -675,21 +677,38 @@ public class EventManager {
<para>
Que fait <literal>sessionFactory.getCurrentSession()</literal> ? Premièrement, vous pouvez
l'invoquer autant de fois que vous le voulez et n'importe où du moment que vous avez votre
<literal>SessionFactory</literal> (facile grâce à <literal>HibernateUtil</literal>).
<literal>SessionFactory</literal> (facile grâce à <literal>HibernateUtil</literal>).
La méthode <literal>getCurrentSession()</literal> renvoie toujours l'unité de travail courante.
Souvenez vous que nous avons basculé notre option de configuration au mécanisme basé sur le "thread"
dans <literal>hibernate.cfg.xml</literal>. Par conséquent, le scope de l'unité de travail
courante est le thread java courant d'exécution. Ceci n'est pas totalement vrai. Une
<literal>Session</literal> commence lorsqu'elle est vraiment utilisée la première fois,
Lorsque nous appelons pour la première fois <literal>getCurrentSession()</literal>.
Ensuite, elle est liée, par Hibernate, au thread courant. Lorsque la transaction s'achève
(commit ou rollback), Hibernate délie la <literal>Session</literal> du thread et la ferme
pour vous. Si vous invoquez <literal>getCurrentSession()</literal> une autre fois, vous obtenez
une nouvelle <literal>Session</literal> et pouvez entamer une nouvelle unité de travail.
Ce modèle de programmation "<emphasis>thread-bound</emphasis>" est le moyen le plus
populaire d'utiliser Hibernate.
dans <literal>hibernate.cfg.xml</literal>. Par conséquent, l'unité de travail courante est liée
au thread Java courant qui exécute notre application. Cependant, ce n'est pas tout, vous devez
aussi considérer le scope, quand une unité de travail commence et quand elle finit.
</para>
<para>
Une <literal>Session</literal> commence lorsqu'elle est vraiment utilisée la première fois,
lorsque nous appelons <literal>getCurrentSession()</literal> pour la première fois.
Ensuite, elle est attachée par Hibernate au thread courant. Lorsque la transaction s'achève, par
commit ou par rollback, Hibernate détache automatiquement la <literal>Session</literal> du thread et la ferme
pour vous. Si vous invoquez <literal>getCurrentSession()</literal> une nouvelle fois, vous obtenez
une nouvelle <literal>Session</literal> et pouvez entamer une nouvelle unité de travail.
Ce modèle de programmation "<emphasis>thread-bound</emphasis>" est le moyen le plus
populaire d'utiliser Hibernate, puisqu'il permet un découpage flexible de votre code (le code délimitant
les transactions peut être séparé du code accédant aux données, nous verrons cela plus loin dans ce tutorial).
</para>
<para>
A propos du scope de l'unité de travail, la <literal>Session</literal> Hibernate devrait-elle
être utilisée pour exécuter une ou plusieurs opérations en base de données ? L'exemple ci-dessus
utilise une <literal>Session</literal> pour une opération. C'est une pure coïncidence,
l'exemple est n'est seulement pas assez complexe pour montrer d'autres approches. Le scope d'une
<literal>Session</literal> Hibernate est flexible mais vous ne devriez jamais concevoir
votre application de manière à utiliser une nouvelle <literal>Session</literal> Hibernate pour
<emphasis>chaque</emphasis> opération en base de données. Donc même si vous le voyez quelques fois
dans les exemples (très simplistes) suivants, considérez <emphasis>une session par operation</emphasis>
comme un anti-pattern. Une véritable application (web) est montrée plus loin dans ce tutorial.
</para>
<para>
Lisez <xref linkend="transactions"/> pour plus d'informations sur la gestion des transactions et leur démarcations.
Nous n'avons pas géré les erreurs et rollback sur l'exemple précédent.
@ -836,7 +855,7 @@ public class Person {
}]]></programlisting>
<para>
Créez un nouveau fichier de mapping appelé <literal>Person.hbm.xml</literal>
Créez un nouveau fichier de mapping appelé <literal>Person.hbm.xml</literal>
(n'oubliez pas la référence à la DTD)
</para>
@ -868,7 +887,7 @@ public class Person {
</sect2>
<sect2 id="tutorial-associations-unidirset" revision="2">
<sect2 id="tutorial-associations-unidirset" revision="3">
<title>Une association unidirectionnelle basée sur Set</title>
<para>
@ -906,7 +925,7 @@ public class Person {
Ce n'est pas nécessaire d'un point de vue fonctionnel. Vous pourrez toujours exécuter une requête
explicite pour récupérer les participants d'un "event" particulier. Ce choix de conception
vous est laissé, mais ce qui reste certains est la cardinalité de l'association: "plusieurs"
des deux côtés, nous appelons cela une association <emphasis>many-to-many</emphasis>.
des deux côtés, nous appelons cela une association <emphasis>many-to-many</emphasis>.
Par conséquent nous utilisons un mapping Hibernate many-to-many:
</para>
@ -920,7 +939,7 @@ public class Person {
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
<many-to-many column="EVENT_ID" class="events.Event"/>
</set>
</class>]]></programlisting>
@ -957,7 +976,7 @@ public class Person {
</sect2>
<sect2 id="tutorial-associations-working" revision="1">
<sect2 id="tutorial-associations-working" revision="2">
<title>Travailler avec l'association</title>
<para>
@ -991,14 +1010,14 @@ public class Person {
Hibernate surveille les changements et exécute le SQL correspondant. Le processus de
synchronisation de l'état de la mémoire avec la base de données, généralement seulement à la fin
d'une unité de travail, est appelé <emphasis>flushing</emphasis>. Dans notre code, l'unité de travail
s'achève par un commit (ou rollback) de la transaction avec la base de données - comme défini
s'achève par un commit (ou rollback) de la transaction avec la base de données - comme défini
par notre option <literal>thread</literal> de configuration pour la classe <literal>CurrentSessionContext</literal>.
</para>
<para>
Vous pourriez bien sûr charger une personne et un événement dans différentes unités de travail. Ou
vous modifiez un objet à l'extérieur d'une <literal>Session</literal>, s'il n'est pas dans un état
persistant (s'il était persistant avant, nous appelons cet état <emphasis>détaché</emphasis>).
persistant (s'il était persistant avant, nous appelons cet état <emphasis>détaché</emphasis>).
Vous pouvez même modifier une collection lorsqu'elle est détachée:
</para>
@ -1038,21 +1057,20 @@ public class Person {
pour retourner cet identifiant).
</para>
<para>
Cela n'a pas grand intérêt dans notre situation, mais c'est un concept important qu'il vous faut concevoir
dans votre application. Pour le moment, complétez cet excercice en ajoutant une nouvelle
action à la méthode principale de l'<literal>EventManager</literal> et invoquez la depuis la ligne de commande.
Si vous avez besoin des identifiants d'un client et d'un évènement - la méthode <literal>save()</literal>
vous les retourne (vous devrez peut être modifier certaines méthodes précédentes pour retourner ces identifiants):
</para>
<programlisting><![CDATA[else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);]]></programlisting>
<para>
Ce n'est pas très utile dans notre situation actuelle, mais c'est un concept important
que vous pouvez mettre dans votre propre application.
Pour le moment, complétez cet exercice en ajoutant une nouvelle action à la méthode
principale des <literal>EventManager</literal>s et appelez la à partir de la ligne de
commande. Si vous avez besoin des identifiants d'une personne et d'un événement - la
méthode <literal>save()</literal> les retourne.
</para>
<para>
C'était un exemple d'une association entre deux classes de même importance, deux entités.
Comme mentionné plus tôt, il y a d'autres classes et d'autres types dans un modèle typique,
@ -1143,7 +1161,7 @@ public void setEmailAddresses(Set emailAddresses) {
nous l'avons fait avant en liant des personnes et des événements. C'est le même code
en Java.
</para>
<programlisting><![CDATA[private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
@ -1285,7 +1303,7 @@ public void removeFromEvent(Event event) {
-->
</sect2>
</sect1>
<sect1 id="tutorial-webapp">
<title>Part 3 - L'application web EventManager</title>
@ -1296,11 +1314,11 @@ public void removeFromEvent(Event event) {
la base de données, et fournir une formulaire HTML pour saisir d'autres évènements.
</para>
<sect2 id="tutorial-webapp-servlet">
<sect2 id="tutorial-webapp-servlet" revision="2">
<title>Ecrire la servlet de base</title>
<para>
Créons une nouvelle classe dans notre répertoire source, dans le package <literal>events</literal>:
Créons une nouvelle classe dans notre répertoire source, dans le package <literal>events</literal> :
</para>
<programlisting><![CDATA[package events;
@ -1309,35 +1327,28 @@ public void removeFromEvent(Event event) {
public class EventManagerServlet extends HttpServlet {
private final SimpleDateFormat dateFormatter =
new SimpleDateFormat("dd.MM.yyyy");
// Servlet code
}]]></programlisting>
<para>
Le <literal>dateFormatter</literal> est un outil que nous utiliserons plus tard pour convertir les objets
<literal>Date</literal> depuis et vers des chaines de caractères. Il est propice de n'avoir qu'un
formatter comme membre de la servlet.
</para>
<para>
La servlet n'accepte que les requêtes HTTP <literal>GET</literal>, la méthode à implémenter est donc
<literal>doGet()</literal>:
<literal>doGet()</literal> :
</para>
<programlisting><![CDATA[protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
try {
// Begin unit of work
// Début de l'unité de travail
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
// Process request and render page...
// Traitement de la requête et rendu de la page...
// End unit of work
// Fin de l'unité de travail
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
@ -1350,14 +1361,21 @@ public class EventManagerServlet extends HttpServlet {
}]]></programlisting>
<para>
La pattern que nous utilisons ici est appelé <emphasis>session-per-request</emphasis>.
Lorsqu'une requête touche la servlet, une nouvelle <literal>Session</literal> hibernate est
ouverte à l'invocationde <literal>getCurrentSession()</literal> sur la
<literal>SessionFactory</literal>. Ensuite, une transaction avec la base de données est démarrée&mdash;
tous les accès à la base de données interviennent au sein de la transactiton, peu importe que les données
Le pattern que nous utilisons ici est appelé <emphasis>session-per-request</emphasis>.
Lorsqu'une requête appelle la servlet, une nouvelle <literal>Session</literal> Hibernate est
ouverte à l'invocation de <literal>getCurrentSession()</literal> sur la
<literal>SessionFactory</literal>. Ensuite, une transaction avec la base de données est démarrée &mdash;
tous les accès à la base de données interviennent au sein de la transaction, peu importe que les données
soient lues ou écrites (nous n'utilisons pas le mode auto-commit dans les applications).
</para>
<para>
<emphasis>N'utilisez pas</emphasis> une nouvelle <literal>Session</literal> Hibernate pour
chaque opération en base de données. Utilisez une <literal>Session</literal> Hibernate qui
porte sur l'ensemble de la requête. Utlisez <literal>getCurrentSession()</literal>,
ainsi elle est automatiquement attachée au thread Java courant.
</para>
<para>
Ensuite, les actions possibles de la requêtes sont exécutées et la réponse HTML
est rendue. Nous en parlerons plus tard.
@ -1367,20 +1385,20 @@ public class EventManagerServlet extends HttpServlet {
Enfin, l'unité de travail s'achève lorsque l'exécution et le rendu sont achevés.
Si un problème survient lors de ces deux phases, une exception est soulevée et la
transaction avec la base de données subit un rollback. Voila pour le pattern
<literal>session-per-request</literal>. Au lieu d'un code de démarcation de transaction
<literal>session-per-request</literal>. Au lieu d'avoir un code de délimitant les transactions
au sein de chaque servlet, vous pouvez écrire un filtre de servlet.
Voir le site Hibernate et le Wiki pour plus d'information sur ce pattern, appelé
<emphasis>Open Session in View</emphasis>&mdash; vous en aurez besoin dès que vous
utiliserez des JSPs et non plus des servlets pour le rendu de vos vues.
<emphasis>Open Session in View</emphasis> &mdash; vous en aurez besoin dès que vous
utiliserez des JSPs et non plus des servlets pour le rendu de vos vues.
</para>
</sect2>
<sect2 id="tutorial-webapp-processing">
<sect2 id="tutorial-webapp-processing" revision="1">
<title>Procéder et rendre</title>
<para>
Implémentons l'exécution de la requête et le rendu de la page.
Implémentons l'exécution de la requête et le rendu de la page.
</para>
<programlisting><![CDATA[// Write HTML header
@ -1411,7 +1429,7 @@ out.flush();
out.close();]]></programlisting>
<para>
Ce style de code avec un mix de Java et d'HTML ne serait pas scalable
Ce style de code avec un mix de Java et d'HTML ne serait pas scalable
dans une application plus complexe&mdash;gardez à l'esprit que nous ne faisons qu'illustrer
les concepts basiques d'Hibernate dans ce tutoriel. Ce code affiche une en tête et un pied de page
HTML. Dans cette page, sont affichés un formulaire pour la saisie d'évènements ainsi
@ -1434,7 +1452,8 @@ out.close();]]></programlisting>
requête:
</para>
<programlisting><![CDATA[private void listEvents(PrintWriter out) {
<programlisting><![CDATA[private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
@ -1535,13 +1554,13 @@ out.close();]]></programlisting>
<para>
Pour construire et déployer, appelez <literal>ant war</literal> dans votre projet et
copier le fichier <literal>hibernate-tutorial.war</literal> dans le répertoire <literal>webapp</literal> de tomcat
Si vous n'avez pas installé Tomcat, téléchargez le et suivez la notice d'installation.
Si vous n'avez pas installé Tomcat, téléchargez le et suivez la notice d'installation.
Vous n'avez pas à modifier la configuration Tomcat pour déployer cette application.
</para>
<para>
Une fois l'application déployée et Tomcat lancé, accédez à l'application via
<literal>http://localhost:8080/hibernate-tutorial/eventmanager</literal>.
<literal>http://localhost:8080/hibernate-tutorial/eventmanager</literal>.
Assurez vous de consulter les traces tomcat pour observer l'initialisation
d'Hibernate à la première requête touchant votre servlet (l'initialisation statique dans <literal>HibernateUtil</literal>
est invoquée) et pour vérifier qu'aucune exception ne survienne.