537 lines
22 KiB
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>
|
|
|