Classes persistantes
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.
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 Map, par exemple.
Un exemple simple de POJO
Toute bonne application Java nécessite une classe persistante
représentant les félins.
Il y a quatre règles à suivre ici :
Implémenter un constructeur sans argument
Cat 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
Constructor.newInstance(). Nous recommandons fortement d'avoir un constructeur par défaut avec
au moins une visibilité paquet pour la génération du proxy à l'exécution dans Hibernate.
Fournir une propriété d'indentifiant (optionnel)
Cat possède une propriété appelée id.
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, java.lang.String ou java.util.Date.
(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).
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.
En fait, quelques fonctionnalités ne sont disponibles que pour les classes
déclarant un identifiant de propriété :
Les réattachements transitifs pour les objets détachés (mise à jour en cascade ou fusion en cascade) -
voir
Session.saveOrUpdate()
Session.merge()
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).
Favoriser les classes non finales (optionnel)
Une fonctionnalité clef d'Hibernate, les proxies, 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.
Vous pouvez persister, grâce à Hibernate, les classes final
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.
Vous devriez aussi éviter de déclarer des méthodes public final sur des classes
non-finales. Si vous voulez utiliser une classe avec une méthode public final, vous devez
explicitement désactiver les proxies en paramétrant
lazy="false".
Déclarer les accesseurs et mutateurs des attributs persistants (optionnel)
Cat 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
getFoo, isFoo et
setFoo. Nous pouvons changer pour un accès direct aux champs pour des propriétés particulières, si besoin est.
Les propriétés n'ont pas à être déclarées publiques -
Hibernate peut persister une propriété avec un paire de getter/setter de
visibilité par défault, protected ou
private.
Implémenter l'héritage
Une sous-classe doit également suivre la première et la seconde règle.
Elle hérite sa propriété d'identifiant de Cat.
Implémenter equals() et hashCode()
Vous devez surcharger les méthodes equals() et
hashCode() si vous
avez l'intention de mettre des instances de classes persistantes dans un Set
(la manière recommandée pour représenter des associations pluri-valuées)
et
avez l'intention d'utiliser le réattachement d'instances détachées
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 equals() et
hashCode() si nous souhaitons avoir une sémantique correcte pour les Sets.
La manière la plus évidente est d'implémenter equals()/hashCode()
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 Set, nous n'aurons qu'un seul élément dans le
Set). 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 Set,
le sauvegarder assignera une valeur d'identifiant à l'objet. Si equals() et hashCode()
sont basées sur la valeur de l'identifiant, le code de hachage devrait changer, rompant le contrat du Set.
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é.
Nous recommandons donc d'implémenter
equals() et hashCode() en utilisant
l'égalité par clé métier.L'égalité par clé métier signifie que la méthode equals()
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
naturelle) :
Notez qu'une clef métier ne doit pas être solide comme une clef primaire de base de données
(voir ). Les propriétés
immuables ou uniques sont généralement de bonnes candidates pour une clef métier.
Modèles dynamiques
Notez que la fonctionnalités suivantes sont actuellement considérées
comme expérimentales et peuvent changer dans un futur proche.
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 Maps de Maps
à 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.
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 SessionFactory
particulière en utilisant l'option de configuration default_entity_mode
(voir ).
Les exemples suivants démontrent la représentation utilisant des Maps.
D'abord, dans le fichier de mapping, un entity-name doit être déclaré
au lieu (ou en plus) d'un nom de classe :
]]>
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.
Après avoir configuré le mode d'entité par défaut à dynamic-map
pour la SessionFactory, nous pouvons lors de l'exécution fonctionner
avec des Maps de Maps :
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.
Les modes de représentation d'une entité peut aussi être configuré par Session :
Veuillez noter que l'appel à getSession() en utilisant un
EntityMode se fait sur l'API Session, pas
SessionFactory. De cette manière, la nouvelle Session
partage les connexions JDBC, transactions et autres informations de contexte sous-jacentes.
Cela signifie que vous n'avez pas à appeler flush() et close()
sur la Session secondaire, et laissez aussi la gestion de la transaction
et de la connexion à l'unité de travail primaire.
Plus d'informations à propos de la représentation XML peuvent être trouvées dans
.
Tuplizers
org.hibernate.tuple.Tuplizer, et ses sous-interfaces, sont responsables
de la gestion d'une représentation particulière d'un morceau de données, en fonction du
org.hibernate.EntityMode 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 org.hibernate.tuple.EntityTuplizer et
org.hibernate.tuple.ComponentTuplizer. Les EntityTuplizers
sont responsables de la gestion des contrats mentionnés ci-dessus pour les entités, alors que
les ComponentTuplizers s'occupent des composants.
Les utilisateurs peuvent aussi brancher leurs propres tuplizers. Peut-être vous est-il nécessaire qu'une
implémentation de java.util.Map autre que java.util.HashMap
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 :
...
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();
}
}
}]]>
TODO: Document user-extension framework in the property and proxy packages