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

537 lines
22 KiB
XML

<?xml version='1.0' encoding="iso-8859-1"?>
<chapter id="persistent-classes" revision="2">
<title>Classes persistantes</title>
<para>
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
dans l'état persistant - au lieu de cela, une instance peut être éphémère (NdT : transient) ou détachée.
</para>
<para>
Hibernate fonctionne de manière optimale lorsque ces classes suivent quelques règles
simples, aussi connues comme le modèle de programmation Plain Old Java Object
(POJO). Cependant, aucune de ces règles ne sont des besoins absolus. En effet, Hibernate3 suppose très peu de choses à propos
de la nature de vos objets persistants. Vous pouvez exprimer un modèle de domaine par d'autres moyens : utiliser des arbres
d'instances de <literal>Map</literal>, par exemple.
</para>
<sect1 id="persistent-classes-pojo">
<title>Un exemple simple de POJO</title>
<para>
Toute bonne application Java nécessite une classe persistante
représentant les félins.
</para>
<programlisting><![CDATA[package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}]]></programlisting>
<para>
Il y a quatre règles à suivre ici :
</para>
<sect2 id="persistent-classes-pojo-constructor" revision="1">
<title>Implémenter un constructeur sans argument</title>
<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
au moins une visibilité <emphasis>paquet</emphasis> pour la génération du proxy à l'exécution dans Hibernate.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-identifier" revision="2">
<title>Fournir une propriété d'indentifiant (optionnel)</title>
<para>
<literal>Cat</literal> possède une propriété appelée <literal>id</literal>.
Cette propriété mappe la valeur de la colonne de clé primaire de la table
d'une base de données.La propriété aurait pu s'appeler complètement autrement,
et son type aurait pu être n'importe quel type primitif, n'importe quel "encapsuleur"
de type primitif, <literal>java.lang.String</literal> ou <literal>java.util.Date</literal>.
(Si votre base de données héritée possède des clés composites, elles peuvent être mappées
en utilisant une classe définie par l'utilisateur et possédant les propriétés associées aux
types de la clé composite - voir la section concernant les identifiants composites plus tard).
</para>
<para>
La propriété d'identifiant est strictement optionnelle. Vous pouver l'oublier et laisser Hibernate
s'occuper des identifiants de l'objet en interne. Toutefois, nous ne le recommandons pas.
</para>
<para>
En fait, quelques fonctionnalités ne sont disponibles que pour les classes
déclarant un identifiant de propriété :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
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>
<listitem>
<para>
<literal>Session.saveOrUpdate()</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Session.merge()</literal>
</para>
</listitem>
</itemizedlist>
<para>
Nous recommandons que vous déclariez les propriétés d'identifiant de manière
uniforme. Nous recommandons également que vous utilisiez un type nullable
(ie. non primitif).
</para>
</sect2>
<sect2 id="persistent-classes-pojo-final">
<title>Favoriser les classes non finales (optionnel)</title>
<para>
Une fonctionnalité clef d'Hibernate, les <emphasis>proxies</emphasis>, nécessitent
que la classe persistente soit non finale ou qu'elle soit l'implémentation d'une
interface qui déclare toutes les méthodes publiques.
</para>
<para>
Vous pouvez persister, grâce à Hibernate, les classes <literal>final</literal>
qui n'implémentent pas d'interface, mais vous ne pourrez pas utiliser les proxies pour les chargements d'associations paresseuses
- ce qui limitera vos possibilités d'ajustement des performances.
</para>
<para>
Vous devriez aussi éviter de déclarer des méthodes <literal>public final</literal> sur des classes
non-finales. Si vous voulez utiliser une classe avec une méthode <literal>public final</literal>, vous devez
explicitement désactiver les proxies en paramétrant
<literal>lazy="false"</literal>.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-accessors" revision="2">
<title>Déclarer les accesseurs et mutateurs des attributs persistants (optionnel)</title>
<para>
<literal>Cat</literal> déclare des mutateurs pour toutes ses champs persistants. Beaucoup d'autres
solutions de mapping Objet/relationnel persistent directement les variables d'instance. Nous pensons
qu'il est bien mieux de fournir une indirection entre le schéma relationnel et les structures de données internes de la classe.
Par défaut, Hibernate persiste les propriétés suivant le style JavaBean, et reconnaît les noms de méthodes de la forme <literal>
getFoo</literal>, <literal>isFoo</literal> et
<literal>setFoo</literal>. Nous pouvons changer pour un accès direct aux champs pour des propriétés particulières, si besoin est.
</para>
<para>
Les propriétés <emphasis>n'ont pas</emphasis> à être déclarées publiques -
Hibernate peut persister une propriété avec un paire de getter/setter de
visibilité par défault, <literal>protected</literal> ou <literal>
private</literal>.
</para>
</sect2>
</sect1>
<sect1 id="persistent-classes-inheritance">
<title>Implémenter l'héritage</title>
<para>
Une sous-classe doit également suivre la première et la seconde règle.
Elle hérite sa propriété d'identifiant de <literal>Cat</literal>.
</para>
<programlisting><![CDATA[package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-equalshashcode" revision="1">
<title>Implémenter <literal>equals()</literal> et <literal>hashCode()</literal></title>
<para>
Vous devez surcharger les méthodes <literal>equals()</literal> et
<literal>hashCode()</literal> si vous
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
avez l'intention de mettre des instances de classes persistantes dans un <literal>Set</literal>
(la manière recommandée pour représenter des associations pluri-valuées)
<emphasis>et</emphasis>
</para>
</listitem>
<listitem>
<para>
avez l'intention d'utiliser le réattachement d'instances détachées
</para>
</listitem>
</itemizedlist>
<para>
Hibernate garantit l'équivalence de l'identité persistante (ligne de base de données) et l'identité Java seulement
à l'intérieur de la portée d'une session particulière. Donc dès que nous mélangeons des instances venant de différentes
sessions, nous devons implémenter <literal>equals()</literal> et
<literal>hashCode()</literal> si nous souhaitons avoir une sémantique correcte pour les <literal>Set</literal>s.
</para>
<para>
La manière la plus évidente est d'implémenter <literal>equals()</literal>/<literal>hashCode()</literal>
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
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.
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>
<para>
Nous recommandons donc d'implémenter
<literal>equals()</literal> et <literal>hashCode()</literal> en utilisant <emphasis>
l'égalité par clé métier</emphasis>.L'égalité par clé métier signifie que la méthode <literal>equals()</literal>
compare uniquement les propriétés qui forment une clé métier, une clé qui
identifierait notre instance dans le monde réel (une clé candidate
<emphasis>naturelle</emphasis>) :
</para>
<programlisting><![CDATA[public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}]]></programlisting>
<para>
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>
</sect1>
<sect1 id="persistent-classes-dynamicmodels">
<title>Modèles dynamiques</title>
<para>
<emphasis>Notez que la fonctionnalités suivantes sont actuellement considérées
comme expérimentales et peuvent changer dans un futur proche.</emphasis>
</para>
<para>
Les entités persistantes ne doivent pas nécessairement être représentées comme
des classes POJO ou des objets JavaBean à l'exécution. Hibernate supporte aussi les
modèles dynamiques (en utilisant des <literal>Map</literal>s de <literal>Map</literal>s
à l'exécution) et la représentation des entités comme des arbres DOM4J. Avec cette
approche, vous n'écrivez pas de classes persistantes, seulement des fichiers de mapping.
</para>
<para>
Par défaut, Hibernate fonctionne en mode POJO normal. Vous pouvez paramétrer
un mode de représentation d'entité par défaut pour une <literal>SessionFactory</literal>
particulière en utilisant l'option de configuration <literal>default_entity_mode</literal>
(voir <xref linkend="configuration-optional-properties"/>).
</para>
<para>
Les exemples suivants démontrent la représentation utilisant des <literal>Map</literal>s.
D'abord, dans le fichier de mapping, un <literal>entity-name</literal> doit être déclaré
au lieu (ou en plus) d'un nom de classe :
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping>]]></programlisting>
<para>
Notez que même si des associations sont déclarées en utilisant des noms de classe cible,
le type de cible d'une association peut aussi être une entité dynamique au lieu d'un POJO.
</para>
<para>
Après avoir configuré le mode d'entité par défaut à <literal>dynamic-map</literal>
pour la <literal>SessionFactory</literal>, nous pouvons lors de l'exécution fonctionner
avec des <literal>Map</literal>s de <literal>Map</literal>s :
</para>
<programlisting><![CDATA[Session s = openSession();
Transaction tx = s.beginTransaction();
Session s = openSession();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();]]></programlisting>
<para>
Les avantages d'un mapping dynamique sont un gain de temps pour le prototypage
sans la nécessité d'implémenter les classes d'entité. Pourtant, vous perdez la
vérification du typage au moment de la compilation et aurez plus d'exceptions à
gérer lors de l'exécution. Grâce au mapping d'Hibernate, le schéma de la base de
données peut facilement être normalisé et solidifié, permettant de rajouter une
implémentation propre du modèle de domaine plus tard.
</para>
<para>
Les modes de représentation d'une entité peut aussi être configuré par <literal>Session</literal> :
</para>
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
]]></programlisting>
<para>
Veuillez noter que l'appel à <literal>getSession()</literal> en utilisant un
<literal>EntityMode</literal> se fait sur l'API <literal>Session</literal>, pas
<literal>SessionFactory</literal>. De cette manière, la nouvelle <literal>Session</literal>
partage les connexions JDBC, transactions et autres informations de contexte sous-jacentes.
Cela signifie que vous n'avez pas à appeler <literal>flush()</literal> et <literal>close()</literal>
sur la <literal>Session</literal> secondaire, et laissez aussi la gestion de la transaction
et de la connexion à l'unité de travail primaire.
</para>
<para>
Plus d'informations à propos de la représentation XML peuvent être trouvées dans
<xref linkend="xml"/>.
</para>
</sect1>
<sect1 id="persistent-classes-tuplizers" revision="1">
<title>Tuplizers</title>
<para>
<literal>org.hibernate.tuple.Tuplizer</literal>, et ses sous-interfaces, sont responsables
de la gestion d'une représentation particulière d'un morceau de données, en fonction du
<literal>org.hibernate.EntityMode</literal> de réprésentation. Si un morceau donné de données
est pensé comme une structure de données, alors un tuplizer est la chose qui sait comment
créer une telle structure de données, comment extraire des valeurs et injecter des valeurs dans
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é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.
</para>
<para>
Les utilisateurs peuvent aussi brancher leurs propres tuplizers. Peut-être vous est-il nécessaire qu'une
implémentation de <literal>java.util.Map</literal> autre que <literal>java.util.HashMap</literal>
soit utilisée dans le mode d'entité dynamic-map ; ou peut-être avez-vous besoin de définir une
statégie de génération de proxy différente de celle utilisée par défaut. Les deux devraient être
effectuées en définissant une implémentation de tuplizer utilisateur. Les définitions de tuplizers
sont attachées au mapping de l'entité ou du composant qu'ils sont censés gérer. Retour à l'exemple de
notre entité utilisateur :
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
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) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}]]></programlisting>
</sect1>
<para>
TODO: Document user-extension framework in the property and proxy packages
</para>
</chapter>