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

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

@ -80,7 +80,7 @@
</sect1>
<sect1 id="queryhql-joins" revision="1">
<sect1 id="queryhql-joins" revision="2">
<title>Associations et jointures</title>
<para>
@ -171,8 +171,9 @@
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
<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.
@ -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>

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,122 +21,218 @@
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>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
]]></programlisting>
<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>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><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
]]></programlisting>
<para>Cette requête spécifie:</para>
<itemizedlist>
<listitem>
<para>la requête SQL</para>
<para>la chaîne de caractère SQL</para>
</listitem>
<listitem>
<para>l'entité retournée par la requête</para>
<para>les colonnes et les types retournés</para>
</listitem>
</itemizedlist>
<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>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>
<programlisting>List cats = sess.createSQLQuery("select {cat.*} from cats cat")
.addEntity("cat", Cat.class)
.list();</programlisting>
<para>Il est possible de ne pas définir l'information sur le type pour toutes ou partie
des calaires.</para>
<para>Cette requête a spécifié :</para>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
]]></programlisting>
<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>
<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>
<sect2>
<title>Requêtes d'entités</title>
<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>
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
]]></programlisting>
<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>
<para>La chaîne de caractère de requête SQL</para>
</listitem>
<listitem>
<para>l'entité retournée par la requête, et son alias de table SQL</para>
<para>L'entité retournée par la requête</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>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>
La méthode <literal>addJoin()</literal> peut être utilisée pour charger des associations vers d'autres
entités et collections.
</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>List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
<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("kitten", "cat.kittens")
.list();</programlisting>
.addJoin("cat.dog");
]]></programlisting>
<para>
Une requête SQL native pourrait retourner une simple valeur scalaire ou une combinaison de scalaires et d'entités.
</para>
<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>Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();</programlisting>
<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>
<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>
<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>
<programlisting>List cats = sess.createSQLQuery(
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
)
.setResultSetMapping("catAndKitten")
.list();</programlisting>
</sect1>
<sect2>
<title>Retour d'entités multiples</title>
<sect1 id="querysql-aliasreferences">
<title>Alias et références de propriété</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>
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>L'injection d'alias de colonne est requis pour la requête suivante
(qui risque de ne pas fonctionner):</para>
<para>
La syntaxe <literal>{}</literal> <emphasis>n'est pas</emphasis> requise pour le requêtes nommées. Voir
<xref linkend="querysql-namedqueries" />.
</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>
<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"
<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("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>
<title>Nom d'injection d'alias</title>
<tgroup cols="4">
<tgroup cols="3">
<colspec colwidth="1*" />
<colspec colwidth="1*" />
@ -155,15 +251,15 @@ List loggedCats = sess.createSQLQuery(sql)
<tbody>
<row>
<entry>Une simple propriété</entry>
<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>
<row>
<entry>Une propriété composée</entry>
<entry>Une propriété composite</entry>
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
@ -172,7 +268,7 @@ List loggedCats = sess.createSQLQuery(sql)
</row>
<row>
<entry>Discriminant d'une entité</entry>
<entry>Discriminateur d'une entité</entry>
<entry><literal>{[aliasname].class}</literal></entry>
@ -188,7 +284,7 @@ List loggedCats = sess.createSQLQuery(sql)
</row>
<row>
<entry>Une clef de collection</entry>
<entry>La clé d'une collection</entry>
<entry><literal>{[aliasname].key}</literal></entry>
@ -196,7 +292,7 @@ List loggedCats = sess.createSQLQuery(sql)
</row>
<row>
<entry>L'identifiant d'une collection</entry>
<entry>L'id d'une collection</entry>
<entry><literal>{[aliasname].id}</literal></entry>
@ -209,12 +305,10 @@ List loggedCats = sess.createSQLQuery(sql)
<entry><literal>{[aliasname].element}</literal></entry>
<entry><literal>XID as {coll.element}</literal></entry>
<entry></entry>
</row>
<row>
<entry>Propriété de l'élément dans la collection</entry>
<entry>Propriété d'un élément de collection</entry>
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
@ -222,7 +316,7 @@ List loggedCats = sess.createSQLQuery(sql)
</row>
<row>
<entry>Toutes les propriétés de l'élément dans la collection</entry>
<entry>Toutes les propriétés d'un élément de collection</entry>
<entry><literal>{[aliasname].element.*}</literal></entry>
@ -230,7 +324,7 @@ List loggedCats = sess.createSQLQuery(sql)
</row>
<row>
<entry>Toutes les propriétés de la collection</entry>
<entry>Toutes les propriétés d'une collection</entry>
<entry><literal>{[aliasname].*}</literal></entry>
@ -239,6 +333,57 @@ List loggedCats = sess.createSQLQuery(sql)
</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>
</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>
@ -477,6 +480,15 @@ List cats = q.list();]]></programlisting>
migrer des requêtes existantes vers Hibernate en les plaçant dans les fichiers de mapping.
</para>
<para>
Notez aussi que la déclaration d'une requête dans un élément <literal>&lt;hibernate-mapping&gt;</literal>
nécessite un nom globalement unique pour la requête, alors que la déclaration d'une requête
dans une élément <literal>&lt;class&gt;</literal> est rendue unique de manière automatique par
la mise en préfixe du nom entièrement de la classe, par exemple
<literal>eg.Cat.ByNameAndMaximumWeight</literal>.
</para>
</sect3>
</sect2>

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

@ -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>
@ -353,7 +353,7 @@ public class Event {
<para>
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
@ -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,6 +655,8 @@ public class EventManager {
session.save(theEvent);
session.getTransaction().commit();
}
}]]></programlisting>
<para>
@ -678,16 +680,33 @@ public class EventManager {
<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
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.
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>
@ -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>
@ -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>
@ -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,
@ -1296,7 +1314,7 @@ 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>
@ -1309,18 +1327,9 @@ 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> :
@ -1330,14 +1339,16 @@ public class EventManagerServlet extends HttpServlet {
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
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 transactiton, peu importe que les données
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,7 +1385,7 @@ 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
@ -1376,7 +1394,7 @@ public class EventManagerServlet extends HttpServlet {
</sect2>
<sect2 id="tutorial-webapp-processing">
<sect2 id="tutorial-webapp-processing" revision="1">
<title>Procéder et rendre</title>
<para>
@ -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) {