3.1final
Table des matières
Traducteur(s): Vincent Ricard, Sebastien Cesbron, Michael Courcy, Vincent Giguère, Baptiste Mathus, Emmanuel Bernard, Anthony Patricio
Travailler dans les deux univers que sont l'orienté objet et la base de données relationnelle peut être lourd et consommateur en temps dans le monde de l'entreprise d'aujourd'hui. Hibernate est un outil de mapping objet/relationnel pour le monde Java. Le terme mapping objet/relationnel (ORM) décrit la technique consistant à faire le lien entre la représentation objet des données et sa représentation relationnelle basée sur un schéma SQL.
Non seulement, Hibernate s'occupe du transfert des classes Java dans les tables de la base de données (et des types de données Java dans les types de données SQL), mais il permet de requêter les données et propose des moyens de les récupérer. Il peut donc réduire de manière significative le temps de développement qui aurait été autrement perdu dans une manipulation manuelle des données via SQL et JDBC.
Le but d'Hibernate est de libérer le développeur de 95 pourcent des tâches de programmation liées à la persistence des données communes. Hibernate n'est probablement pas la meilleure solution pour les applications centrées sur les données qui n'utilisent que les procédures stockées pour implémenter la logique métier dans la base de données, il est le plus utile dans les modèles métier orientés objets dont la logique métier est implémentée dans la couche Java dite intermédiaire. Cependant, Hibernate vous aidera à supprimer ou à encapsuler le code SQL spécifique à votre base de données et vous aidera sur la tâche commune qu'est la transformation des données d'une représentation tabulaire à une représentation sous forme de graphe d'objets.
Si vous êtes nouveau dans Hibernate et le mapping Objet/Relationnel voire même en Java, suivez ces quelques étapes :
Lisez Chapitre 1, Introduction à Hibernate pour un didacticiel plus long avec plus d'instructions étape par étape.
Lisez Chapitre 2, Architecture pour comprendre les environnements dans lesquels Hibernate peut être utilisé.
Regardez le répertoire eg de la distribution Hibernate, il contient une application simple et autonome. Copiez votre pilote JDBC dans le répertoire lib/ et éditez src/hibernate.properties, en positionnant correctement les valeurs pour votre base de données. A partir d'une invite de commande dans le répertoire de la distribution, tapez ant eg (cela utilise Ant), ou sous Windows tapez build eg.
Faîtes de cette documentation de référence votre principale source d'information. Pensez à lire Hibernate in Action (http://www.manning.com/bauer) si vous avez besoin de plus d'aide avec le design d'applications ou si vous préférez un tutoriel pas à pas. Visitez aussi http://caveatemptor.hibernate.org et téléchargez l'application exemple pour Hibernate in Action.
Les questions les plus fréquemment posées (FAQs) trouvent leur réponse sur le site web Hibernate.
Des démos, exemples et tutoriaux de tierces personnes sont référencés sur le site web Hibernate.
La zone communautaire (Community Area) du site web Hibernate est une bonne source d'information sur les design patterns et sur différentes solutions d'intégration d'Hibernate (Tomcat, JBoss, Spring Framework, Struts, EJB, etc).
Si vous avez des questions, utilisez le forum utilisateurs du site web Hibernate. Nous utilisons également l'outil de gestion des incidents JIRA pour tout ce qui est rapports de bogue et demandes d'évolution. Si vous êtes intéressé par le développement d'Hibernate, joignez-vous à la liste de diffusion de développement.
Le développement commercial, le support de production et les formations à Hibernate sont proposés par JBoss Inc (voir http://www.hibernate.org/SupportTraining/). Hibernate est un projet Open Source professionnel et un composant critique de la suite de produits JBoss Enterprise Middleware System (JEMS).
Ce chapitre est un didacticiel introductif destiné aux nouveaux utilisateurs d'Hibernate. Nous commençons avec une simple application en ligne de commande utilisant une base de données en mémoire, et la développons en étapes faciles à comprendre.
Ce didacticiel est destiné aux nouveaux utilisateurs d'Hibernate mais requiert des connaissances Java et SQL. Il est basé sur un didacticiel de Michael Gloegl, les bibliothèques tierces que nous nommons sont pour les JDK 1.4 et 5.0. Vous pourriez avoir besoin d'autres bibliothèques pour le JDK 1.3.
Le code source de ce tutoriel est inclus dans la distribution dans le répertoire doc/reference/tutorial/.
D'abord, nous créerons une simple application Hibernate en console. Nous utilisons une base de données en mémoire (HSQL DB), donc nous n'avons pas à installer de serveur de base de données.
Supposons que nous ayons besoin d'une petite application de base de données qui puisse stocker des événements que nous voulons suivre, et des informations à propos des hôtes de ces événements.
La première chose que nous faisons est de configurer notre répertoire de développement et de mettre toutes les bibliothèques dont nous avons besoin dedans. Téléchargez la distribution Hibernate à partir du site web d'Hibernate. Extrayez le paquet et placez toutes les bibliothèques requises trouvées dans /lib dans le répertoire /lib de votre nouveau répertoire de travail. Il devrait ressembler à ça :
. +lib antlr.jar cglib-full.jar asm.jar asm-attrs.jars commons-collections.jar commons-logging.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar
Ceci est l'ensemble minimum de bibliothèques requises (notez que nous avons aussi copié hibernate3.jar, l'archive principale) pour Hibernate. Lisez le fichier README.txt dans le répertoire lib/ de la distribution Hibernate pour plus d'informations à propos des biliothèques tierces requises et optionnelles. (En fait, log4j n'est pas requis mais préféré par beaucoup de développeurs.)
Ensuite, nous créons une classe qui réprésente l'événement que nous voulons stocker dans notre base de données.
Notre première classe persistante est une simple classe JavaBean avec quelques propriétés :
package events;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}Vous pouvez voir que cette classe utilise les conventions de nommage standard JavaBean pour les méthodes getter/setter des propriétés, ainsi qu'une visibilité privée pour les champs. Ceci est la conception recommandée - mais pas obligatoire. Hibernate peut aussi accéder aux champs directement, le bénéfice des méthodes d'accès est la robustesse pour la refonte de code. Le constructeur sans argument est requis pour instancier un objet de cette classe via reflexion.
La propriété id contient la valeur d'un identifiant unique pour un événement particulier. Toutes les classes d'entités persistantes (ainsi que les classes dépendantes de moindre importance) auront besoin d'une telle propriété identifiante si nous voulons utiliser l'ensemble complet des fonctionnalités d'Hibernate. En fait, la plupart des applications (surtout les applications web) ont besoin de distinguer des objets par des identifiants, donc vous devriez considérer ça comme une fonctionnalité plutôt que comme une limitation. Cependant, nous ne manipulons généralement pas l'identité d'un objet, dorénavant la méthode setter devrait être privée. Seul Hibernate assignera les identifiants lorsqu'un objet est sauvegardé. Vous pouvez voir qu'Hibernate peut accéder aux méthodes publiques, privées et protégées, ainsi qu'aux champs (publics, privés, protégés) directement. Le choix vous est laissé, et vous pouvez l'ajuster à la conception de votre application.
Le constructeur sans argument est requis pour toutes les classes persistantes ; Hibernate doit créer des objets pour vous en utilisant la réflexion Java. Le constructeur peut être privé, cependant, la visibilité du paquet est requise pour la génération de proxy à l'exécution et une récupération des données efficaces sans instrumentation du bytecode.
Placez ce fichier source Java dans un répertoire appelé src dans le dossier de développement. Ce répertoire devrait maintenant ressembler à ça :
.
+lib
<Hibernate et bibliothèques tierces>
+src
+events
Event.javaDans la prochaine étape, nous informons Hibernate de cette classe persistante.
Hibernate a besoin de savoir comment charger et stocker des objets d'une classe persistante. C'est là qu'intervient le fichier de mapping Hibernate. Le fichier de mapping indique à Hibernate à quelle table dans la base de données il doit accéder, et quelles colonnes de cette table il devra utiliser.
La structure basique de ce fichier de mapping ressemble à ça :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
[...]
</hibernate-mapping>Notez que la DTD Hibernate est très sophistiquée. Vous pouvez l'utiliser pour l'auto-complétement des éléments et des attributs de mapping XML dans votre éditeur ou votre IDE. Vous devriez aussi ouvrir le fichier DTD dans votre éditeur de texte - c'est le moyen le plus facile d'obtenir une vue d'ensemble de tous les éléments et attributs, et de voir les valeurs par défaut, ainsi que quelques commentaires. Notez qu'Hibernate ne chargera pas le fichier DTD à partir du web, mais regardera d'abord dans le classpath de l'application. Le fichier DTD est inclus dans hibernate3.jar ainsi que dans le répertoire src de la distribution Hibernate.
Nous omettrons la déclaration de la DTD dans les exemples futurs pour raccourcir le code. Bien sûr il n'est pas optionnel.
Entre les deux balises hibernate-mapping, incluez un élément class. Toutes les classes d'entités persistantes (encore une fois, il pourrait y avoir des classes dépendantes plus tard, qui ne sont pas des entités mère) ont besoin d'un mapping vers une table de la base de données SQL :
<hibernate-mapping>
<class name="Event" table="EVENTS">
</class>
</hibernate-mapping>Plus loin, nous disons à Hibernate comment persister et charger un objet de la classe Event dans la table EVENTS, chaque instance est représentée par une ligne dans cette table. Maintenant nous continuons avec le mapping de la propriété de l'identifiant unique vers la clef primaire de la table. De plus, comme nous ne voulons pas nous occuper de la gestion de cet identifiant, nous utilisons une stratégie de génération d'identifiant d'Hibernate pour la colonne de la clef primaire subrogée :
<hibernate-mapping>
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="increment"/>
</id>
</class>
</hibernate-mapping>L'élément id est la déclaration de la propriété de l'identifiant, name="id" déclare le nom de la propriété Java - Hibernate utilisera les méthodes getter et setter pour accéder à la propriété. L'attribut column indique à Hibernate quelle colonne de la table EVENTS nous utilisons pour cette clef primaire. L'élément generator imbriqué spécifie la stratégie de génération de l'identifiant, dans ce cas nous avons utilisé increment, laquelle est une méthode très simple utile surtout pour les tests (et didacticiels). Hibernate supporte aussi les identifiants générés par les bases de données, globalement uniques, ainsi que les identifiants assignés par l'application (ou n'importe quelle stratégie que vous avez écrit en extension).
Finalement nous incluons des déclarations pour les propriétés persistantes de la classe dans le fichier de mapping. Par défaut, aucune propriété de la classe n'est considérée comme persistante :
<hibernate-mapping>
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="increment"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping>Comme avec l'élément id, l'attribut name de l'élément property indique à Hibernate quels getters/setters utiliser.
Pourquoi le mapping de la propriété date inclut l'attribut column, mais pas title ? Sans l'attribut column Hibernate utilise par défaut le nom de la propriété comme nom de colonne. Ca fonctionne bien pour title. Cependant, date est un mot clef réservé dans la plupart des bases de données, donc nous utilisons un nom différent pour le mapping.
La prochaine chose intéressante est que le mapping de title manque aussi d'un attribut type. Les types que nous déclarons et utilisons dans les fichiers de mapping ne sont pas, comme vous pourriez vous y attendre, des types de données Java. Ce ne sont pas, non plus, des types de base de données SQL. Ces types sont donc appelés des types de mapping Hibernate, des convertisseurs qui peuvent traduire des types Java en types SQL et vice versa. De plus, Hibernate tentera de déterminer la bonne conversion et le type de mapping lui-même si l'attribut type n'est pas présent dans le mapping. Dans certains cas, cette détection automatique (utilisant la réflexion sur la classe Java) pourrait ne pas donner la valeur attendue ou dont vous avez besoin. C'est le cas avec la propriété date. Hibernate ne peut pas savoir si la propriété "mappera" une colonne SQL de type date, timestamp ou time. Nous déclarons que nous voulons conserver des informations avec une date complète et l'heure en mappant la propriété avec un timestamp.
Ce fichier de mapping devrait être sauvegardé en tant que Event.hbm.xml, juste dans le répertoire à côté du fichier source de la classe Java Event. Le nommage des fichiers de mapping peut être arbitraire, cependant le suffixe hbm.xml est devenu une convention dans la communauté des développeurs Hibernate. La structure du répertoire devrait ressembler à ça :
. +lib <Hibernate et bibliothèques tierces> +src Event.java Event.hbm.xml
Nous poursuivons avec la configuration principale d'Hibernate.
Nous avons maintenant une classe persistante et son fichier de mapping. Il est temps de configurer Hibernate. Avant ça, nous avons besoin d'une base de données. HSQL DB, un SGBD SQL basé sur Java et travaillant en mémoire, peut être téléchargé à partir du site web de HSQL. En fait, vous avez seulement besoin de hsqldb.jar. Placez ce fichier dans le répertoire lib/ du dossier de développement.
Créez un répertoire appelé data à 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 java -classpath lib/hsqldb.jar org.hsqldb.Server dans votre répertoire de travail. 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 CTRL + C dans la fenêtre the window), effacez le répertoire data/ et redémarrez HSQL DB à nouveau.
Hibernate est la couche de votre application qui se connecte à cette base de données, donc il a besoin des informations de connexion. Les connexions sont établies à travers un pool de connexions JDBC, que nous devons aussi configurer. La distribution Hibernate contient différents outils de gestion de pools de connexions JDBC open source, mais pour ce didacticiel nous utiliserons le pool de connexions intégré à Hibernate. Notez que vous devez copier les bibliothèques requises dans votre classpath et utiliser une configuration de pool de connexions différente si vous voulez utiliser un logiciel de gestion de pools JDBC tiers avec une qualité de production.
Pour la configuration d'Hibernate, nous pouvons utiliser un simple fichier hibernate.properties, un fichier hibernate.cfg.xml légèrement plus sophistiqué, ou même une configuration complète par programmation. La plupart des utilisateurs préfèrent le fichier de configuration XML :
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="events/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration>Notez que cette configuration XML utilise une DTD différente. Nous configurons une SessionFactory d'Hibernate - une fabrique globale responsable d'une base de données particulière. Si vous avez plusieurs base de données, utilisez plusieurs configurations <session-factory>, généralement dans des fichiers de configuration différents (pour un démarrage plus facile).
Les quatre premiers éléments property contiennent la configuration nécessaire pour la connexion JDBC. L'élément property du dialecte spécifie quelle variante du SQL Hibernate va générer. La gestion automatique des sessions d'Hibernate pour les contextes de persistance sera détaillée très vite. L'option hbm2ddl.auto active la génération automatique des schémas de base de données - directement dans la base de données. Cela peut bien sûr aussi être désactivé (en supprimant l'option de configuration) ou redirigé vers un fichier avec l'aide de la tâche Ant SchemaExport. Finalement, nous ajoutons le(s) fichier(s) de mapping pour les classes persistantes.
Copiez ce fichier dans le répertoire source, il terminera dans la racine du classpath. Hibernate cherchera automatiquement, au démarrage, un fichier appelé hibernate.cfg.xml dans la racine du classpath.
Nous allons maintenant construire le didacticiel avec Ant. Vous aurez besoin d'avoir Ant d'installé - récupérez-le à partir de la page de téléchargement de Ant. Comment installer Ant ne sera pas couvert ici. Référez-vous au manuel d'Ant. Après que vous aurez installé Ant, nous pourrons commencer à créer le fichier de construction. Il s'appellera build.xml et sera placé directement dans le répertoire de développement.
Un fichier de construction basique ressemble à ça :
<project name="hibernate-tutorial" default="compile">
<property name="sourcedir" value="${basedir}/src"/>
<property name="targetdir" value="${basedir}/bin"/>
<property name="librarydir" value="${basedir}/lib"/>
<path id="libraries">
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>
</path>
<target name="clean">
<delete dir="${targetdir}"/>
<mkdir dir="${targetdir}"/>
</target>
<target name="compile" depends="clean, copy-resources">
<javac srcdir="${sourcedir}"
destdir="${targetdir}"
classpathref="libraries"/>
</target>
<target name="copy-resources">
<copy todir="${targetdir}">
<fileset dir="${sourcedir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
</project>Cela dira à Ant d'ajouter tous les fichiers du répertoire lib finissant par .jar dans le classpath utilisé pour la compilation. Cela copiera aussi tous les fichiers source non Java dans le répertoire cible, par exemple les fichiers de configuration et de mapping d'Hibernate. Si vous lancez Ant maintenant, vous devriez obtenir cette sortie :
C:\hibernateTutorial\>ant
Buildfile: build.xml
copy-resources:
[copy] Copying 2 files to C:\hibernateTutorial\bin
compile:
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
BUILD SUCCESSFUL
Total time: 1 second Il est temps de charger et de stocker quelques objets Event, mais d'abord nous devons compléter la configuration avec du code d'infrastructure. Nous devons démarrer Hibernate. Ce démarrage inclut la construction d'un objet SessionFactory global et le stocker quelque part facile d'accès dans le code de l'application. Une SessionFactory peut ouvrir des nouvelles Sessions. Une Session représente une unité de travail simplement "threadée", la SessionFactory est un objet global "thread-safe", instancié une seule fois.
Nous créerons une classe d'aide HibernateUtil qui s'occupe du démarrage et rend la gestion des Sessions plus facile. Regardons l'implémentation :
package util;
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
public static final SessionFactory sessionFactory;
static {
try {
// Création de la SessionFactory à partir de hibernate.cfg.xml
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}Cette classe ne produit pas seulement la SessionFactory globale dans un initialiseur statique (appelé une seule fois par la JVM lorsque la classe est chargée), elle masque le fait qu'elle exploite un singleton. Elle pourrait aussi obtenir la SessionFactory depuis JNDI dans un serveur d'applications.
Si vous nommez la SessionFactory dans votre fichier de configuration, Hibernate tentera la récupération depuis JNDI. Pour éviter ce code, vous pouvez aussi utiliser un déploiement JMX et laisser le conteneur (compatible JMX) instancier et lier un HibernateService à JNDI. Ces options avancées sont détaillées dans la documentation de référence Hibernate.
Placez HibernateUtil.java dans le répertoire source de développement, et ensuite Event.java :
.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
Event.hbm.xml
+util
HibernateUtil.java
hibernate.cfg.xml
+data
build.xmlCela devrait encore compiler sans problème. Nous avons finalement besoin de configurer le système de "logs" - Hibernate utilise commons-logging et vous laisse le choix entre log4j et le système de logs du JDK 1.4. La plupart des développeurs préfèrent log4j : copiez log4j.properties de la distribution d'Hibernate (il est dans le répertoire etc/) dans votre répertoire src, puis faites de même avec hibernate.cfg.xml. Regardez la configuration d'exemple et changez les paramètres si vous voulez une sortie plus verbeuse. Par défaut, seul le message de démarrage d'Hibernate est affiché sur la sortie standard.
L'infrastructure de ce didacticiel est complète - et nous sommes prêts à effectuer un travail réel avec Hibernate.
Finalement nous pouvons utiliser Hibernate pour charger et stocker des objets. Nous écrivons une classe EventManager avec une méthode main() :
package events;
import org.hibernate.Session;
import java.util.Date;
import util.HibernateUtil;
public class EventManager {
public static void main(String[] args) {
EventManager mgr = new EventManager();
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
private void createAndStoreEvent(String title, Date theDate) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
}Nous créons un nouvel objet Event, et le remettons à Hibernate. Hibernate s'occupe maintenant du SQL et exécute les INSERTs dans la base de données. Regardons le code de gestion de la Session et de la Transaction avant de lancer ça.
Une Session est une unité de travail. Pour le moment, nous allons faire les choses simplement et assumer une granularité un-un entre une Session hibernate et une transaction à la base de données. Pour isoler notre code du système de transaction sous-jacent (dans notre cas, du pure JDBC, mais cela pourrait être JTA), nous utilisons l'API Transaction qui est disponible depuis la Session Hibernate.
Que fait sessionFactory.getCurrentSession() ? Premièrement, vous pouvez l'invoquer autant de fois que vous le voulez et n'importe où du moment que vous avez votre SessionFactory (facile grâce à HibernateUtil). La méthode getCurrentSession() 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 hibernate.cfg.xml. 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 Session commence lorsqu'elle est vraiment utilisée la première fois, Lorsque nous appelons pour la première fois getCurrentSession(). Ensuite, elle est liée, par Hibernate, au thread courant. Lorsque la transaction s'achève (commit ou rollback), Hibernate délie la Session du thread et la ferme pour vous. Si vous invoquez getCurrentSession() une autre fois, vous obtenez une nouvelle Session et pouvez entamer une nouvelle unité de travail. Ce modèle de programmation "thread-bound" est le moyen le plus populaire d'utiliser Hibernate.
Lisez Chapitre 11, Transactions et accès concurrents pour plus d'informations sur la gestion des transactions et leur démarcations. Nous n'avons pas géré les erreurs et rollback sur l'exemple précédent.
Pour lancer cette première routine, nous devons ajouter une cible appelable dans le fichier de construction de Ant :
<target name="run" depends="compile">
<java fork="true" classname="events.EventManager" classpathref="libraries">
<classpath path="${targetdir}"/>
<arg value="${action}"/>
</java>
</target>La valeur de l'argument action correspond à la ligne de commande qui appelle la cible :
C:\hibernateTutorial\>ant run -Daction=store
Vous devriez voir, après la compilation, Hibernate démarrer et, en fonction de votre configuration, beaucoup de traces sur la sortie. À la fin vous trouverez la ligne suivante :
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
C'est l'INSERT exécuté par Hibernate, les points d'interrogation représentent les paramètres JDBC liés. Pour voir les valeurs liées aux arguments, ou pour réduire la verbosité des traces, vérifier votre log4j.properties.
Maintenant nous aimerions aussi lister les événements stockés, donc nous ajoutons une option à la méthode principale :
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println("Event: " + theEvent.getTitle() +
" Time: " + theEvent.getDate());
}
}Nous ajoutons aussi une nouvelle méthode listEvents() :
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}Ce que nous faisons ici c'est utiliser une requête HQL (Hibernate Query Language) pour charger tous les objets Event existants de la base de données. Hibernate générera le SQL approprié, l'enverra à la base de données et peuplera des objets Event avec les données. Vous pouvez créer des requêtes plus complexes avec HQL, bien sûr.
Maintenant, pour exécuter et tester tout ça, suivez ces étapes :
Exécutez ant run -Daction=store pour stocker quelque chose dans la base de données et, bien sûr, pour générer, avant, le schéma de la base de données grâce à hbm2ddl.
Maintenant désactivez hbm2ddl en commentant la propriété dans votre fichier hibernate.cfg.xml. Généralement vous la laissez seulement activée dans des tests unitaires en continu, mais une autre exécution de hbm2ddl effacerait tout ce que vous avez stocké - le paramètre de configuration create se traduit en fait par "supprimer toutes les tables du schéma, puis re-créer toutes les tables, lorsque la SessionFactory est construite".
Si maintenant vous appelez Ant avec -Daction=list, vous devriez voir les événements que vous avez stockés jusque là. Vous pouvez bien sûr aussi appeler l'action store plusieurs fois.
Nous avons mappé une classe d'une entité persistante vers une table. Partons de là et ajoutons quelques associations de classe. D'abord nous ajouterons des gens à notre application, et stockerons une liste d'événements auxquels ils participent.
La première version de la classe Person est simple :
package events;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
public Person() {}
// Accessor methods for all properties, private setter for 'id'
}Créez un nouveau fichier de mapping appelé Person.hbm.xml (n'oubliez pas la référence à la DTD)
<hibernate-mapping>
<class name="events.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
</hibernate-mapping>Finalement, ajoutez la nouveau mapping à la configuration d'Hibernate :
<mapping resource="events/Event.hbm.xml"/> <mapping resource="events/Person.hbm.xml"/>
Nous allons maintenant créer une association entre ces deux entités. Évidemment, des personnes peuvent participer aux événements, et des événements ont des participants. Les questions de conception que nous devons traiter sont : direction, cardinalité et comportement de la collection.
Nous allons ajouter une collection d'événements à la classe Person. De cette manière nous pouvons facilement naviguer dans les événements d'une personne particulière, sans exécuter une requête explicite - en appelant aPerson.getEvents(). Nous utilisons une collection Java, un Set, parce que la collection ne contiendra pas d'éléments dupliqués et l'ordre ne nous importe pas.
Nous avons besoin d'une association unidirectionnelle, pluri-valuée, implémentée avec un Set. Écrivons le code pour ça dans les classes Java et mappons les :
public class Person {
private Set events = new HashSet();
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
}D'abord nous mappons cette association, mais pensez à l'autre côté. Clairement, nous pouvons la laisser unidirectionnelle. Ou alors, nous pourrions créer une autre collection sur Event, si nous voulons être capable de la parcourir de manière bidirectionnelle, c'est-à-dire avoir anEvent.getParticipants(). Ce n'est pas nécessaire d'un point de vue fonctionnel. Vous pourrez toujours exécuter une requête explicite pour récupérer les participants d'un "event" particulier. Ce choix de conception vous est laissé, mais ce qui reste certains est la cardinalité de l'association: "plusieurs" des deux côtés, nous appelons cela une association many-to-many. Par conséquent nous utilisons un mapping Hibernate many-to-many:
<class name="events.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
</class>Hibernate supporte toutes sortes de mapping de collection, un <set> étant le plus commun. Pour une association many-to-many (ou une relation d'entité n:m), une table d'association est requise. Chaque ligne dans cette table représente un lien entre une personne et un événement. Le nom de la table est configuré avec l'attribut table de l'élément set. Le nom de la colonne identifiant dans l'association, du côté de la personne, est défini avec l'élément <key>, et le nom de la colonne pour l'événement dans l'attribut column de <many-to-many>. Vous devez aussi donner à Hibernate la classe des objets de votre collection (c'est-à-dire : la classe de l'autre côté de la collection).
Le schéma de base de données pour ce mapping est donc :
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
Réunissons quelques personnes et quelques événements dans une nouvelle méthode dans EventManager :
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
Event anEvent = (Event) session.load(Event.class, eventId);
aPerson.getEvents().add(anEvent);
session.getTransaction().commit();
}Après le chargement d'une Person et d'un Event, modifiez simplement la collection en utilisant les méthodes normales de la collection. Comme vous pouvez le voir, il n'y a pas d'appel explicite à update() ou save(), Hibernate détecte automatiquement que la collection a été modifiée et a besoin d'être mise à jour. Ceci est appelé la vérification sale automatique (NdT : "automatic dirty checking"), et vous pouvez aussi l'essayer en modifiant le nom ou la propriété date de n'importe lequel de vos objets. Tant qu'ils sont dans un état persistant, c'est-à-dire, liés à une Session Hibernate particulière (c-à-d qu'ils ont juste été chargés ou sauvegardés dans une unité de travail), Hibernate surveille les changements et exécute le SQL correspondant. Le processus de synchronisation de l'état de la mémoire avec la base de données, généralement seulement à la fin d'une unité de travail, est appelé flushing. Dans notre code, l'unité de travail s'achève par un commit (ou rollback) de la transaction avec la base de données - comme défini par notre option thread de configuration pour la classe CurrentSessionContext.
Vous pourriez bien sûr charger une personne et un événement dans différentes unités de travail. Ou vous modifiez un objet à l'extérieur d'une Session, s'il n'est pas dans un état persistant (s'il était persistant avant, nous appelons cet état détaché). Vous pouvez même modifier une collection lorsqu'elle est détachée:
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent = (Event) session.load(Event.class, eventId);
session.getTransaction().commit();
// End of first unit of work
aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}L'appel à update rend un objet détaché à nouveau persistant, vous pourriez dire qu'il le lie à une unité de travail, ainsi toutes les modifications (ajout, suppression) que vous avez faites pendant qu'il était détaché peuvent être sauvegardées dans la base de données (il se peut que vous ayez besoin de modifier quelques unes des méthodes précédentes pour retourner cet identifiant).
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);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 EventManagers 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 save() les retourne.
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, généralement "moins importants". Vous en avez déjà vu certains, comme un int ou une String. Nous appelons ces classes des types de valeur, et leurs instances dépendent d'une entité particulière. Des instances de ces types n'ont pas leur propre identité, elles ne sont pas non plus partagées entre des entités (deux personnes ne référencent pas le même objet firstname, même si elles ont le même prénom). Bien sûr, des types de valeur ne peuvent pas seulement être trouvés dans le JDK (en fait, dans une application Hibernate toutes les classes du JDK sont considérées comme des types de valeur), vous pouvez aussi écrire vous-même des classes dépendantes, Address ou MonetaryAmount, par exemple.
Vous pouvez aussi concevoir une collection de types de valeur. C'est conceptuellement très différent d'une collection de références vers d'autres entités, mais très ressemblant en Java.
Nous ajoutons une collection d'objets de type de valeur à l'entité Person. Nous voulons stocker des adresses email, donc le type que nous utilisons est String, et la collection est encore un Set :
private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses = emailAddresses;
}Le mapping de ce Set :
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set>La différence comparée au mapping vu plus tôt est la partie element, laquelle dit à Hibernate que la collection ne contient pas de références vers une autre entité, mais une collection d'éléments de type String (le nom en minuscule vous indique que c'est un type/convertisseur du mapping Hibernate). Une fois encore, l'attribut table de l'élément set détermine le nom de la table pour la collection. L'élément key définit le nom de la colonne de la clef étrangère dans la table de la collection. L'attribut column dans l'élément element définit le nom de la colonne où les valeurs de String seront réellement stockées.
Regardons le schéma mis à jour :
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | | ___________________
|_____________| |__________________| | PERSON | | |
| | | | |_____________| | PERSON_EMAIL_ADDR |
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|_____________| | FIRSTNAME | |___________________|
| LASTNAME |
|_____________|
Vous pouvez voir que la clef primaire de la table de la collection est en fait une clef composée, utilisant deux colonnes. Ceci implique aussi qu'il ne peut pas y avoir d'adresses email dupliquées par personne, ce qui est exactement la sémantique dont nous avons besoin pour un ensemble en Java.
Vous pouvez maintenant tester et ajouter des éléments à cette collection, juste comme nous l'avons fait avant en liant des personnes et des événements. C'est le même code en Java.
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// The getEmailAddresses() might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}Cette fois ci, nous n'avons pas utilisé une requête de chargement agressif (fetch) pour initialiser la collection. Par conséquent, l'invocation du getter déclenchera un select supplémentaire pour l'initialiser. Traquez les logs SQL et tentez d'optimiser ce cas avec un chargement aggressif.
Ensuite nous allons mapper une association bidirectionnelle - faire fonctionner l'association entre une personne et un événement à partir des deux côtés en Java. Bien sûr, le schéma de la base de données ne change pas, nous avons toujours une pluralité many-to-many. Une base de données relationnelle est plus flexible qu'un langage de programmation réseau, donc elle n'a pas besoin de direction de navigation - les données peuvent être vues et récupérées de toutes les manières possibles.
D'abord, ajouter une collection de participants à la classe Event :
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}Maintenant mapper ce côté de l'association aussi, dans Event.hbm.xml.
<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="events.Person"/>
</set>Comme vous le voyez, ce sont des mappings de sets normaux dans les deux documents de mapping. Notez que les noms de colonne dans key et many-to-many sont inversés dans les 2 documents de mapping. L'ajout le plus important ici est l'attribut inverse="true" dans l'élément set du mapping de la collection des Events.
Ce que signifie qu'Hibernate devrait prendre l'autre côté - la classe Person - s'il a besoin de renseigner des informations à propos du lien entre les deux. Ce sera beaucoup plus facile à comprendre une fois que vous verrez comment le lien bidirectionnel entre les deux entités est créé.
Premièrement, gardez à l'esprit qu'Hibernate n'affecte pas la sémantique normale de Java. Comment avons-nous créé un lien entre une Person et un Event dans l'exemple unidirectionnel ? Nous avons ajouté une instance de Event à la collection des références d'événement d'une instance de Person. Donc, évidemment, si vous voulons rendre ce lien bidirectionnel, nous devons faire la même chose de l'autre côté - ajouter une référence de Person à la collection d'un Event. Cette "configuration du lien des deux côtés" est absolument nécessaire et vous ne devriez jamais oublier de le faire.
Beaucoup de développeurs programment de manière défensive et créent des méthodes de gestion de lien pour affecter correctement les deux côtés, par exemple dans Person :
protected Set getEvents() {
return events;
}
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}Notez que les méthodes get et set pour la collection sont maintenant protégées - ceci permet à des classes du même paquet et aux sous-classes d'accéder encore aux méthodes, mais empêche n'importe qui d'autre de mettre le désordre directement dans les collections (enfin, presque). Vous devriez probablement faire de même avec la collection de l'autre côté.
Et à propos de l'attribut de mapping inverse ? Pour vous, et pour Java, un lien bidirectionnel est simplement une manière de configurer correctement les références des deux côtés. Hibernate n'a cependant pas assez d'informations pour ordonner correctement les expressions SQL INSERT et UPDATE (pour éviter les violations de contrainte), et a besoin d'aide pour gérer proprement les associations bidirectionnelles. Rendre inverse un côté d'une assocation dit à Hibernate de l'ignorer essentiellement, pour le considérer comme un miroir de l'autre côté. C'est tout ce qui est nécessaire à Hibernate pour découvrir tout des problèmes de transformation d'un modèle de navigation directionnelle vers un schéma SQL de base de données. Les règles dont vous devez vous souvenir sont : toutes les associations bidirectionnelles ont besoin d'un côté marqué inverse. Dans une association un-vers-plusieurs vous pouvez choisir n'importe quel côté, il n'y a pas de différence.
Une application web Hibernate utilise la Session et Transaction comme une application standalone. Cependant, quelques patterns sont utiles. Nous allons coder une EventManagerServlet. Cette servlet peut lister tous les évènements stockés dans la base de données, et fournir une formulaire HTML pour saisir d'autres évènements.
Créons une nouvelle classe dans notre répertoire source, dans le package events:
package events;
// Imports
public class EventManagerServlet extends HttpServlet {
private final SimpleDateFormat dateFormatter =
new SimpleDateFormat("dd.MM.yyyy");
// Servlet code
}Le dateFormatter est un outil que nous utiliserons plus tard pour convertir les objets Date depuis et vers des chaines de caractères. Il est propice de n'avoir qu'un formatter comme membre de la servlet.
La servlet n'accepte que les requêtes HTTP GET, la méthode à implémenter est donc doGet():
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try {
// Begin unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
// Process request and render page...
// End unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
} catch (Exception ex) {
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().rollback();
throw new ServletException(ex);
}
}La pattern que nous utilisons ici est appelé session-per-request. Lorsqu'une requête touche la servlet, une nouvelle Session hibernate est ouverte à l'invocationde getCurrentSession() sur la SessionFactory. Ensuite, une transaction avec la base de données est démarrée— tous les accès à la base de données interviennent au sein de la transactiton, peu importe que les données soient lues ou écrites (nous n'utilisons pas le mode auto-commit dans les applications).
Ensuite, les actions possibles de la requêtes sont exécutées et la réponse HTML est rendue. Nous en parlerons plus tard.
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 session-per-request. Au lieu d'un code de démarcation de transaction 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é Open Session in View— vous en aurez besoin dès que vous utiliserez des JSPs et non plus des servlets pour le rendu de vos vues.
Implémentons l'exécution de la requête et le rendu de la page.
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
// Handle actions
if ( "store".equals(request.getParameter("action")) ) {
String eventTitle = request.getParameter("eventTitle");
String eventDate = request.getParameter("eventDate");
if ( "".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b><i>Please enter event title and date.</i></b>");
} else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Added event.</i></b>");
}
}
// Print page
printEventForm(out);
listEvents(out);
// Write HTML footer
out.println("</body></html>");
out.flush();
out.close();Ce style de code avec un mix de Java et d'HTML ne serait pas scalable dans une application plus complexe—gardez à l'esprit que nous ne faisons qu'illustrer les concepts basiques d'Hibernate dans ce tutoriel. Ce code affiche une en tête et un pied de page HTML. Dans cette page, sont affichés un formulaire pour la saisie d'évènements ainsi qu'une liste de tous les évènements de la base de données. La première méthode est triviale est ne fait que sortir de l'HTML:
private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name='eventTitle' length='50'/><br/>");
out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action' value='store'/>");
out.println("</form>");
}La méthode listEvents() utilise la Session Hibernate liée au thread courant pour exécuter la requête:
private void listEvents(PrintWriter out) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border='1'>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
for (Iterator it = result.iterator(); it.hasNext();) {
Event event = (Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() + "</td>");
out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}FEnfin, l'action store renvoie à la méthode createAndStoreEvent(), qui utilise aussi la Session du thread courant:
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}La servlet est faite. Une requête à la servlet sera exécutée par une seule Session et Transaction. Comme pour une application standalone, Hibernate peut automatiquement lier ces objets au thread courant d'exécution. Cela vous laisse la liberté de séparer votre code en couches et d'accéder à la SessionFactory par le moyen que vous voulez. Généralement, vous utiliserez des conceptions plus sophistiquées et déplacerez le code d'accès aux données dans une couche DAO. Voir le wiki Hibernate pour plus d'exemples.
Pour déployer cette application, vous devez créer une archive Web, un War. Ajoutez la cible Ant suivante dans votre build.xml:
<target name="war" depends="compile">
<war destfile="hibernate-tutorial.war" webxml="web.xml">
<lib dir="${librarydir}">
<exclude name="jsdk*.jar"/>
</lib>
<classes dir="${targetdir}"/>
</war>
</target>Cette cible créé un fichier nommé hibernate-tutorial.war dans le répertoire de votre projet. Elle package les bibliothèques et le descripteur web.xml qui est attendu dans le répertoire racine de votre projet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>Event Manager</servlet-name>
<servlet-class>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>Avant de compiler et déployer l'application web, notez qu'une bibliothèque supplémentaire est requise: jsdk.jar. C'est le kit de développement de Servlet Java, si vous ne disposez pas de cette bibliothèque, prenez la sur le site de Sun et copiez la dans votre répertoire des bibliothèques. Cependant, elle ne sera utilisée uniquement pour la compilation et sera exclue du paackage WAR.
Pour construire et déployer, appelez ant war dans votre projet et copier le fichier hibernate-tutorial.war dans le répertoire webapp de tomcat Si vous n'avez pas installé Tomcat, téléchargez le et suivez la notice d'installation. Vous n'avez pas à modifier la configuration Tomcat pour déployer cette application.
Une fois l'application déployée et Tomcat lancé, accédez à l'application via http://localhost:8080/hibernate-tutorial/eventmanager. Assurez vous de consulter les traces tomcat pour observer l'initialisation d'Hibernate à la première requête touchant votre servlet (l'initialisation statique dans HibernateUtil est invoquée) et pour vérifier qu'aucune exception ne survienne.
Ce didacticiel a couvert les bases de l'écriture d'une simple application Hibernate ainsi qu'une petite application web.
Si vous êtes déjà confiants avec Hibernate, continuez à parcourir les sujets que vous trouvez intéressants à travers la table des matières de la documentation de référence - les plus demandés sont le traitement transactionnel (Chapitre 11, Transactions et accès concurrents), la performance des récupérations d'information (Chapitre 19, Améliorer les performances), ou l'utilisation de l'API (Chapitre 10, Travailler avec des objets) et les fonctionnalités des requêtes (Section 10.4, « Requêtage »).
N'oubliez pas de vérifier le site web d'Hibernate pour d'autres didacticiels (plus spécialisés).
Voici une vue (très) haut niveau de l'architecture d'Hibernate :

Ce diagramme montre Hibernate utilisant une base de données et des données de configuration pour fournir un service de persistance (et des objets persistants) à l'application.
Nous aimerions décrire une vue plus détaillée de l'architecture. Malheureusement, Hibernate est flexible et supporte différentes approches. Nous allons en montrer les deux extrêmes. L'architecture légère laisse l'application fournir ses propres connexions JDBC et gérer ses propres transactions. Cette approche utilise le minimum des APIs Hibernate :

L'architecture la plus complète abstrait l'application des APIs JDBC/JTA sous-jacentes et laisse Hibernate s'occuper des détails.

Voici quelques définitions des objets des diagrammes :
Un cache threadsafe (immuable) des mappings vers une (et une seule) base de données. Une factory (fabrique) de Session et un client de ConnectionProvider. Peut contenir un cache optionnel de données (de second niveau) qui est réutilisable entre les différentes transactions que cela soit au sein du même processus (JVLM) ou par plusieurs n½uds d'un cluster.
Un objet mono-threadé, à durée de vie courte, qui représente une conversation entre l'application et l'entrepôt de persistance. Encapsule une connexion JDBC. Factory (fabrique) des objets Transaction. Contient un cache (de premier niveau) des objets persistants, ce cache est obligatoire. Il est utilisé lors de la navigation dans le graphe d'objets ou lors de la récupération d'objets par leur identifiant.
Objets mono-threadés à vie courte contenant l'état de persistance et la fonction métier. Ceux-ci sont en général les objets de type JavaBean (ou POJOs) ; la seule particularité est qu'ils sont associés avec une (et une seule) Session. Dès que la Session est fermée, ils seront détachés et libres d'être utilisés par n'importe laquelle des couches de l'application (ie. de et vers la présentation en tant que Data Transfer Objects - DTO : objet de transfert de données).
Instances de classes persistantes qui ne sont actuellement pas associées à une Session. Elles ont pu être instanciées par l'application et ne pas avoir (encore) été persistées ou elle ont pu être instanciées par une Session fermée.
(Optionnel) Un objet mono-threadé à vie courte utilisé par l'application pour définir une unité de travail atomique. Abstrait l'application des transactions sous-jacentes qu'elles soient JDBC, JTA ou CORBA. Une Session peut fournir plusieurs Transactions dans certains cas. Toutefois, la délimitation des transactions, via l'API d'Hibernate ou par la Transaction sous-jacente, n'est jamais optionnelle!
(Optionnel) Une fabrique de (pool de) connexions JDBC. Abstrait l'application de la Datasource ou du DriverManager sous-jacent. Non exposé à l'application, mais peut être étendu/implémenté par le développeur.
(Optionnel) Une fabrique d'instances de Transaction. Non exposé à l'application, mais peut être étendu/implémenté par le développeur.
Hibernate fournit de nombreuses interfaces d'extensions optionnelles que vous pouvez implémenter pour personnaliser le comportement de votre couche de persistance. Reportez vous à la documentation de l'API pour plus de détails.
Dans une architecture légère, l'application n'aura pas à utiliser les APIs Transaction/TransactionFactory et/ou n'utilisera pas les APIs ConnectionProvider pour utiliser JTA ou JDBC.
Une instance d'une classe persistante peut être dans l'un des trois états suivants, définis par rapport à un contexte de persistance. L'objet Session d'hibernate correspond à ce concept de contexte de persistance :
L'instance n'est pas et n'a jamais été associée à un contexte de persistance. Elle ne possède pas d'identité persistante (valeur de clé primaire)
L'instance est associée au contexte de persistance. Elle possède une identité persistante (valeur de clé primaire) et, peut-être, un enregistrement correspondant dans la base. Pour un contexte de persistance particulier, Hibernate garantit que l'identité persistante est équivalente à l'identité Java (emplacement mémoire de l'objet)
L'instance a été associée au contexte de persistance mais ce contexte a été fermé, ou l'instance a été sérialisée vers un autre processus. Elle possède une identité persistante et peut-être un enregistrement correspondant dans la base. Pour des instances détachées, Hibernate ne donne aucune garantie sur la relation entre l'identité persistante et l'identité Java.
JMX est le standard J2EE de gestion des composants Java. Hibernate peut être géré via un service JMX standard. Nous fournissons une implémentation d'un MBean dans la distribution : org.hibernate.jmx.HibernateService.
Pour avoir un exemple sur la manière de déployer Hibernate en tant que service JMX dans le serveur d'application JBoss Application Server, référez vous au guide utilisateur JBoss (JBoss User Guide). Si vous déployez Hibernate via JMX sur JBoss AS, vous aurez également les bénéfices suivants :
Gestion de la session : Le cycle de vie de la Session Hibernate peut être automatiquement limitée à la portée d'une transaction JTA. Cela signifie que vous n'avez plus besoin d'ouvrir et de fermer la Session manuellement, cela devient le travail de l'intercepteur EJB de JBoss. Vous n'avez pas non plus à vous occuper des démarcations des transactions dans votre code (sauf si vous voulez écrire une couche de persistance qui soit portable, dans ce cas vous pouvez utiliser l'API optionnelle Transaction d'Hibernate). Vous appelez l'HibernateContext pour accéder à la Session.
Déploiement HAR : Habituellement vous déployez le service JMX Hibernate en utilisant le descripteur de déploiement de JBoss (dans un fichier EAR et/ou un SAR), il supporte toutes les options de configuration usuelles d'une SessionFactory Hibernate. Cependant, vous devez toujours nommer tous vos fichiers de mapping dans le descripteur de déploiement. Si vous décidez d'utiliser le déploiement optionnel sous forme de HAR, JBoss détectera automatiquement tous vos fichiers de mapping dans votre fichier HAR.
Consultez le guide d'utilisation de JBoss AS pour plus d'informations sur ces options.
Les statistiques pendant l'exécution d'Hibernate (au runtime) sont une autre fonctionnalité disponible en tant que service JMX. Voyez pour cela Section 3.4.6, « Statistiques Hibernate ».
Hibernate peut aussi être configuré en tant que connecteur JCA. Référez-vous au site web pour de plus amples détails. Il est important de noter que le support JCA d'Hibernate est encore considéré comme expérimental.
Certaines applications utilisant Hibernate ont besoin d'une sorte de session "contextuelle", où une session est liée à la portée d'un contexte particulier. Cependant, les applications ne définissent pas toutes la notion de contexte de la même manière, et différents contextes définissent différentes portées à la notion de "courant". Les applications à base d'Hibernate, versions précédentes à la 3.0 utilisaient généralement un principe maison de sessions contextuelles basées sur le ThreadLocal, ainsi que sur des classes utilitaires comme HibernateUtil, ou utilisaient des framework tiers (comme Spring ou Pico) qui fournissaient des sessions contextuelles basées sur l'utilisation de proxy/interception.
A partir de la version 3.0.1, Hibernate a ajouté la méthode SessionFactory.getCurrentSession(). Initialement, cela demandait l'usage de transactions JTA, où la transaction JTA définissait la portée et le contexte de la session courante. L'équipe Hibernate pense que, étant donnée la maturité des implémentations de JTA TransactionManager , la plupart (sinon toutes) des applications devraient utiliser la gestion des transactions par JTA qu'elles soient ou non déployées dans un conteneur J2EE. Par conséquent, vous devriez toujours contextualiser vos sessions, si vous en avez besoin, via la méthode basée sur JTA.
Cependant, depuis la version 3.1, la logique derrière SessionFactory.getCurrentSession() est désormais branchable. A cette fin, une nouvelle interface d'extension (org.hibernate.context.CurrentSessionContext) et un nouveau paramètre de configuration (hibernate.current_session_context_class) ont été ajoutés pour permettre de configurer d'autres moyens de définir la portée et le contexte des sessions courantes.
Allez voir les Javadocs de l'interface org.hibernate.context.CurrentSessionContext pour une description détaillée de son contrat. Elle définit une seule méthode, currentSession(), depuis laquelle l'implémentation est responsable de traquer la session courante du contexte. Hibernate fournit deux implémentation de cette interface.
org.hibernate.context.JTASessionContext - les sessions courantes sont associées à une transaction JTA. La logique est la même que l'ancienne approche basée sur JTA. Voir les javadocs pour les détails.
org.hibernate.context.ThreadLocalSessionContext - les sessions courantes sont associées au thread d'exécution. Voir les javadocs pour les détails.
Les deux implémentations fournissent un modèle de programmation de type "une session - une transaction à la base de données", aussi connu sous le nom de session-per-request. Le début et la fin d'une session Hibernate sont définis par la durée d'une transaction de base de données. Si vous utilisez une démarcation programmatique de la transaction (par exemple sous J2SE ou JTA/UserTransaction/BMT), nous vous conseillons d'utiliser l'API Hibernate Transaction pour masquer le système de transaction utilisé. Si vous exécutez sous un conteneur EJB qui supporte CMT, vous n'avez besoin d'aucune opérations de démarcations de session ou transaction dans votre code puisque tout est géré de manière déclarative. Référez vous à Chapitre 11, Transactions et accès concurrents pour plus d'informations et des exemples de code.
Le paramètre de configuration hibernate.current_session_context_class définit quelle implémentation de org.hibernate.context.CurrentSessionContext doit être utilisée. Notez que pour assurer la compatibilité avec les versions précédentes, si ce paramètre n'est pas défini mais qu'un org.hibernate.transaction.TransactionManagerLookup est configuré, Hibernate utilisera le org.hibernate.context.JTASessionContext. La valeur de ce paramètre devrait juste nommer la classe d'implémentation à utiliser, pour les deux implémentations fournies, il y a cependant deux alias correspondant: "jta" et "thread".
Parce qu'Hibernate est conçu pour fonctionner dans différents environnements, il existe beaucoup de paramètres de configuration. Heureusement, la plupart ont des valeurs par défaut appropriées et la distribution d'Hibernate contient un exemple de fichier hibernate.properties dans le répertoire etc/ qui montre les différentes options. Vous n'avez qu'à placer ce fichier dans votre classpath et à l'adapter.
Une instance de org.hibernate.cfg.Configuration représente un ensemble de mappings des classes Java d'une application vers la base de données SQL. La Configuration est utilisée pour construire un objet (immuable) SessionFactory. Les mappings sont constitués d'un ensemble de fichiers de mapping XML.
Vous pouvez obtenir une instance de Configuration en l'instanciant directement et en spécifiant la liste des documents XML de mapping. Si les fichiers de mapping sont dans le classpath, vous pouvez le faire à l'aide de la méthode addResource() :
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");Une alternative (parfois meilleure) est de spécifier les classes mappées et de laisser Hibernate trouver les documents de mapping pour vous :
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);Hibernate va rechercher les fichiers de mappings /org/hibernate/auction/Item.hbm.xml et /org/hibernate/auction/Bid.hbm.xml dans le classpath. Cette approche élimine les noms de fichiers en dur.
Une Configuration vous permet également de préciser des propriétés de configuration :
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");Ce n'est pas le seul moyen de passer des propriétés de configuration à Hibernate. Les différentes options sont :
Passer une instance de java.util.Properties à Configuration.setProperties().
Placer hibernate.properties dans un répertoire racine du classpath
Positionner les propriétés System en utilisant java -Dproperty=value.
Inclure des éléments <property> dans le fichier hibernate.cfg.xml (voir plus loin).
L'utilisation d'hibernate.properties est l'approche la plus simple si vous voulez démarrer rapidement
La Configuration est un objet de démarrage qui sera supprimé une fois qu'une SessionFactory aura été créée.
Une fois que tous les mappings ont été parsés par la Configuration, l'application doit obtenir une fabrique d'instances de Session. Cette fabrique sera partagée entre tous les threads de l'application :
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate permet à votre application d'instancier plus d'une SessionFactory. Cela est pratique lorsque vous utilisez plus d'une base de données.
Habituellement, vous voulez que la SessionFactory crée les connexions JDBC et les mette dans un pool pour vous. Si vous suivez cette approche, ouvrir une Session est aussi simple que :
Session session = sessions.openSession(); // open a new Session
Dès que vous ferez quelquechose qui requiert un accès à la base de données, une connexion JDBC sera récupérée dans le pool.
Pour faire cela, il faut passer les propriétés de la connexion JDBC à Hibernate. Tous les noms des propriétés Hibernate et leur signification sont définies dans la classe org.hibernate.cfg.Environment. Nous allons maintenant décrire les paramètres de configuration des connexions JDBC les plus importants.
Hibernate obtiendra des connexions (et les mettra dans un pool) en utilisant java.sql.DriverManager si vous positionnez les paramètres de la manière suivante :
Tableau 3.1. Propriétés JDBC d'Hibernate
| Nom de la propriété | Fonction |
|---|---|
| hibernate.connection.driver_class | Classe du driver jdbc |
| hibernate.connection.url | URL jdbc |
| hibernate.connection.username | utilisateur de la base de données |
| hibernate.connection.password | mot de passe de la base de données |
| hibernate.connection.pool_size | nombre maximum de connexions dans le pool |
L'algorithme natif de pool de connexions d'Hibernate est plutôt rudimentaire. Il a été fait dans le but de vous aider à démarrer et n'est pas prévu pour un système en production ou même pour un test de peformance. Utilisez plutôt un pool tiers pour de meilleures performances et une meilleure stabilité : pour cela, remplacez la propriété hibernate.connection.pool_size avec les propriétés spécifique au pool de connexions que vous avez choisi. Cela désactivera le pool de connexions interne d'Hibernate. Vous pouvez par exemple utiliser C3P0.
C3P0 est un pool de connexions JDBC open source distribué avec Hibernate dans le répertoire lib. Hibernate utilisera son provider C3P0ConnectionProvider pour le pool de connexions si vous positionnez les propriétés hibernate.c3p0.*. Si vous voulez utiliser Proxool, référez vous au groupe de propriétés d'hibernate.properties correspondant et regardez sur le site web d'Hibernate pour plus d'informations.
Voici un exemple de fichier hibernate.properties pour C3P0:
hibernate.connection.driver_class = org.postgresql.Driver hibernate.connection.url = jdbc:postgresql://localhost/mydatabase hibernate.connection.username = myuser hibernate.connection.password = secret hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statement=50 hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
Dans le cadre de l'utilisation au sein d'un serveur d'applications, vous devriez quasiment toujours configurer Hibernate pour qu'il obtienne ses connexions de la DataSource du serveur d'application enregistrée dans le JNDI. Pour cela vous devrez définir au moins une des propriétés suivantes :
Tableau 3.2. Propriété d'une Datasource Hibernate
| Nom d'une propriété | fonction |
|---|---|
| hibernate.connection.datasource | Nom JNDI de la datasource |
| hibernate.jndi.url | URL du fournisseur JNDI (optionnelle) |
| hibernate.jndi.class | Classe de l'InitialContextFactory du JNDI (optionnelle) |
| hibernate.connection.username | utilisateur de la base de données (optionnelle) |
| hibernate.connection.password | mot de passe de la base de données (optionnelle) |
Voici un exemple de fichier hibernate.properties pour l'utilisation d'une datasource JNDI fournie par un serveur d'applications :
hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \
org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialectLes connexions JDBC obtenues à partir d'une datasource JNDI participeront automatiquement aux transactions gérées par le conteneur du serveur d'applications.
Des propriétés supplémentaires de connexion peuvent être passées en préfixant le nom de la propriété par "hibernate.connnection". Par exemple, vous pouvez spécifier un jeu de caractères en utilisant hibernate.connection.charSet.
Vous pouvez fournir votre propre stratégie d'obtention des connexions JDBC en implémentant l'interface org.hibernate.connection.ConnectionProvider. Vous pouvez sélectionner une implémentation spécifique en positionnant hibernate.connection.provider_class.
Il y a un certain nombre d'autres propriétés qui contrôlent le fonctionnement d'Hibernate à l'exécution. Toutes sont optionnelles et ont comme valeurs par défaut des valeurs "raisonnables" pour un fonctionnement nominal.
Attention : Certaines de ces propriétés sont uniquement de niveau System. Les propriétés de niveau System ne peuvent être positionnées que via la ligne de commande (java -Dproperty=value) ou être définies dans hibernate.properties. Elle ne peuvent pas l'être via une des autres techniques décrites ci-dessus.
Tableau 3.3. Propriétés de configuration d'Hibernate
| Nom de la propriété | Fonction |
|---|---|
| hibernate.dialect |
Le nom de la classe du Dialect Hibernate.
qui permet à Hibernate de générer du SQL optimisé pour une
base de données relationnelle particulière.
ex. nom.complet.de.ma.classe.de.Dialect |
| hibernate.show_sql |
Ecrit toutes les requêtes SQL sur la console. Il s'agit d'une
alternative au positionnement de la catégorie de log
org.hibernate.SQL au niveau debug.
ex. true | false |
| hibernate.format_sql |
Formate et indente le sql dans la console et dans le log
ex. true | false |
| hibernate.default_schema |
Positionne dans le SQL généré un schéma/tablespace par défaut pour les noms de
table ne l'ayant pas surchargé.
ex. MON_SCHEMA |
| hibernate.default_catalog |
Qualifie les noms de tables non qualifiées avec ce catalogue
dans le SQL généré.
ex. CATALOG_NAME |
| hibernate.session_factory_name |
La SessionFactory sera automatiquement
liée à ce nom dans le JNDI après sa création.
ex. jndi/nom/hierarchique |
| hibernate.max_fetch_depth |
Définit la profondeur maximale d'un arbre de chargement par
jointures ouvertes pour les associations à cardinalité unitaire
(un-à-un, plusieurs-à-un).
Un 0 désactive le chargement par jointure
ouverte.
ex. valeurs recommandées entre 0 et 3 |
| hibernate.default_batch_fetch_size |
Définit une taille par défaut pour le chargement par lot des associations
ex. Valeurs recommandées : 4, 8, 16 |
| hibernate.default_entity_mode |
Définit un mode de représentation par défaut des entités pour
toutes les sessions ouvertes depuis cette SessionFactory dynamic-map, dom4j, pojo |
| hibernate.order_updates |
Force Hibernate à trier les updates SQL par la valeur de la clé
primaire des éléments qui sont mis à jour. Cela permet de limiter
les deadlocks de transaction dans les systèmes hautement concurents.
ex. true | false |
| hibernate.generate_statistics |
Si activé, Hibernate va collecter des statistiques utiles
pour le réglage des performances.
ex. true | false |
| hibernate.use_identifer_rollback |
Si activé, les propriétés correspondant à l'identifiant
des objets vont être remises aux valeurs par défaut lorsque
les objets seront supprimés.
ex. true | false |
| hibernate.use_sql_comments |
Si activé, Hibernate va générer des commentaires à l'intérieur
des requêtes SQL pour faciliter le debogage., par défaut à false.
ex. true | false |
Tableau 3.4. Propriétés Hibernate liées à JDBC et aux connexions
| Nom de la propriété | Fonction |
|---|---|
| hibernate.jdbc.fetch_size | Une valeur non nulle détermine la taille de chargement des statements JDBC (appelle Statement.setFetchSize()). |
| hibernate.jdbc.batch_size |
Une valeur non nulle active l'utilisation par Hibernate des mises
à jour par batch de JDBC2.
ex. les valeurs recommandées entre 5 et 30 |
| hibernate.jdbc.batch_versioned_data |
Paramétrez cette propriété à true si votre pilote JDBC
retourne des row counts corrects depuis executeBatch() (il est
souvent approprié d'activer cette option). Hibernate utilisera alors le "batched DML" pour
versionner automatiquement les données. Par défaut = false.
eg. true | false |
| hibernate.jdbc.factory_class |
Sélectionne un Batcher personnalisé. La
plupart des applications n'auront pas besoin de cette propriété
de configuration
ex. classname.of.Batcher |
| hibernate.jdbc.use_scrollable_resultset |
Active l'utilisation par Hibernate des resultsets scrollables
de JDBC2. Cette propriété est seulement nécessaire lorsque l'on
utilise une connexion JDBC fournie par l'utilisateur. Autrement,
Hibernate utilise les métadonnées de la connexion.
ex. true | false |
| hibernate.jdbc.use_streams_for_binary |
Utilise des flux lorsque l'on écrit/lit des types
binary ou serializable
vers et à partir de JDBC (propriété de niveau système).
ex. true | false |
| hibernate.jdbc.use_get_generated_keys |
Active l'utilisation de PreparedStatement.getGeneratedKeys() de JDBC3
pour récupérer nativement les clés générées après insertion. Nécessite un pilote
JDBC3+, le mettre à false si votre pilote a des problèmes avec les générateurs
d'identifiant Hibernate. Par défaut, essaie de déterminer les possibilités du
pilote en utilisant les meta données de connexion.
eg. true|false |
| hibernate.connection.provider_class |
Le nom de la classe d'un ConnectionProvider personnalisé
qui fournit des connexions JDBC à Hibernate
ex. classname.of.ConnectionProvider |
| hibernate.connection.isolation |
Définit le niveau d'isolation des transactions JDBC. Regardez
java.sql.Connection pour connaître le
sens des différentes valeurs mais notez également que la plupart
des bases de données ne supportent pas tous les niveaux d'isolation.
ex. 1, 2, 4, 8 |
| hibernate.connection.autocommit |
Active le mode de commit automatique (autocommit) pour les connexions
JDBC du pool (non recommandé).
ex. true | false |
| hibernate.connection.release_mode |
Spécifie à quel moment Hibernate doit relacher les connexion JDBC.
Par défaut une connexion JDBC est conservée jusqu'à ce que la session
soit explicitement fermée ou déconnectée. Pour une source de données
JTA d'un serveur d'application, vous devriez utiliser after_statement
pour libérer les connexions de manière plus agressive après chaque appel
JDBC. Pour une connexion non JTA, il est souvent préférable de libérer
la connexion à la fin de chaque transaction en utilisant after_transaction.
auto choisira after_statement pour
des transactions JTA et CMT et after_transaction pour
des transactions JDBC.
ex. on_close (default) | after_transaction | after_statement | auto |
| hibernate.connection.<propertyName> | Passe la propriété JDBCpropertyName à DriverManager.getConnection(). |
| hibernate.jndi.<propertyName> | Passe la propriété propertyName à l'InitialContextFactory de JNDI. |
Tableau 3.5. Propriétés du Cache d'Hibernate
| Nom de la propriété | Fonction |
|---|---|
| hibernate.cache.provider_class |
Le nom de classe d'un CacheProvider
spécifique.
ex. nom.de.classe.du.CacheProvider |
| hibernate.cache.use_minimal_puts |
Optimise le cache de second niveau en minimisant les écritures,
au prix de plus de lectures. Ce paramètre est surtout utile pour
les caches en cluster et est activé par défaut dans hibernate3
pour les implémentations de cache en cluster.
ex. true|false |
| hibernate.cache.use_query_cache |
Activer le cache de requête, les requêtes individuelles doivent tout
de même être déclarées comme pouvant être mise en cache.
ex. true|false |
| hibernate.cache.use_second_level_cache |
Peut être utilisé pour désactiver complètement le cache de second niveau
qui est activé par défaut pour les classes qui spécifient un élément
<cache> dans leur mapping.
ex. true|false |
| hibernate.cache.query_cache_factory |
Le nom de classe d'une interface QueryCacheFactory ,
par défaut = built-in StandardQueryCacheFactory.
ex. nom.de.la.classe.de.QueryCacheFactory |
| hibernate.cache.region_prefix |
Un préfixe à utiliser pour le nom des régions du
cache de second niveau.
ex. prefix |
| hibernate.cache.use_structured_entries |
Force Hibernate à stocker les données dans le cache de
second niveau dans un format plus adapté à la visualisation
par un humain.
ex. true|false |
Tableau 3.6. Propriétés des transactions Hibernate
| Nom de la propriété | Fonction |
|---|---|
| hibernate.transaction.factory_class |
Le nom de classe d'une TransactionFactory
qui sera utilisée par l'API Transaction
d'Hibernate (la valeur par défaut est
JDBCTransactionFactory).
ex. nom.de.classe.d.une.TransactionFactory |
| jta.UserTransaction |
Le nom JNDI utilisé par la JTATransactionFactory
pour obtenir la UserTransaction JTA du serveur
d'applications.
eg. jndi/nom/compose |
| hibernate.transaction.manager_lookup_class |
Le nom de la classe du TransactionManagerLookup
- requis lorsque le cache de niveau JVM est activé ou lorsque l'on
utilise un générateur hilo dans un environnement JTA.
ex. nom.de.classe.du.TransactionManagerLookup |
| hibernate.transaction.flush_before_completion |
Si activé, la session sera automatiquement vidée durant la phase
qui précède la fin de la transaction (before completion).
La gestion automatique de contexte fourni par Hibernate est
recommandée, voir
Section 2.5, « Sessions Contextuelles ».
ex. true | false |
| hibernate.transaction.auto_close_session |
Si activé, la session sera automatiquement fermé pendant la phase
qui suit la fin de la transaction (after completion).
La gestion automatique de contexte fourni par Hibernate est
recommandée, voir
ex. true | false |
Tableau 3.7. Propriétés diverses
| Nom de la propriété | Fonction |
|---|---|
| hibernate.current_session_context_class |
Fournit une stratégie particulière pour contextualiser
la Session courante. Voir
Section 2.5, « Sessions Contextuelles » pour plus
d'informations sur les stratégies fournies.
eg. jta | thread | custom.Class |
| hibernate.query.factory_class |
Choisi l'implémentation du parseur de requête
ex. org.hibernate.hql.ast.ASTQueryTranslatorFactory ou org.hibernate.hql.classic.ClassicQueryTranslatorFactory |
| hibernate.query.substitutions |
Lien entre les tokens de requêtes Hibernate et les
tokens SQL (les tokens peuvent être des fonctions ou des
noms littéraux par exemple).
ex. hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC |
| hibernate.hbm2ddl.auto |
Valide ou exporte automatiquement le schéma DDL vers la base de données
lorsque la SessionFactory est créée.
La valeur create-drop permet de supprimer
le schéma de base de données lorsque la SessionFactory
est fermée explicitement.
ex. validate | update | create | create-drop |
| hibernate.cglib.use_reflection_optimizer |
Active l'utilisation de CGLIB à la place de la réflexion à l'exécution
(Propriété de niveau système). La réflexion peut parfois être utile pour
résoudre des problèmes. Notez qu'Hibernate a tout de même toujours besoin
de CGLIB même si l'optimiseur est désactivé. Cette optimisation ne peut être
définie que dans le fichier hibernate.cfg.xml.
ex. true | false |
Vous devriez toujours positionner la propriété hibernate.dialect à la sous-classe de org.hibernate.dialect.Dialect appropriée à votre base de données. Si vous spécifiez un dialecte, Hibernate utilisera des valeurs adaptées pour certaines autres propriétés listées ci-dessus, vous évitant l'effort de le faire à la main.
Tableau 3.8. Dialectes SQL d'Hibernate (hibernate.dialect)
| SGBD | Dialecte |
|---|---|
| DB2 | org.hibernate.dialect.DB2Dialect |
| DB2 AS/400 | org.hibernate.dialect.DB2400Dialect |
| DB2 OS390 | org.hibernate.dialect.DB2390Dialect |
| PostgreSQL | org.hibernate.dialect.PostgreSQLDialect |
| MySQL | org.hibernate.dialect.MySQLDialect |
| MySQL with InnoDB | org.hibernate.dialect.MySQLInnoDBDialect |
| MySQL with MyISAM | org.hibernate.dialect.MySQLMyISAMDialect |
| Oracle (any version) | org.hibernate.dialect.OracleDialect |
| Oracle 9i/10g | org.hibernate.dialect.Oracle9Dialect |
| Sybase | org.hibernate.dialect.SybaseDialect |
| Sybase Anywhere | org.hibernate.dialect.SybaseAnywhereDialect |
| Microsoft SQL Server | org.hibernate.dialect.SQLServerDialect |
| SAP DB | org.hibernate.dialect.SAPDBDialect |
| Informix | org.hibernate.dialect.InformixDialect |
| HypersonicSQL | org.hibernate.dialect.HSQLDialect |
| Ingres | org.hibernate.dialect.IngresDialect |
| Progress | org.hibernate.dialect.ProgressDialect |
| Mckoi SQL | org.hibernate.dialect.MckoiDialect |
| Interbase | org.hibernate.dialect.InterbaseDialect |
| Pointbase | org.hibernate.dialect.PointbaseDialect |
| FrontBase | org.hibernate.dialect.FrontbaseDialect |
| Firebird | org.hibernate.dialect.FirebirdDialect |
Si votre base de données supporte les outer joins de type ANSI, Oracle ou Sybase, le chargement par jointure ouverte devrait améliorer les performances en limitant le nombre d'aller-retour avec la base de données (la base de données effectuant donc potentiellement plus de travail). Le chargement par jointure ouverte permet à un graphe entier d'objets connectés par une relation plusieurs-à-un, un-à-plusieurs ou un-à-un d'être chargé en un seul SELECT SQL.
Le chargement par jointure ouverte peut être désactiver globalement en mettant la propriété hibernate.max_fetch_depth à 0. Une valeur de 1 ou plus active le chargement par jointure ouverte pour les associatiosn un-à-un et plusieurs-à-un qui ont été mappée avec fetch="join".
Reportez vous à Section 19.1, « Stratégies de chargement » pour plus d'information.
Oracle limite la taille d'un tableau de byte qui peuvent être passées à et vers son pilote JDBC. Si vous souhaitez utiliser des instances larges de type binary ou serializable, vous devez activer la propriété hibernate.jdbc.use_streams_for_binary. C'est une fonctionalité de niveau système uniquement.
Les propriétés préfixées par hibernate.cache vous permettent d'utiliser un système de cache de second niveau. Ce cache peut avoir une portée dans le processus ou même être utilisable dans un système distribué. Référez vous au chapitre Section 19.2, « Le cache de second niveau » pour plus de détails.
Vous pouvez définir de nouveaux tokens dans les requêtes Hibernate en utilisant la propriété hibernate.query.substitutions. Par exemple :
hibernate.query.substitutions vrai=1, faux=0
remplacerait les tokens vrai et faux par des entiers dans le SQL généré.
hibernate.query.substitutions toLowercase=LOWER
permettrait de renommer la fonction SQL LOWER en toLowercase
Si vous activez hibernate.generate_statistics, Hibernate va fournir un certains nombre de métriques utiles pour régler les performances d'une application qui tourne via SessionFactory.getStatistics(). Hibernate peut aussi être configuré pour exposer ces statistiques via JMX. Lisez les Javadoc des interfaces dans le package org.hibernate.stats pour plus d'informations.
Hibernate trace divers évènements en utilisant Apache commons-logging.
Le service commons-logging délèguera directement à Apache Log4j (si vous incluez log4j.jar dans votre classpath) ou le système de trace du JDK 1.4 (si vous tournez sous le JDK 1.4 et supérieur). Vous pouvez télécharger Log4j à partir de http://jakarta.apache.org. Pour utiliser Log4j, vous devrez placer dans votre classpath un fichier log4j.properties. Un exemple de fichier est distribué avec Hibernate dans le répertoire src/.
Nous vous recommandons fortement de vous familiariser avec les messages des traces d'Hibernate. Beaucoup de soins a été apporté pour donner le plus de détails possible sans les rendre illisibles. C'est un outil essentiel en cas de soucis. Les catégories de trace les plus intéressantes sont les suivantes :
Tableau 3.9. Catégories de trace d'Hibernate
| Catégorie | Fonction |
|---|---|
| org.hibernate.SQL | Trace toutes les requêts SQL de type DML (gestion des données) qui sont exécutées |
| org.hibernate.type | Trace tous les paramètres JDBC |
| org.hibernate.tool.hbm2ddl | Trace toutes les requêts SQL de type DDL (gestion de la structure de la base) qui sont exécutées |
| org.hibernate.pretty | Trace l'état de toutes les entités (20 entités maximum) qui sont associées avec la session hibernate au moment du flush |
| org.hibernate.cache | Trace toute l'activité du cache de second niveau |
| org.hibernate.transaction | Trace toute l'activité relative aux transactions |
| org.hibernate.jdbc | Trace toute acquisition de ressource JDBC |
| org.hibernate.hql.ast.AST | Trace l'arbre syntaxique des requêtes HQL et SQL durant l'analyse syntaxique des requêtes |
| org.hibernate.secure | Trace toutes les demandes d'autorisation JAAS |
| org.hibernate | Trace tout (beaucoupe d'informations, mais très utile pour résoudre les problèmes). |
Lorsque vous développez des applications avec Hibernate, vous devriez quasiment toujours travailler avec le niveau debug activé pour la catégorie org.hibernate.SQL, ou sinon avec la propriété hibernate.show_sql activée.
L'interface org.hibernate.cfg.NamingStrategy vous permet de spécifier une "stratégie de nommage" des objets et éléments de la base de données.
Vous pouvez fournir des règles pour automatiquement générer les identifiants de base de données à partir des identifiants Java, ou transformer une colonne ou table "logique" donnée dans le fichier de mapping en une colonne ou table "physique". Cette fonctionnalité aide à réduire la verbosité de documents de mapping, en éliminant le bruit répétitif (les préfixes TBL_ par exemple). La stratégie par défaut utilisée par Hibernate est minimale.
Vous pouvez définir une stratégie différente en appelant Configuration.setNamingStrategy() avant d'ajouter des mappings :
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory();net.sf.hibernate.cfg.ImprovedNamingStrategy est une stratégie fournie qui peut être utile comme point de départ de quelques applications.
Une approche alternative est de spécifier toute la configuration dans un fichier nommé hibernate.cfg.xml. Ce fichier peut être utilisé à la place du fichier hibernate.properties, voire même peut servir à surcharger les propriétés si les deux fichiers sont présents.
Le fichier de configuration XML doit par défaut se placer à la racine du CLASSPATH. En voici un exemple :
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory
name="java:hibernate/SessionFactory">
<!-- properties -->
<property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">false</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction">java:comp/UserTransaction</property>
<!-- mapping files -->
<mapping resource="org/hibernate/auction/Item.hbm.xml"/>
<mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
<!-- cache settings -->
<class-cache class="org.hibernate.auction.Item" usage="read-write"/>
<class-cache class="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
</session-factory>
</hibernate-configuration>Commme vous pouvez le voir, l'avantage de cette approche est l'externalisation des noms des fichiers de mapping de la configuration. Le fichier hibernate.cfg.xml est également plus pratique quand on commence à régler le cache d'Hibernate. Notez que vous pouvez choisir entre utiliser hibernate.properties ou hibernate.cfg.xml, les deux sont équivalents, sauf en ce qui concerne les bénéfices de l'utilisation de la syntaxe XML mentionnés ci-dessus.
Avec la configuration XML, démarrer Hibernate devient donc aussi simple que ceci :
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Hibernate possède les points suivants d'intégration à l'infrastructure J2EE :
Source de données gérée par le conteneur : Hibernate peut utiliser des connexions JDBC gérées par le conteneur et fournie par l'intermédiaire de JNDI. Souvent, un TransactionManager compatible JTA et un ResourceManager s'occupent de la gestion des transactions (CMT). Ils sont particulièrement prévus pour pouvoir gérer des transactions distribuées sur plusieurs sources de données. Vous pouvez bien sûr également définir vos limites de transaction dans votre programme (BMT) ou vous pouvez sinon aussi utiliser l'API optionnelle Transaction d'Hibernate qui vous garantira la portabilité de votre code entre plusieurs serveurs d'application.
Association JNDI automatique: Hibernate peut associer sa SessionFactory à JNDI après le démarrage.
Association de la Session à JTA: La Session Hibernate peut être associée automatiquement à une transaction JTA si vous utilisez les EJBs. Vous avez juste à récupérer la SessionFactory depuis JNDI et à récupérer la Session courante. Hibernate s'occupe de vider et fermer la Session lorsque le transaction JTA se termine. La démarcation des transactions se fait de manière déclarative dans les descripteurs de déploiement.
Déploiement JMX :Si vous avez un serveur d'application compatible JMX (JBoss AS par exemple), vous pouvez choisir de déployer Hibernate en temps que MBean géré par le serveur. Cela vous évite de coder la ligne de démarrage qui permet de construire la SessionFactory depuis la Configuration. Le conteneur va démarrer votre HibernateService, et va idéalement s'occuper des dépendances entre les services (la source de données doit être disponible avant qu'Hibernate ne démarre, etc).
En fonction de votre environnement, vous devrez peut être mettre l'option de configuration hibernate.connection.aggressive_release à vrai si le serveur d'application affiche des exceptions de type "connection containment".
L'API de la Session Hibernate est indépendante de tout système de démarcation des transactions qui peut être présent dans votre architecture. Si vous laissez Hibernate utiliser l'API JDBC directement via un pool de connexion, vous devrez commencer et terminer vos transactions en utilisant l'API JDBC. Si votre application tourne à l'intérieur d'un serveur d'application J2EE, vous voudrez peut être utiliser les transactions gérées par les beans (BMT) et appeller l'API JTA et UserTransaction lorsque cela est nécessaire.
Pour conserver votre code portable entre ces deux environnements (et d'autres éventuels) nous vous recommandons d'utiliser l'API optionnelle Transaction d'Hibernate, qui va encapsuler et masquer le système de transaction sous-jacent. Pour cela, vous devez préciser une classe de fabrique d'instances de Transaction en positionnant la propriété hibernate.transaction.factory_class.
Il existe trois choix standards (fournis) :
délègue aux transactions de la base de données (JDBC). Valeur par défaut.
délègue à CMT si une transaction existante est sous ce contexte (ex: méthode d'un EJB session), sinon une nouvelle transaction est entamée et une transaction gérée par le bean est utilisée.
délègue à aux transactions JTA gérées par le conteneur
Vous pouvez également définir votre propre stratégie transactionnelle (pour un service de transaction CORBA par exemple).
Certaines fonctionnalités d'Hibernate (i.e. le cache de second niveau, l'association automatique des Session à JTA, etc.) nécessitent l'accès au TransactionManager JTA dans un environnement "managé". Dans un serveur d'application, vous devez indiquer comment Hibernate peut obtenir une référence vers le TransactionManager, car J2EE ne fournit pas un seul mécanisme standard.
Tableau 3.10. TransactionManagers JTA
| Fabrique de Transaction | Serveur d'application |
|---|---|
| org.hibernate.transaction.JBossTransactionManagerLookup | JBoss |
| org.hibernate.transaction.WeblogicTransactionManagerLookup | Weblogic |
| org.hibernate.transaction.WebSphereTransactionManagerLookup | WebSphere |
| org.hibernate.transaction.WebSphereExtendedJTATransactionLookup | WebSphere 6 |
| org.hibernate.transaction.OrionTransactionManagerLookup | Orion |
| org.hibernate.transaction.ResinTransactionManagerLookup | Resin |
| org.hibernate.transaction.JOTMTransactionManagerLookup | JOTM |
| org.hibernate.transaction.JOnASTransactionManagerLookup | JOnAS |
| org.hibernate.transaction.JRun4TransactionManagerLookup | JRun4 |
| org.hibernate.transaction.BESTransactionManagerLookup | Borland ES |
Une SessionFactory Hibernate associée au JNDI peut simplifier l'accès à la fabrique et donc la création de nouvelles Sessions. Notez que cela n'est pas lié avec les Datasource associées au JNDI, elles utilisent juste le même registre.
Si vous désirez associer la SessionFactory à un nom JNDI, spécifiez un nom (ex. java:hibernate/SessionFactory) en utilisant la propriété hibernate.session_factory_name. Si cette propriété est omise, la SessionFactory ne sera pas associée au JNDI (c'est particulièrement pratique dans les environnements ayant une implémentation de JNDI en lecture seule, comme c'est le cas pour Tomcat).
Lorsqu'il associe la SessionFactory au JNDI, Hibernate utilisera les valeurs de hibernate.jndi.url, hibernate.jndi.class pour instancier un contexte d'initialisation. S'ils ne sont pas spécifiés, l'InitialContext par défaut sera utilisé.
Hibernate va automatiquement placer la SessionFactory dans JNDI après avoir appelé cfg.buildSessionFactory(). Cela signifie que vous devez avoir cet appel dans un code de démarrage (ou dans une classe utilitaire) dans votre application sauf si vous utilisez le déploiement JMX avec le service HibernateService présenté plus tard dans ce document.
Si vous utilisez SessionFactory JNDI, un EJB ou n'importe quelle autre classe peut obtenir la SessionFactory en utilisant un lookup JNDI.
Nous recommandons que vous liiez la SessionFactory à JNDI dans les environnements managés et que vous utilisiez un singleton static si ce n'est pas le cas. Pour isoler votre application de ces détails, nous vous recommandons aussi de masquer le code de lookup actuel pour une SessionFactory dans une classe helper, comme HibernateUtil.getSessionFactory(). Notez qu'une telle classe est aussi un moyen efficace de démarrer Hibernate—voir chapitre 1.
Le moyen le plus simple de gérer les Sessions et transactions est la gestion automatique de session "courante" offerte par Hibernate. Voir détail à Section 2.5, « Sessions Contextuelles ». En utilisant le contexte de session "jta" session context, s'il n'y a pas de Session associée à la transaction JTA courante, une session sera démarrée et associée à la transaction JTA courante la première fois que vous appelez sessionFactory.getCurrentSession(). Les Sessions obtenue via getCurrentSession() dans une contexte "jta" seront automatiquement flushées avant la validation de la transaction, fermées une fois la transaction complétée, et libéreront les connexions JDBC de manière aggressive après chaque statement. Ceci permet aux Sessions d'être gérées par le cycle de vie de la transaction JTA à la quelle est sont associées, laissant le code de l'utilisateur propre de ce type de gestion. Votre code peut soit utiliser JTA de manière programmatique via UserTransaction, ou (ce qui est recommandé pour la portabilité du code) utiliser l'API Transaction API pour marquer les limites. Si vous exécutez sous un conteneur EJB, la démarcation déclarative des transactions avec CMT est recommandée.
La ligne cfg.buildSessionFactory() doit toujours être exécutée quelque part pour avoir une SessionFactory dans JNDI. Vous pouvez faire cela dans un bloc d'initialisation static (comme celui qui se trouve dans la classe HibernateUtil) ou vous pouvez déployer Hibernate en temps que service managé.
Hibernate est distribué avec org.hibernate.jmx.HibernateService pour le déploiement sur un serveur d'application avec le support de JMX comme JBoss AS. Le déploiement et la configuration sont spécifiques à chaque vendeur. Voici un fichier jboss-service.xml d'exemple pour JBoss 4.0.x:
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- Required services -->
<depends>jboss.jca:service=RARDeployer</depends>
<depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<!-- Bind the Hibernate service to JNDI -->
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<!-- Datasource settings -->
<attribute name="Datasource">java:HsqlDS</attribute>
<attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute>
<!-- Transaction integration -->
<attribute name="TransactionStrategy">
org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled">true</attribute>
<attribute name="AutoCloseSessionEnabled">true</attribute>
<!-- Fetching options -->
<attribute name="MaximumFetchDepth">5</attribute>
<!-- Second-level caching -->
<attribute name="SecondLevelCacheEnabled">true</attribute>
<attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled">true</attribute>
<!-- Logging -->
<attribute name="ShowSqlEnabled">true</attribute>
<!-- Mapping files -->
<attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
</mbean>
</server>Ce fichier est déployé dans un répertoire META-INF et est packagé dans un fichier JAR avec l'extension .sar (service archive). Vous devez également packager Hibernate, les librairies tierces requises, vos classes persistantes compilées et vos fichiers de mapping dans la même archive. Vos beans entreprise (souvent des EJBs session) peuvent rester dans leur propre fichier JAR mais vous pouvez inclure ce fichier JAR dans le jar principal du service pour avoir une seule unité déployable à chaud. Vous pouvez consulter la documentation de JBoss AS pour plus d'information sur les services JMX et le déploiement des EJBs.
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.
Toute bonne application Java nécessite une classe persistante représentant les félins.
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);
}
}Il y a quatre règles à suivre ici :
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.
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 Section 10.11, « Persistance transitive »
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).
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".
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.
Une sous-classe doit également suivre la première et la seconde règle. Elle hérite sa propriété d'identifiant de Cat.
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}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) :
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;
}
}Notez qu'une clef métier ne doit pas être solide comme une clef primaire de base de données (voir Section 11.1.3, « L'identité des objets »). Les propriétés immuables ou uniques sont généralement de bonnes candidates pour une clef métier.
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 Tableau 3.3, « Propriétés de configuration d'Hibernate »).
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 :
<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>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 :
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();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 :
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
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 Chapitre 18, Mapping XML.
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é 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 :
<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.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
Les mappings Objet/relationnel sont généralement définis dans un document XML. Le document de mapping est conçu pour être lisible et éditable à la main. Le langage de mapping est Java-centrique, c'est à dire que les mappings sont construits à partir des déclarations des classes persistantes et non des déclarations des tables.
Remarquez que même si beaucoup d'utilisateurs de Hibernate préfèrent écrire les fichiers de mappings à la main, plusieurs outils existent pour générer ce document, notamment XDoclet, Middlegen et AndroMDA.
Démarrons avec un exemple de mapping :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat"
table="cats"
discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass"
type="character"/>
<property name="weight"/>
<property name="birthdate"
type="date"
not-null="true"
update="false"/>
<property name="color"
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
<property name="sex"
not-null="true"
update="false"/>
<property name="litterId"
column="litterId"
update="false"/>
<many-to-one name="mother"
column="mother_id"
update="false"/>
<set name="kittens"
inverse="true"
order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat"
discriminator-value="D">
<property name="name"
type="string"/>
</subclass>
</class>
<class name="Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>Etudions le contenu du document de mapping. Nous décrirons uniquement les éléments et attributs du document utilisés par Hibernate à l'exécution. Le document de mapping contient aussi des attributs et éléments optionnels 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 not-null.)
Tous les mappings XML devraient utiliser le doctype indiqué. Ce fichier est présent à l'URL ci-dessus, dans le répertoire hibernate-x.x.x/src/org/hibernate ou dans hibernate3.jar. Hibernate va toujours chercher la DTD dans son classpath en premier lieu. Si vous constatez des recherches de la DTD sur Internet, vérifiez votre déclaration de DTD par rapport au contenu de votre classpath.
Cet élément a plusieurs attributs optionnels. Les attributs schema et catalog indiquent que les tables référencées par ce mapping appartiennent au schéma nommé et/ou au catalogue. S'ils sont spécifiés, les noms de tables seront qualifiés par les noms de schéma et catalogue. L'attribut default-cascade indique quel type de cascade sera utlisé par défaut pour les propriétés et collections qui ne précisent pas l'attribut cascade. L'attribut auto-import nous permet d'utiliser par défaut des noms de classes non qualifiés dans le langage de requête.
<hibernate-mapping
schema="schemaName" (1)
catalog="catalogName" (2)
default-cascade="cascade_style" (3)
default-access="field|property|ClassName" (4)
default-lazy="true|false" (5)
auto-import="true|false" (6)
package="package.name" (7)
/>| (1) | schema (optionnel) : Le nom d'un schéma de base de données. |
| (2) | catalog (optionnel) : Le nom d'un catalogue de base de données. |
| (3) | default-cascade (optionnel - par défaut vaut : none) : Un type de cascade par défaut. |
| (4) | default-access (optionnel - par défaut vaut : property) : Comment hibernate accèdera aux propriétés. On peut aussi redéfinir sa propre implémentation de PropertyAccessor. |
| (5) | default-lazy (optionnel - par défaut vaut : true) : Valeur par défaut pour un attribut lazy non spécifié : celui des mappings de classes et de collection. |
| (6) | auto-import (optionnel - par défaut vaut : true) : Spécifie si l'on peut utiliser des noms de classes non qualifiés (des classes de ce mapping) dans le langage de requête. |
| (7) | package (optionnel) : Préfixe de package par défaut pour les noms de classe non qualifiés du document de mapping. |
Si deux classes possèdent le même nom de classe (non qualifié), vous devez indiquer auto-import="false". Hibernate lancera une exception si vous essayez d'assigner à deux classes le même nom importé.
Notez que l'élément hibernate-mapping vous permet d'imbriquer plusieurs mappings de <class> persistantes, comme dans l'exemple ci-dessus. Cependant la bonne pratique (ce qui est attendu par certains outils) est de mapper une seule classe (ou une seule hiérarchie de classes) par fichier de mapping et de nommer ce fichier d'après le nom de la superclasse, par exemple Cat.hbm.xml, Dog.hbm.xml, ou en cas d'héritage, Animal.hbm.xml.
Déclarez une classe persistante avec l'élément class :
<class
name="ClassName" (1)
table="tableName" (2)
discriminator-value="discriminator_value" (3)
mutable="true|false" (4)
schema="owner" (5)
catalog="catalog" (6)
proxy="ProxyInterface" (7)
dynamic-update="true|false" (8)
dynamic-insert="true|false" (9)
select-before-update="true|false" (10)
polymorphism="implicit|explicit" (11)
where="arbitrary sql where condition" (12)
persister="PersisterClass" (13)
batch-size="N" (14)
optimistic-lock="none|version|dirty|all" (15)
lazy="true|false" (16)
entity-name="EntityName" (17)
catalog="catalog" (18)
check="arbitrary sql check condition" (19)
rowid="rowid" (20)
subselect="SQL expression" (21)
abstract="true|false"
entity-name="EntityName"
/>| (1) | name (optionnel) : Le nom Java complet de la classe (ou interface) persistante. Si cet attribut est absent, il est supposé que ce mapping ne se rapporte pas à une entité POJO. |
| (2) | table (optionnel - par défaut le nom (non-qualifié) de la classe) : Le nom de sa table en base de données. |
| (3) | discriminator-value (optionnel - par défaut le nom de la classe) : Une valeur permettant de distinguer les sous-classes dans le cas de l'utilisation du polymorphisme. Les valeurs null et not null sont autorisées. |
| (4) | mutable (optionnel, vaut true par défaut) : Spécifie que des instances de la classe sont (ou non) immuables. |
| (5) | schema (optionnel) : Surcharge le nom de schéma spécifié par l'élément racine <hibernate-mapping>. |
| (6) | catalog (optionnel) : Surcharge le nom du catalogue spécifié par l'élément racine <hibernate-mapping>. |
| (7) | proxy (optionnel) : Spécifie une interface à utiliser pour l'initialisation différée (lazy loading) des proxies. Vous pouvez indiquer le nom de la classe elle-même. |
| (8) | dynamic-update (optionnel, par défaut à false) : Spécifie que les UPDATE SQL doivent être générés à l'exécution et contenir uniquement les colonnes dont les valeurs ont été modifiées. |
| (9) | dynamic-insert (optionnel, par défaut à false): Spécifie que les INSERT SQL doivent être générés à l'exécution et ne contenir que les colonnes dont les valeurs sont non nulles. |
| (10) | select-before-update (optionnel, par défaut à false): Spécifie que Hibernate ne doit jamais exécuter un UPDATE SQL sans être certain qu'un objet a été réellement modifié. Dans certains cas, (en réalité, seulement quand un objet transient a été associé à une nouvelle session par update()), cela signifie que Hibernate exécutera un SELECT SQL pour s'assurer qu'un UPDATE SQL est véritablement nécessaire. |
| (11) | polymorphism (optionnel, vaut implicit par défaut) : Détermine si, pour cette classe, une requête polymorphique implicite ou explicite est utilisée. |
| (12) | where (optionnel) spécifie une clause SQL WHERE à utiliser lorsque l'on récupère des objets de cette classe. |
| (13) | persister (optionnel) : Spécifie un ClassPersister particulier. |
| (14) | batch-size (optionnel, par défaut = 1) : spécifie une taille de batch pour remplir les instances de cette classe par identifiant en une seule requête. |
| (15) | optimistic-lock (optionnel, par défaut = version) : Détermine la stratégie de verrou optimiste. |
| (16) | lazy (optionnel) : Déclarer lazy="true" est un raccourci pour spécifier le nom de la classe comme étant l'interface proxy. |
| (17) | entity-name (optionnel) : Hibernate3 permet à une classe d'être mappée plusieurs fois (potentiellement à plusieurs tables), et permet aux mappings d'entité d'être représentés par des Maps ou du XML au niveau Java. Dans ces cas, vous devez indiquer un nom explicite arbitraire pour les entités. Voir Section 4.4, « Modèles dynamiques » et Chapitre 18, Mapping XML pour plus d'informations. |
| (18) | catalog (optionnel) : The name of a database catalog used for this class and its table. |
| (19) | check (optionnel) : expression SQL utilisée pour générer une contrainte de vérification multi-lignes pour la génération automatique de schéma. |
| (20) | rowid (optionnel) : Hibernate peut utiliser des ROWID sur les bases de données qui utilisent ce mécanisme. Par exemple avec Oracle, Hibernate peut utiliser la colonne additionnelle rowid pour des mises à jour rapides si cette option vaut rowid. Un ROWID représente la localisation physique d'un tuple enregistré. |
| (21) | subselect (optionnel) : Permet de mapper une entité immuable en lecture-seule sur un sous-select de base de données. Utile pour avoir une vue au lieu d'une table en base, mais à éviter. Voir plus bas pour plus d'information. |
| ??? | abstract (optionnel) : Utilisé pour marquer des superclasses abstraites dans des hiérarchies de <union-subclass>. |
Il est tout à fait possible d'utiliser une interface comme nom de classe persistante. Vous devez alors déclarer les classes implémentant cette interface en utilisant l'élément <subclass>. Vous pouvez faire persister toute classe interne static. Vous devez alors spécifier le nom de la classe par la notation habituelle des classes internes c'est à dire eg.Foo$Bar.
Les classes immuables, mutable="false", ne peuvent pas être modifiées ou supprimées par l'application. Cela permet à Hibernate de faire quelques optimisations mineures sur les performances.
L'attribut optionnnel proxy permet les intialisations différées des instances persistantes de la classe. Hibernate retournera initialement des proxies CGLIB qui implémentent l'interface nommée. Le véritable objet persistant ne sera chargé que lorsque une méthode du proxy sera appelée. Voir plus bas le paragraphe abordant les proxies et le chargement différé (lazy initialization).
Le polymorphisme implicite signifie que les instances de la classe seront retournées par une requête qui utilise les noms de la classe ou de chacune de ses superclasses ou encore des interfaces implémentées par cette classe ou ses superclasses. Les instances des classes filles seront retournées par une requête qui utilise le nom de la classe elle même. Le polymorphisme explicite signifie que les instances de la classe ne seront retournées que par une requête qui utilise explicitement son nom et que seules les instances des classes filles déclarées dans les éléments <subclass> ou <joined-subclass> seront retournées. Dans la majorités des cas la valeur par défaut, polymorphism="implicit", est appropriée. Le polymorphisme explicite est utile lorsque deux classes différentes sont mappées à la même table (ceci permet d'écrire une classe "légère" qui ne contient qu'une partie des colonnes de la table - voir la partie design pattern du site communautaire).
L'attribut persister vous permet de customiser la stratégie utilisée pour la classe. Vous pouvez, par exemple, spécifier votre propre sous-classe de org.hibernate.persister.EntityPersister ou vous pourriez aussi créer une nouvelle implémentation de l'interface org.hibernate.persister.ClassPersister qui proposerait une persistance via, par exemple, des appels de procédures stockées, de la sérialisation vers des fichiers plats ou un annuaire LDAP. Voir org.hibernate.test.CustomPersister pour un exemple simple (d'une "persistance" vers une Hashtable).
Notez que les paramètres dynamic-update et dynamic-insert ne sont pas hérités par les sous-classes et peuvent donc être spécifiés pour les éléments <subclass> ou <joined-subclass> Ces paramètres peuvent améliorer les performances dans certains cas, mais peuvent aussi les amoindrir. A utiliser en connaissance de causes.
L'utilisation de select-before-update va généralement faire baisser les performances. Ce paramètre est pratique pour prévenir l'appel inutile d'un trigger sur modification quand on réattache un graphe d'instances à une Session.
Si vous utilisez le dynamic-update, les différentes stratégies de verrouillage optimiste (optimistic locking) sont les suivantes:
version vérifie les colonnes version/timestamp
all vérifie toutes les colonnes
dirty vérifie les colonnes modifiées, permettant des updates concurrents
none pas de verrouillage optimiste
Nous encourageons très fortement l'utilisation de colonnes de version/timestamp pour le verrouillage optimiste avec Hibernate. C'est la meilleure stratégie en regard des performances et la seule qui gère correctement les modifications sur les objets détachés (c'est à dire lorsqu'on utilise Session.merge()).
Il n'y a pas de différence entre table et vue pour le mapping Hibernate, tant que c'est transparent au niveau base de données (remarquez que certaines BDD ne supportent pas les vues correctement, notamment pour les updates). Vous rencontrerez peut-être des cas où vous souhaitez utiliser une vue mais ne pouvez pas en créer sur votre BDD (par exemple à cause de schémas anciens et figés). Dans ces cas, vous pouvez mapper une entité immuable en lecture seule sur un sous-select SQL donné:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class>Déclarez les tables à synchroniser avec cette entité pour assurer que le flush automatique se produise correctement, et pour que les requêtes sur l'entité dérivée ne renvoient pas des données périmées. Le litéral <subselect> est disponible comme attribut ou comme élément de mapping.
Les classes mappées doivent déclarer la clef primaire de la table en base de données. La plupart des classes auront aussi une propriété de type javabean présentant l'identifiant unique d'une instance. L'élément <id> sert à définir le mapping entre cette propriété et la clef primaire en base.
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="null|any|none|undefined|id_value" (4)
access="field|property|ClassName"> (5)
<generator class="generatorClass"/>
</id>| (1) | name (optionnel) : Nom de la propriété qui sert d'identifiant. |
| (2) | type (optionnel) : Nom indiquant le type Hibernate. |
| (3) | column (optionnel - le nom de la propriété est pris par défaut) : Nom de la clef primaire. |
| (4) | unsaved-value (optionnel - par défaut une valeur "bien choisie") : Une valeur de la propriété d'identifiant qui indique que l'instance est nouvellement instanciée (non sauvegardée), et qui la distingue des instances transients qui ont été sauvegardées ou chargées dans une session précédente. |
| (5) | access (optionnel - par défaut property) : La stratégie que doit utiliser Hibernate pour accéder aux valeurs des propriétés. |
Si l'attribut name est absent, Hibernate considère que la classe ne possède pas de propriété identifiant.
L'attribut unsaved-value est important ! Si l'identifiant de votre classe n'a pas une valeur par défaut compatible avec le comportement standard de Java (zéro ou null), vous devez alors préciser la valeur par défaut.
La déclaration alternative <composite-id> permet l'acccès aux données d'anciens systèmes qui utilisent des clefs composées. Son utilisation est fortement déconseillée pour d'autres cas.
L'élément fils <generator> nomme une classe Java utilisée pour générer les identifiants uniques pour les instances des classes persistantes. Si des paramètres sont requis pour configurer ou initialiser l'instance du générateur, ils sont passés en utilisant l'élément <param>.
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>Tous les générateurs doivent implémenter l'interface org.hibernate.id.IdentifierGenerator. C'est une interface très simple ; certaines applications peuvent proposer leur propre implémentations spécialisées. Cependant, Hibernate propose une série d'implémentations intégrées. Il existe des noms raccourcis pour les générateurs intégrés :
Génère des identifiants de type long, short ou int qui ne sont uniques que si aucun autre processus n'insère de données dans la même table. Ne pas utiliser en environnement clusterisé.
Utilisation de la colonne identity de DB2, MySQL, MS SQL Server, Sybase et HypersonicSQL. L'identifiant renvoyé est de type long, short ou int.
Utilisation des séquences dans DB2, PostgreSQL, Oracle, SAP DB, McKoi ou d'un générateur dans Interbase. L'identifiant renvoyé est de type long, short ou int
Utilise un algorithme hi/lo pour générer de façon efficace des identifiants de type long, short ou int, en prenant comme source de valeur "hi" une table et une colonne (par défaut hibernate_unique_key et next_hi respectivement). L'algorithme hi/lo génère des identifiants uniques pour une base de données particulière seulement.
Utilise un algorithme hi/lo pour générer efficacement des identifiants de type long, short ou int, étant donné un nom de séquence en base.
Utilise un algorithme de type UUID 128 bits pour générer des identifiants de type string, unique au sein d'un réseau (l'adresse IP est utilisée). Le UUID en codé en une chaîne de nombre héxadécimaux de longueur 32.
Utilise une chaîne GUID générée par la base pour MS SQL Server et MySQL.
Choisit identity, sequence ou hilo selon les possibilités offertes par la base de données sous-jacente.
Laisse l'application affecter un identifiant à l'objet avant que la métode save() soit appelée. Il s'agit de la stratégie par défaut si aucun <generator> n'est spécifié.
Récupère une clef primaire assignée par un trigger en sélectionnant la ligne par une clef unique quelconque.
Utilise l'identifiant d'un objet associé. Habituellement utilisé en conjonction avec une association <one-to-one> sur la clef primaire.
Les générateurs hilo et seqhilo proposent deux implémentations alternatives de l'algorithme hi/lo, une approche largement utilisée pour générer des identifiants. La première implémentation nécessite une table "spéciale" en base pour héberger la prochaine valeur "hi" disponible. La seconde utilise une séquence de type Oracle (quand la base sous-jacente le propose).
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id><id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id>Malheureusement, vous ne pouvez pas utilisez hilo quand vous apportez votre propre Connection à Hibernate. Quand Hibernate utilise une datasource du serveur d'application pour obtenir des connexions inscrites avec JTA, vous devez correctement configurer hibernate.transaction.manager_lookup_class.
Le contenu du UUID est : adresse IP, date de démarrage de la JVM (précis au quart de seconde), l'heure système et un compteur (unique au sein de la JVM). Il n'est pas possible d'obtenir l'adresse MAC ou une adresse mémoire à partir de Java, c'est donc le mieux que l'on puisse faire sans utiliser JNI.
Pour les bases qui implémentent les colonnes "identité" (DB2, MySQL, Sybase, MS SQL), vous pouvez utiliser la génération de clef par identity. Pour les bases qui implémentent les séquences (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) vous pouvez utiliser la génération de clef par sequence. Ces deux méthodes nécessitent deux requêtes SQL pour insérer un objet.
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence">person_id_sequence</param>
</generator>
</id><id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id>Pour le développement multi-plateformes, la stratégie native choisira entre les méthodes identity, sequence et hilo, selon les possibilités offertes par la base sous-jacente.
Si vous souhaitez que l'application assigne des identifiants (par opposition à la génération par Hibernate), vous pouvez utiliser le générateur assigned. Ce générateur spécial utilisera une valeur d'identifiant déjà utilisé par la propriété identifiant l'objet. Ce générateur est utilisé quand la clef primaire est une clef naturelle plutôt qu'une clef secondaire. C'est le comportement par défaut si vous ne précisez pas d'élément <generator>.
Choisir le générateur assigned fait utiliser unsaved-value="undefined" par Hibernate, le forçant à interroger la base pour déterminer si l'instance est transiente ou détachée, à moins d'utiliser une propriété version ou timestamp, ou alors de définir Interceptor.isUnsaved().
Pour les schémas de base hérités d'anciens systèmes uniquement (Hibernate ne génère pas de DDL avec des triggers)
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key">socialSecurityNumber</param>
</generator>
</id>Dans l'exemple ci-dessus, socialSecurityNumber a une valeur unique définie par la classe en tant que clef naturelle et person_id est une clef secondaire dont la valeur est générée par trigger.
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName">
node="element-name|."
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName class="ClassName" column="column_name"/>
......
</composite-id>Pour une table avec clef composée, vous pouvez mapper plusieurs attributs de la classe comme propriétés identifiantes. L'élement <composite-id> accepte les mappings de propriétés <key-property> et les mappings <key-many-to-one> comme fils.
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>Vos classes persistantes doivent surcharger les méthodes equals() et hashCode() pour implémenter l'égalité d'identifiant composé. Elles doivent aussi implenter l'interface Serializable.
Malheureusement cette approche sur les identifiants composés signifie qu'un objet persistant est son propre identifiant. Il n'y a pas d'autre moyen pratique de manipuler l'objet que par l'objet lui-même. Vous devez instancier une instance de la classe persistante elle-même et peupler ses attributs identifiants avant de pouvoir appeler la méthode load() pour charger son état persistant associé à une clef composée. Nous appelons cette approche "identifiant composé embarqué" et ne la recommandons pas pour des applications complexes.
Une seconde approche, appelée identifiant composé mappé, consiste à encapsuler les propriétés identifiantes (celles contenues dans <composite-id>) dans une classe particulière.
<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>Dans cet exemple, la classe d'identifiant composée,MedicareId et la classe mappée elle-même, possèdent les propriétés medicareNumber et dependent. La classe identifiante doit redéfinir equals() et hashCode() et implémenter Serializable. Le désavantage de cette approche est la duplication du code.
Les attributs suivants servent à configurer un identifiant composé mappé :
mapped (optionnel, défaut à false) : indique qu'un identifiant composé mappé est utilisé, et que les propriétés contenues font référence aux deux classes (celle mappée et la classe identifiante).
class (optionnel, mais requis pour un identifiant composé mappé) : La classe composant utilisée comme identifiant composé.
Nous décrirons une troisième approche beaucoup plus efficace ou l'identifiant composé est implémenté comme une classe composant dans Section 8.4, « Utiliser un composant comme identifiant ». Les attributs décrits ci dessous, ne s'appliquent que pour cette dernière approche :
name (optionnel, requis pour cette approche) : une propriété de type composant qui contient l'identifiant composé (voir chapitre 9).
access (optionnel - défaut à property) : La stratégie qu'Hibernate utilisera pour accéder à la valeur de la propriété.
class (optionnel - défaut au type de la propriété déterminé par réflexion) : La classe composant utilisée comme identifiant (voir prochaine section).
Cette dernière approche est celle que nous recommandons pour toutes vos applications.
L'élément <discriminator> est nécessaire pour la persistance polymorphique qui utilise la stratégie de mapping de table par hiérarchie de classe. La colonne discriminante contient une valeur marqueur qui permet à la couche de persistance de savoir quelle sous-classe instancier pour une ligne particulière de table en base. Un nombre restreint de types peuvent être utilisés : string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator
column="discriminator_column" (1)
type="discriminator_type" (2)
force="true|false" (3)
insert="true|false" (4)
formula="arbitrary sql expression" (5)
/>| (1) | column (optionnel - par défaut à class) le nom de la colonne discriminante. |
| (2) | type (optionnel - par défaut à string) un nom indiquant le type Hibernate. |
| (3) | force (optionnel - par défaut à false) "oblige" Hibernate à spécifier une valeur discriminante autorisée même quand on récupère toutes les instances de la classe de base. |
| (4) | insert (optionnel - par défaut à true) à passer à false si la colonne discriminante fait aussi partie d'un identifiant composé mappé (Indique à Hibernate de ne pas inclure la colonne dans les INSERT SQL). |
| (5) | formula (optionnel) une expression SQL arbitraire qui est exécutée quand un type doit être évalué. Permet la discrimination basée sur le contenu. |
Les véritables valeurs de la colonne discriminante sont spécifiées par l'attribut discriminator-value des éléments <class> et <subclass>.
L'attribut force n'est utile que si la table contient des lignes avec des valeurs "extra" discriminantes qui ne sont pas mappées à une classe persistante. Ce ne sera généralement pas le cas.
En utilisant l'attribut formula vous pouvez déclarer une expression SQL arbitraire qui sera utilisée pour évaluer le type d'une ligne :
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>L'élément <version> est optionnel et indique que la table contient des données versionnées. C'est particulièrement utile si vous avez l'intention d'utiliser des transactions longues (voir plus-bas).
<version
column="version_column" (1)
name="propertyName" (2)
type="typename" (3)
access="field|property|ClassName" (4)
unsaved-value="null|negative|undefined" (5)
generated="never|always" (6)
insert="true|false" (7)
node="element-name|@attribute-name|element/@attribute|."
/>| (1) | column (optionnel - par défaut égal au nom de la propriété) : Le nom de la colonne contenant le numéro de version. |
| (2) | name : Le nom d'un attribut de la classe persistante. |
| (3) | type (optionnel - par défaut à integer) : Le type du numéro de version. |
| (4) | access (optionnel - par défaut à property) : La stratégie à utiliser par Hibernate pour accéder à la valeur de la propriété. |
| (5) | unsaved-value (optionnel - par défaut à undefined) : Une valeur de la propriété d'identifiant qui indique que l'instance est nouvellement instanciée (non sauvegardée), et qui la distingue des instances détachées qui ont été sauvegardées ou chargées dans une session précédente (undefined indique que la valeur de l'atribut identifiant devrait être utilisé). |
| (6) | generated (optional - défaut à never) : Indique que la valeur de la propriété version est générée par la base de données cf. Section 5.6, « Propriétés générées ». |
| (7) | insert (optionnel - défaut à true) : Indique si la colonne de version doit être incluse dans les ordres insert. Peut être à false si et seulement si la colonne de la base de données est définie avec une valeur par défaut à 0. |
Les numéros de version doivent avoir les types Hibernate long, integer, short, timestamp ou calendar.
Une propriété de version ou un timestamp ne doit jamais être null pour une instance détachée, ainsi Hibernate pourra détecter toute instance ayant une version ou un timestamp null comme transient, quelles que soient les stratégies unsaved-value spécifiées. Déclarer un numéro de version ou un timestamp "nullable" est un moyen pratique d'éviter tout problème avec les réattachements transitifs dans Hibernate, particulièrement utile pour ceux qui utilisent des identifiants assignés ou des clefs composées !
L'élément optionnel <timestamp> indique que la table contient des données horodatées (timestamp). Cela sert d'alternative à l'utilisation de numéros de version. Les timestamps (ou horodatage) sont par nature une implémentation moins fiable pour l'optimistic locking. Cependant, l'application peut parfois utiliser l'horodatage à d'autres fins.
<timestamp
column="timestamp_column" (1)
name="propertyName" (2)
access="field|property|ClassName" (3)
unsaved-value="null|undefined" (4)
source="vm|db" (5)
generated="never|always" (6)
node="element-name|@attribute-name|element/@attribute|."
/>| (1) | column (optionnel - par défaut à le nom de la propriété) : Le nom d'une colonne contenant le timestamp. |
| (2) | name : Le nom d'une propriété au sens JavaBean de type Date ou Timestamp de la classe persistante. |
| (3) | access (optionnel - par défaut à property) : La stratégie à utiliser par Hibernate pour accéder à la valeur de la propriété. |
| (4) | unsaved-value (optionnel - par défaut à null) : Propriété dont la valeur est un numéro de version qui indique que l'instance est nouvellement instanciée (non sauvegardée), et qui la distingue des instances détachées qui ont été sauvegardées ou chargées dans une session précédente (undefined indique que la valeur de l'attribut identifiant devrait être utilisée). |
| (5) | source (optionnel - par défaut à vm) : D'où Hibernate doit-il récupérer la valeur du timestamp? Depuis la base de données ou depuis la JVM d'exécution? Les valeurs de timestamp de la base de données provoquent une surcharge puisque Hibernate doit interroger la base pour déterminer la prochaine valeur mais cela est plus sûr lorsque vous fonctionnez dans un cluster. Remarquez aussi que certains des dialectes ne supportent pas cette fonction, et que d'autres l'implémentent mal, provoquant des erreurs de précision (Oracle 8 par exemple). |
| (6) | generated (optional - défaut à never) : Indique que la valeur de ce timestamp est générée par la base de données cf. Section 5.6, « Propriétés générées ». |
Notez que <timestamp> est équivalent à <version type="timestamp">.
L'élément <property> déclare une propriété de la classe au sens JavaBean.
<property
name="propertyName" (1)
column="column_name" (2)
type="typename" (3)
update="true|false" (4)
insert="true|false" (4)
formula="arbitrary SQL expression" (5)
access="field|property|ClassName" (6)
lazy="true|false" (7)
unique="true|false" (8)
not-null="true|false" (9)
optimistic-lock="true|false" (10)
generated="never|insert|always" (11)
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>| (1) | name : nom de la propriété, avec une lettre initiale en minuscule. |
| (2) | column (optionnel - par défaut au nom de la propriété) : le nom de la colonne mappée. Cela peut aussi être indiqué dans le(s) sous-élément(s) <column>. |
| (3) | type (optionnel) : nom indiquant le type Hibernate. |
| (4) | update, insert (optionnel - par défaut à true) : indique que les colonnes mappées devraient être incluses dans des UPDATE SQL et/ou des INSERT. Mettre les deux à false empêche la propagation en base de données (utile si vous savez qu'un trigger affectera la valeur à la colonne). |
| (5) | formula (optionnel) : une expression SQL qui définit la valeur pour une propriété calculée. Les propriétés calculées ne possède pas leur propre mapping. |
| (6) | access (optionnel - par défaut à property): Stratégie que Hibernate doit utiliser pour accéder à cette valeur. |
| (7) | lazy (optionnel - par défaut à false): Indique que cette propriété devrait être chargée en différé (lazy loading) quand on accède à la variable d'instance pour la première fois. |
| (8) | unique (optionnel): Génère le DDL d'une contrainte d'unicité pour les colonnes. Permet aussi d'en faire la cible d'un property-ref. |
| (9) | not-null (optionnel): Génère le DDL d'une contrainte de non nullité pour les colonnes. |
| (10) | optimistic-lock (optionnel - par défaut à true): Indique que les mises à jour de cette propriété peuvent ou non nécessiter l'acquisition d'un verrou optimiste. En d'autres termes, cela détermine s'il est nécessaire d'incrémenter un numéro de version quand cette propriété est marquée obsolète (dirty). |
| (11) | generated (optional - défaut ànever): Indique que la valeur de ce timestamp est générée par la base de données cf. Section 5.6, « Propriétés générées ». |
typename peut être:
Nom d'un type basique Hibernate (ex: integer, string, character, date, timestamp, float, binary, serializable, object, blob).
Nom d'une classe Java avec un type basique par défaut (ex: int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob).
Nom d'une classe Java sérialisable.
Nom d'une classe ayant un type spécifique (ex: com.illflow.type.MyCustomType).
Si vous n'indiquez pas un type, Hibernate utlisera la réflexion sur le nom de la propriété pour tenter de trouver le type Hibernate correct. Hibernate essayera d'interprêter le nom de la classe retournée par le getter de la propriété en utilisant les régles 2, 3, 4 dans cet ordre. Cependant, ce n'est pas toujours suffisant. Dans certains cas vous aurez encore besoin de l'attribut type (Par exemple, pour distinguer Hibernate.DATE et Hibernate.TIMESTAMP, ou pour préciser un type spécifique).
L'attribut access permet de contrôler comment Hibernate accèdera à la propriété à l'exécution. Par défaut, Hibernate utilisera les méthodes set/get. Si vous indiquez access="field", Hibernate ignorera les getter/setter et accèdera à la propriété directement en utilisant la réflexion. Vous pouvez spécifier votre propre stratégie d'accès aux propriété en donnant une classe qui implémente l'interface org.hibernate.property.PropertyAccessor.
Une fonctionnalité particulièrement intéressante est les propriétés dérivées. Ces propriétés sont par définition en lecture seule, la valeur de la propriété est calculée au chargement. Le calcul est déclaré comme une expression SQL, qui se traduit par une sous-requête SELECT dans la requête SQL qui charge une instance :
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>Remarquez que vous pouvez référencer la propre table des entités en ne déclarant pas un alias sur une colonne particulière (customerId dans l'exemple donné). Notez aussi que vous pouvez utiliser le sous-élément de mapping <formula> plutôt que d'utiliser l'attribut si vous le souhaitez.
Une association ordinaire vers une autre classe persistante est déclarée en utilisant un élément many-to-one. Le modèle relationnel est une association de type many-to-one : une clef étrangère dans une table référence la ou les clef(s) primaire(s) dans la table cible.
<many-to-one
name="propertyName" (1)
column="column_name" (2)
class="ClassName" (3)
cascade="cascade_style" (4)
fetch="join|select" (5)
update="true|false" (6)
insert="true|false" (6)
property-ref="propertyNameFromAssociatedClass" (7)
access="field|property|ClassName" (8)
unique="true|false" (9)
not-null="true|false" (10)
optimistic-lock="true|false" (11)
lazy="proxy|no-proxy|false" (12)
not-found="ignore|exception" (13) (14)
entity-name="EntityName" (15)
formula="arbitrary SQL expression" (16)
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>| (1) | name : Nom de la propriété. |
| (2) | column (optionnel) : Le nom de la clef étrangère. Cela peut être aussi indiqué avec le sous-élément <column>. |
| (3) | class (optionnel - par défaut le type de la propriété déterminé par réflexion) : Le nom de la classe associée. |
| (4) | cascade (optionnel) : Indique quelles opérations doivent être propagées de l'objet père vers les objets associés. |
| (5) | fetch (optionnel - par défaut à select) : Choisit entre le chargement de type outer-join ou le chargement par select successifs. |
| (6) | update, insert (optionnel - par défaut à true) : indique que les colonnes mappées devraient être incluses dans des UPDATE SQL et/ou des INSERT. Mettre les deux à false empêche la propagation en base de données (utile si vous savez qu'un trigger affectera la valeur à la colonne). |
| (7) | property-ref : (optionnel) Le nom d'une propriété de la classe associée qui est liée à cette clef étrangère. Si ce n'est pas spécifié, la clef primaire de la classe associée est utilisée. |
| (8) | access (optionnel - par défaut à property) : La stratégie à utiliser par Hibernate pour accéder à la valeur de cette propriété. |
| (9) | unique (optionnel) : Génère le DDL d'une contrainte d'unicité pour la clef étrangère. Permet aussi d'en faire la cible d'un property-ref. Cela permet de créer une véritable association one-to-one. |
| (10) | not-null (optionnel) : Génère le DDL pour une contrainte de non nullité pour la clef étrangère. |
| (11) | optimistic-lock (optionnel - par défaut à true) : Indique que les mises à jour de cette propriété requièrent ou non l'acquisition d'un verrou optimiste. En d'autres termes, détermine si un incrément de version doit avoir lieu quand la propriété est marquée obsolète (dirty). |
| (12) | lazy (optionnel - par défaut à false) : Indique que cette propriété doit être chargée en différé (lazy loading) au premier accès à la variable d'instance (nécessite une instrumentation du bytecode lors de la phase de construction). Remarquez que cela n'influence pas le comportement du proxy Hibernate - comme l'attribut lazy sur des classes ou des mappings de collections, mais utilise l'interception pour le chargement différé. lazy="false" indique que l'association sera toujours chargée. |
| (13) | not-found (optionnel - par défaut à exception) : Indique comment les clefs étrangères qui référencent des lignes manquantes doivent être manipulées : ignore traitera une ligne manquante comme une association nulle. |
| (15) | entity-name (optionnel) : Le nom de l'entité de la classe associée. |
| (16) | formula (optionnel) : une expression SQL qui définit la valeur pour une clé étrangère calculée. |
Donner une valeur significative à l'attribut cascade autre que none propagera certaines opérations à l'objet associé. Les valeurs significatives sont les noms des opérations Hibernate basiques, persist, merge, delete, save-update, evict, replicate, lock, refresh, ainsi que les valeurs spéciales delete-orphan et all et des combinaisons de noms d'opérations séparées par des virgules, comme par exemple cascade="persist,merge,evict" ou cascade="all,delete-orphan". Voir Section 10.11, « Persistance transitive » pour une explication complète. Notez que les assocations many-to-one et one-to-one ne supportent pas orphan delete.
Une déclaration many-to-one typique est aussi simple que :
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
L'attribut property-ref devrait être utilisé pour mapper seulement des données provenant d'un ancien système où les clefs étrangères font référence à une clef unique de la table associée et qui n'est pas la clef primaire. C'est un cas de mauvaise conception relationnelle. Par exemple, supposez que la classe Product a un numéro de série unique qui n'est pas la clef primaire. (L'attribut unique contrôle la génération DDL par Hibernate avec l'outil SchemaExport.)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
Ainsi le mapping pour OrderItem peut utiliser :
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
bien que ce ne soit certainement pas encouragé.
Si la clef unique référencée comprend des propriétés multiples de l'entité associée, vous devez mapper ces propriétés à l'intérieur d'un élément <properties>.
Une association one-to-one vers une autre classe persistante est déclarée avec l'élément one-to-one.
<one-to-one
name="propertyName" (1)
class="ClassName" (2)
cascade="cascade_style" (3)
constrained="true|false" (4)
fetch="join|select" (5)
property-ref="propertyNameFromAssociatedClass" (6)
access="field|property|ClassName" (7)
formula="any SQL expression" (8)
entity-name="EntityName" (9)
/>| (1) | name : Le nom de la propriété. |
| (2) | class (optionnel - par défaut du type de la propriété déterminé par réflexion) : Le nom de la classe associée. |
| (3) | cascade (optionnel) : Indique quelles opérations doivent être cascadées de l'objet père vers l'objet associé. |
| (4) | constrained (optionnel) : Indique qu'une contrainte de clef étrangère sur la clef primaire de la table mappée référence la table de la classe associée. Cette option affecte l'ordre dans lequel chaque save() et chaque delete() sont cascadés et détermine si l'association peut utiliser un proxy (aussi utilisé par l'outil d'export de schéma). |
| (5) | fetch (optionnel - par défaut à select) : Choisit entre récupération par jointure externe ou select séquentiel. |
| (6) | property-ref (optionnel) : Le nom de la propriété de la classe associée qui est jointe à la clef primaire de cette classe. Si ce n'est pas spécifié, la clef primaire de la classe associée est utilisée. |
| (7) | access (optionnel - par défaut à property) : La stratégie à utiliser par Hibernate pour accéder à la valeur de la propriété. |
| (8) | formula (optionnel) : Presque toutes les associations one-to-one pointent sur la clef primaire de l'entité propriétaire. Dans les rares cas différents, vous devez donner une ou plusieurs autres colonnes ou expression à joindre par une formule SQL (voir org.hibernate.test.onetooneformula pour un exemple). |
| (9) | lazy (optionnel - par défaut proxy) : Par défaut, les associations simples sont soumise à proxy. lazy="no-proxy" spécifie que la propriété doit être chargée à la demande au premier accès à l'instance. (nécessite l'intrumentation du bytecode à la construction). lazy="false" indique que l'association sera toujours chargée agressivement. Notez que si constrained="false", l'utilisation de proxy est impossible et Hibernate chargera automatiquement l'association ! |
| (10) | entity-name (optional) : The entity name of the associated class. |
Il existe deux types d'associations one-to-one :
associations par clef primaire
association par clef étrangère unique
Les associations par clef primaire ne nécessitent pas une colonne supplémentaire en table ; si deux lignes sont liés par l'association alors les deux lignes de la table partagent la même valeur de clef primaire. Donc si vous voulez que deux objets soient liés par une association par clef primaire, vous devez faire en sorte qu'on leur assigne la même valeur d'identifiant !
Pour une association par clef primaire, ajoutez les mappings suivants à Employee et Person, respectivement.
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Maintenant, vous devez faire en sorte que les clefs primaires des lignes liées dans les tables PERSON et EMPLOYEE sont égales. On utilise une stratégie Hibernate spéciale de génération d'identifiants appelée foreign :
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property">employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class>Une instance fraîchement enregistrée de Person se voit alors assignée la même valeur de clef primaire que l'instance de Employee référencée par la propriété employee de cette Person.
Alternativement, une clef étrangère avec contrainte d'unicité de Employee vers Person peut être indiquée ainsi :
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
Et cette association peut être rendue bidirectionnelle en ajoutant ceci au mapping de Person :
<one-to-one name"employee" class="Employee" property-ref="person"/>
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id>Bien que nous recommandions l'utilisation de clé primaire générée, vous devriez toujours essayer d'identifier des clé métier (naturelles) pour toutes vos entités. Une clé naturelle est une propriété ou une combinaison de propriétés uniques et non nulles. Si elle est aussi immuable, c'est encore mieux. Mappez les propriétés de la clé naturelle dans l'élément <natural-id>. Hibernate générera la clé unique nécessaire et les contraintes de non-nullité, et votre mapping s'auto-documentera.
Nous vous recommandons fortement d'implémenter equals() et hashCode() pour comparer les clés naturelles de l'entité.
Ce mapping n'est pas destiné à être utilisé avec des entités qui ont des clés naturelles.
mutable (optionel, par défaut à false) : Par défaut, les identifiants naturels sont supposés être immuable (constants).
L'élément <component> mappe les propriétés d'un objet fils aux colonnes d'une classe parente. Les composants peuvent en retour déclarer leurs propres propriétés, composants ou collections. Voir "Components" plus bas.
<component
name="propertyName" (1)
class="className" (2)
insert="true|false" (3)
update="true|false" (4)
access="field|property|ClassName" (5)
lazy="true|false" (6)
optimistic-lock="true|false" (7)
unique="true|false" (8)
>
<property ...../>
<many-to-one .... />
........
</component>| (1) | name : Nom de la propriété |
| (2) | class (optionnel - par défaut au type de la propriété déterminé par réflexion) : le nom de la classe (fille) du composant. |
| (3) | insert : Est ce que les colonnes mappées apparaissent dans les INSERTs ? |
| (4) | update: Est ce que les colonnes mappées apparaissent dans les UPDATEs ? |
| (5) | access (optionnel - par défaut à property) : La stratégie que Hibernate doit utiliser pour accéder à la valeur de cette propriété. |
| (6) | lazy (optionnel - par défaut à false) : Indique que ce composant doit être chargé au premier accès à la variable d'instance (nécessite une instrumentation du bytecode au moment du build). |
| (7) | optimistic-lock (optionnel - par défaut à true) : Indique que les mises à jour sur ce composant nécessitent ou non l'acquisition d'un verrou optimiste. En d'autres termes, cela détermine si une incrémentation de version doit avoir lieu quand la propriété est marquée obsolète (dirty). |
| (8) | unique (optionnel - par défaut à false) : Indique qu'une contrainte d'unicité existe sur toutes les colonnes mappées de ce composant. |
Les tags fils <property> mappent les propriétés de la classe fille sur les colonnes de la table.
L'élément <component> permet de déclarer sous-élément <parent> qui associe une propriété de la classe composant comme une référence arrière vers l'entité contenante.
L'élément <dynamic-component> permet à une Map d'être mappée comme un composant, quand les noms de la propriété font référence aux clefs de cette Map, voir Section 8.5, « Composant Dynamique ».
L'élément <properties> permet la définition d'un groupement logique nommé des propriétés d'une classe. L'utilisation la plus importante de cette construction est la possibilité pour une combinaison de propriétés d'être la cible d'un property-ref. C'est aussi un moyen pratique de définir une contrainte d'unicité multi-colonnes.
<properties
name="logicalName" (1)
insert="true|false" (2)
update="true|false" (3)
optimistic-lock="true|false" (4)
unique="true|false" (5)
>
<property ...../>
<many-to-one .... />
........
</properties>| (1) | name : Le nom logique d'un regroupement et non le véritable nom d'une propriété. |
| (2) | insert : Est-ce que les colonnes mappées apparaissent dans les INSERTs ? |
| (3) | update : Est-ce que les colonnes mappées apparaissent dans les UPDATEs ? |
| (4) | optimistic-lock (optionnel - par défaut à true) : Indique que les mises à jour sur ce composant nécessitent ou non l'acquisition d'un verrou optimiste. En d'autres termes, cela détermine si une incrémentation de version doit avoir lieu quand la propriété est marquée obsolète (dirty). |
| (5) | unique (optionnel - par défaut à false) : Indique qu'une contrainte d'unicité existe sur toutes les colonnes mappées de ce composant. |
Par exemple, si nous avons le mapping de <properties> suivant :
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class>Alors nous pourrions avoir une association sur des données d'un ancien système (legacy) qui font référence à cette clef unique de la table Person au lieu de la clef primaire :
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one>Nous ne recommandons pas l'utilisation de ce genre de chose en dehors du contexte de mapping de données héritées d'anciens systèmes.
Pour finir, la persistance polymorphique nécessite la déclaration de chaque sous-classe de la classe persistante de base. pour la stratégie de mapping de type table-per-class-hierarchy, on utilise la déclaration <subclass>.
<subclass
name="ClassName" (1)
discriminator-value="discriminator_value" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
<property .... />
.....
</subclass>| (1) | name : Le nom complet de la sous-classe. |
| (2) | discriminator-value (optionnel - par défaut le nom de la classe) : une valeur qui distingue les différentes sous-classes. |
| (3) | proxy (optionnel) : Indique une classe ou interface à utiliser pour les chargements à la demande des proxies (lazy). |
| (4) | lazy (optionnel, par défaut à true) : Spécifier lazy="false" désactive l'utilisation du chargement à la demande (lazy). |
Chaque sous-classe devrait déclarer ses propres propriétés persistantes et sous-classes. Les propriétés <version> et <id> sont implicitement hérités de la classe de base. Chaque sous-classe dans une hiérarchie doit définir une unique discriminator-value. Si aucune n'est spécifiée, le nom complet de la classe Java est utilisé.
Pour plus d'infos sur le mapping d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
Une autre façon possible de faire est la suivante, chaque sous-classe peut être mappée vers sa propre table (stratégie de mapping de type table-per-subclass). L'état hérité est récupéré en joignant la table de la super-classe. L'élément <joined-subclass> est utilisé.
<joined-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName">
<key .... >
<property .... />
.....
</joined-subclass>| (1) | name : Le nom Java complet de la sous-classe. |
| (2) | table : Le nom de la table de la sous-classe. |
| (3) | proxy (optionnel) : Indique une classe ou interface pour le chargement différé des proxies. |
| (4) | lazy (optionnel, par défaut à true) : Indiquer lazy="false" désactive l'utilisation du chargement à la demande. |
Aucune colonne discriminante n'est nécessaire pour cette stratégie de mapping. Cependant, chaque sous-classe doit déclarer une colonne de table contenant l'objet identifiant qui utilise l'élément <key>. Le mapping au début de ce chapitre serait ré-écrit ainsi :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat" table="CATS">
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<class name="eg.Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
Une troisième option est de seulement mapper vers des tables les classes concrètes d'une hiérarchie d'héritage, (stratégie de type table-per-concrete-class) où chaque table définit tous les états persistants de la classe, y compris les états hérités. Dans Hibernate il n'est absolument pas nécessaire de mapper explicitement de telles hiérarchies d'héritage. Vous pouvez simplement mapper chaque classe avec une déclaration <class> différente. Cependant, si vous souhaitez utiliser des associations polymorphiques (càd une association vers la superclasse de la hiérarchie), vous devez utiliser le mapping <union-subclass>.
<union-subclass
name="ClassName" (1)
table="tablename" (2)
proxy="ProxyInterface" (3)
lazy="true|false" (4)
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName">
<property .... />
.....
</union-subclass>| (1) | name : Le nom Java complet de la sous-classe. |
| (2) | table : nom de la table de la sous-classe. |
| (3) | proxy (optionnel) : Indique une classe ou interface pour le chargement différé des proxies. |
| (4) | lazy (optionnel, par défaut à true) : Indiquer lazy="false" désactive l'utilisation du chargement à la demande. |
Aucune colonne discriminante ou colonne clef n'est requise pour cette stratégie de mapping.
Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
En utilisant l'élément <join>, il est possible de mapper des propriétés d'une classe sur plusieurs tables.
<join
table="tablename" (1)
schema="owner" (2)
catalog="catalog" (3)
fetch="join|select" (4)
inverse="true|false" (5)
optionnel="true|false"> (6)
<key ... />
<property ... />
...
</join>| (1) | table : Le nom de la table jointe. |
| (2) | schema (optionnel) : court-circuite le nom de schéma spécifié par l'élément de base <hibernate-mapping>. |
| (3) | catalog (optionnel) : court-circuite le nom de catalogue spécifié par l'élément de base <hibernate-mapping>. |
| (4) | fetch (optionnel - par défaut à join) : Si positionné à join, Hibernate utilisera une jointure interne pour charger une jointure définie par une classe ou ses super-classes et une jointure externe pour une <jointure> définie par une sous-classe. Si positionné à select alors Hibernate utilisera un select séquentiel pour une <jointure> définie sur une sous-classe, qui ne sera délivrée que si une ligne se représente une instance de la sous-classe. Les jointures internes seront quand même utilisées pour charger une <jointure> définie par une classe et ses super-classes. |
| (5) | inverse (optionnel - par défaut à false) : Si positionné à true, Hibernate n'essaiera pas d'insérer ou de mettre à jour les propriétés définies par cette jointure. |
| (6) | optionnel (optionnel - par défaut à false) : Si positionné à true, Hibernate insèrera une ligne seulement si les propriétés définies par cette jointure sont non-nulles et utilisera toujours une jointure externe pour charger les propriétés. |
Par exemple, les informations d'adresse pour une personne peuvent être mappées vers une table séparée (tout en préservant des sémantiques de type valeur pour toutes ses propriétés) :
<class name="Person"
table="PERSON">
<id name="id" column="PERSON_ID">...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...Cette fonctionnalité est souvent seulement utile pour les modèles de données hérités d'anciens systèmes (legacy), nous recommandons d'utiliser moins de tables que de classes et un modèle de domaine à granularité fine. Cependant, c'est utile pour passer d'une stratégie de mapping d'héritage à une autre dans une hiérarchie simple ainsi qu'il est expliqué plus tard.
Nous avons rencontré l'élément <key> à plusieurs reprises maintenant. Il apparaît partout que l'élément de mapping parent définit une jointure sur une nouvele table, et définit la clef étrangère dans la table jointe, ce qui référence la clef primaire de la table d'origine.
<key
column="columnname" (1)
on-delete="noaction|cascade" (2)
property-ref="propertyName" (3)
not-null="true|false" (4)
update="true|false" (5)
unique="true|false" (6)
/>| (1) | column (optionnel) : Le nom de la colonne de la clef étrangère Cela peut aussi être spécifié par l'élément(s) intégré(s) <column>. |
| (2) | on-delete (optionnel, par défaut à noaction) : Indique si la contrainte de clef étrangère possède la possibilité au niveau base de données de suppression en cascade. |
| (3) | property-ref (optionnel) : Indique que la clef étrangère fait référence à des colonnes qui ne sont pas la clef primaire de la table d'origine (Pour les données de systèmes legacy). |
| (4) | not-null (optionnel) : Indique que les colonnes des clefs étrangères ne peuvent pas être nulles (c'est implicite si la clef étrangère fait partie de la clef primaire). |
| (5) | update (optionnel) : Indique que la clef étrangère ne devrait jamais être mise à jour (implicite si celle-ci fait partie de la clef primaire). |
| (6) | unique (optionnel) : Indique que la clef étrangère doit posséder une contrainte d'unicité (implicite si la clef étrangère est aussi la clef primaire). |
Nous recommandons pour les systèmes où les suppressions doivent être performantes de définir toutes les clefs on-delete="cascade", ainsi Hibernate utilisera une contrainte ON CASCADE DELETE au niveau base de données, plutôt que de nombreux DELETE individuels. Attention, cette fonctionnalité court-circuite la stratégie habituelle de verrou optimiste pour les données versionnées.
Les attributs not-null et update sont utiles pour mapper une association one-to-many unidirectionnelle. Si vous mappez un one-to-many unidirectionnel vers une clef étrangère non nulle, vous devez déclarer la colonne de la clef en utilisant <key not-null="true">.
Tout élément de mapping qui accepte un attribut column acceptera alternativement un sous-élément <column>. De façon identique, <formula> est une alternative à l'attribut formula.
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"/><formula>SQL expression</formula>
Les attributs column et formula peuvent même être combinés au sein d'une même propriété ou mapping d'association pour exprimer, par exemple, des conditions de jointure exotiques.
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one>Supposez que votre application possède deux classes persistantes du même nom, et vous ne voulez pas préciser le nom Java complet (packages inclus) dans les queries Hibernate. Les classes peuvent alors être "importées" explicitement plutôt que de compter sur auto-import="true".Vous pouvez même importer des classes et interfaces qui ne sont pas mappées explicitement.
<import class="java.lang.Object" rename="Universe"/>
<import
class="ClassName" (1)
rename="ShortName" (2)
/>| (1) | class : Nom Java complet de la classe. |
| (2) | rename (optionnel - par défaut vaut le nom de la classe Java (sans package)) : Nom pouvant être utilisé dans le langage de requête. |
Il existe encore un type de mapping de propriété. L'élément de mapping <any> définit une association polymorphique vers des classes de tables multiples. Ce type de mapping requiert toujours plus d'une colonne. La première colonne contient le type de l'entité associée. Les colonnes restantes contiennent l'identifiant. il est impossible de spécifier une contrainte de clef étrangère pour ce type d'association, donc ce n'est certainement pas considéré comme le moyen habituel de mapper des associations (polymorphiques). Vous devriez utiliser cela uniquement dans des cas particuliers (par exemple des logs d'audit, des données de session utilisateur, etc...).
L'attribut meta-type permet à l'application de spécifier un type personnalisé qui mappe des valeurs de colonnes de le base de données sur des classes persistantes qui ont un attribut identifiant du type spécifié par id-type. Vous devez spécifier le mapping à partir de valeurs du méta-type sur les noms des classes.
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any><any
name="propertyName" (1)
id-type="idtypename" (2)
meta-type="metatypename" (3)
cascade="cascade_style" (4)
access="field|property|ClassName" (5)
optimistic-lock="true|false" (6)
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any>| (1) | name : le nom de la propriété. |
| (2) | id-type : le type identifiant. |
| (3) | meta-type (optionnel - par défaut à string) : Tout type permis pour un mapping par discriminateur. |
| (4) | cascade (optionnel - par défaut à none) : le style de cascade. |
| (5) | access (optionnel - par défaut à property) : La stratégie à utiliser par Hibernate pour accéder à cette propriété. |
| (6) | optimistic-lock (optionnel - par défaut à true) : Indique que les mises à jour sur cette propriété nécessitent ou non l'acquisition d'un verrou optimiste. En d'autres termes, définit si un incrément de version doit avoir lieu quand cette propriété est marquée dirty. |
Pour comprendre le comportement des différents objets Java par rapport au service de persistance, nous avons besoin de les classer en deux groupes :
Une entité existe indépendamment de tout autre objet possédant une référence vers l'entité. Comparez cela avec le modèle Java habituel où un objet est supprimé par le garbage collector dès qu'il n'est plus référencé. Les entités doivent être explicitement enregistrées et supprimées (sauf dans les cas où sauvegardes et suppressions sont cascadées d'une entité mère vers ses enfants). C'est différent du modèle ODMG de persistance par atteignabilité - et correspond mieux à la façon dont les objets sont habituellement utilisés dans des grands systèmes. Les entités permettent les références circulaires et partagées. Elles peuvent aussi être versionnées.
L'état persistant d'une entité consiste en des références vers d'autres entités et instances de types valeurs. Ces valeurs sont des types primitifs, des collections (et non le contenu d'une collection), des composants de certains objets immuables. Contrairement aux entités, les valeurs (et en particulier les collections et composants) sont persistés par atteignabiliité. Comme les valeurs (et types primitifs) sont persistés et supprimés avec l'entité qui les contient, ils ne peuvent pas posséder leurs propres versions. Les valeurs n'ont pas d'identité indépendantes, ainsi elles ne peuvent pas être partagées par deux entités ou collections.
Jusqu'à présent nous avons utilisé le terme "classe persistante" pour parler d'entités. Nous allons continuer à faire ainsi. Cependant, au sens strict, toutes les classes définies par un utilisateur possédant un état persistant ne sont pas des entités. Un composant est une classe définie par un utilisateur avec les caractéristiques d'une valeur. Une propriété Java de type java.lang.String a aussi les caractéristiques d'une valeur. Given this definition, we can say that all types (classes) provided by the JDK have value type semantics in Java, while user-defined types may be mapped with entity or value type semantics. This decision is up to the application developer. A good hint for an entity class in a domain model are shared references to a single instance of that class, while composition or aggregation usually translates to a value type.
Nous nous pencherons sur ces deux concepts tout au long de la documentation.
Le défi est de mapper les type Javas (et la définition des développeurs des entités et valeurs types) sur les types du SQL ou des bases de données. Le pont entre les deux systèmes est proposé par Hibernate : pour les entités nous utilisons <class>, <subclass> et ainsi de suite. Pour les types valeurs nous utilisons <property>, <component>, etc., habituellement avec un attribut type. La valeur de cet attribut est le nom d'un type de mapping Hibernate. Hibernate propose de base de nombreux mappings (pour les types de valeurs standards du JDK). Vous pouvez écrire vos propres types de mappings et implémenter aussi vos propres stratégies de conversion, nous le verrons plus tard.
Tous les types proposés de base par Hibernate à part les collections autorisent la valeur null.
Les types basiques de mapping proposés de base peuvent grossièrement être rangés dans les catégories suivantes :
Les mappings de type des primitives Java ou leurs classes wrappers (ex: Integer pour int) vers les types SQL (propriétaires) appropriés. boolean, yes_noet true_false sont tous des alternatives pour les types Java boolean ou java.lang.Boolean.
Mapping de type de java.lang.String vers VARCHAR (ou le VARCHAR2 Oracle).
Mappings de type pour java.util.Date et ses sous-classes vers les types SQL DATE, TIME et TIMESTAMP (ou équivalent).
Mappings de type pour java.util.Calendar vers les types SQL TIMESTAMP et DATE (ou équivalent).
Mappings de type pour java.math.BigDecimal et java.math.BigInteger vers NUMERIC (ou le NUMBER Oracle).
Mappings de type pour java.util.Locale, java.util.TimeZone et java.util.Currency vers VARCHAR (ou le VARCHAR2 Oracle). Les instances de Locale et Currency sont mappées sur leurs codes ISO. Les instances de TimeZone sont mappées sur leur ID.
Un type de mapping pour java.lang.Class vers VARCHAR (ou le VARCHAR2 Oracle). Un objet Class est mappé sur son nom Java complet.
Mappe les tableaux de bytes vers le type binaire SQL approprié.
Mappe les longues chaînes de caractères Java vers les types SQL CLOB ou TEXT.
Mappe les types Java sérialisables vers le type SQL binaire approprié. Vous pouvez aussi indiquer le type Hibernate serializable avec le nom d'une classe Java sérialisable ou une interface qui ne soit pas par défaut un type de base.
Mappings de type pour les classes JDBC java.sql.Clob and java.sql.Blob. Ces types peuvent ne pas convenir pour certaines applications car un objet blob ou clob peut ne pas être réutilisable en dehors d'une transaction (de plus l'implémentation par les pilotes est moyennement bonne).
Mappings de type pour ceux qui sont habituellement modifiable, pour lesquels Hibernate effectue certains optimisations convenant seulement aux types Java immuables, et l'application les traite comme immuable. Par exemple, vous ne devriez pas appeler Date.setTime() sur une instance mappée sur un imm_timestamp. Pour changer la valeur de la propriété, et faire que cette modification soit persistée, l'application doit assigner un nouvel (non identique) objet à la propriété.
Les identifiants uniques des entités et collections peuvent être de n'importe quel type de base excepté binary, blob et clob (les identifiants composites sont aussi permis, voir plus bas).
Les types de base des valeurs ont des Type constants correspondants définis dans org.hibernate.Hibernate. Par exemple, Hibernate.STRING représenté le type string.
Il est assez facile pour les développeurs de créer leurs propres types de valeurs. Par exemple, vous pourriez vouloir persister des propriétés du type java.lang.BigInteger dans des colonnnes VARCHAR. Hibernate ne procure pas par défaut un type pour cela. Mais les types que vous pouvez créer ne se limitent pas à mapper des propriétés (ou élément collection) à une simple colonne d'une table. Donc, par exemple, vous pourriez avoir une propriété Java getName()/setName() de type java.lang.String persistée dans les colonnes FIRST_NAME, INITIAL, SURNAME.
Pour implémenter votre propre type, vous pouvez soit implémenter org.hibernate.UserType soit org.hibernate.CompositeUserType et déclarer des propriétés utilisant des noms de classes complets du type. Regardez org.hibernate.test.DoubleStringType pour voir ce qu'il est possible de faire.
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property>Remarquez l'utilisation des tags <column> pour mapper une propriété sur des colonnes multiples.
Les interfaces CompositeUserType, EnhancedUserType, UserCollectionType, et UserVersionType permettent des utilisations plus spécialisées.
Vous pouvez même donner des paramètres en indiquant UserType dans le fichier de mapping ; Pour cela, votre UserType doit implémenter l'interface org.hibernate.usertype.ParameterizedType. Pour spécifier des paramètres dans votre type propre, vous pouvez utiliser l'élément <type> dans vos fichiers de mapping.
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default">0</param>
</type>
</property>Le UserType permet maintenant de récupérer la valeur pour le paramètre nommé default à partir de l'objet Properties qui lui est passé.
Si vous utilisez fréquemment un UserType, cela peut être utile de lui définir un nom plus court. Vous pouvez faire cela en utilisant l'élément <typedef>. Les typedefs permettent d'assigner un nom à votre type propre et peuvent aussi contenir une liste de valeurs de paramètres par défaut si ce type est paramétré.
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default">0</param>
</typedef><property name="priority" type="default_zero"/>
Il est aussi possible de redéfinir les paramètres par défaut du typedef au cas par cas en utilisant des paramètres type sur le mapping de la propriété.
Bien que le fait que Hibernate propose de base une riche variété de types, et qu'il supporte les composants signifie que vous aurez très rarement besoin d'utiliser un nouveau type propre, il est néanmoins de bonne pratique d'utiliser des types propres pour les classes (non entités) qui apparaissent fréquemment dans votre application. Par exemple une classe MonetaryAmount est un bon candidat pour un CompositeUserType même s'il pourrait facilement être mappé comme un composant. Une motivation pour cela est l'abstraction. Avec un type propre vos documents de mapping sont à l'abri des changements futurs dans votre façon de représenter des valeurs monétaires.
Il est possible de proposer plus d'un mapping par classe persistante. Dans ce cas, vous devez spécifier un nom d'entité pour lever l'ambiguité entre les instances des entités mappées (par défaut, le nom de l'entité est celui de la classe). Hibernate vous permet de spécifier le nom de l'entité lorsque vous utilisez des objets persistants, lorsque vous écrivez des requêtes ou quand vous mappez des associations vers les entités nommées.
<class name="Contract" table="Contracts"
entity-name="CurrentContract">
...
<set name="history" inverse="true"
order-by="effectiveEndDate desc">
<key column="currentContractId"/>
<one-to-many entity-name="HistoricalContract"/>
</set>
</class>
<class name="Contract" table="ContractHistory"
entity-name="HistoricalContract">
...
<many-to-one name="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class>Remarquez comment les associations sont désormais spécifiées en utilisant entity-name au lieu de class.
Vous pouvez forcer Hibernate à mettre un identifiant entre quotes dans le SQL généré en mettant le nom de la table ou de la colonne entre backticks dans le document de mapping. Hibernate utilisera les bons styles de quotes pour le Dialect SQL (habituellement des doubles quotes, mais des parenthèses pour SQL server et des backticks pour MySQL).
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class>XML ne convient pas à tout le monde, il y a donc des moyens alternatifs pour définir des metatda de mappings O/R dans Hibernate.
De nombreux utilisateurs de Hibernate préfèrent embarquer les informations de mappings directement au sein du code source en utilisant les tags XDoclet @hibernate.tags. Nous ne couvrons pas cette approche dans ce document cependant, puisque c'est considéré comme faisant partie de XDoclet. Cependant, nous présentons l'exemple suivant de la classe Cat avec des mappings XDoclet.
package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
/*
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
/**
* @hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother() {
return mother;
}
void setMother(Cat mother) {
this.mother = mother;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}Voyez le site web de Hibernate pour plus d'exemples sur XDoclet et Hibernate.
Le JDK 5.0 introduit des annotations proches de celles de XDoclet au niveau java, qui sont type-safe et vérifiées à la compilation. Ce mécanisme est plus puissant que XDoclet et mieux supporté par les outils et IDE. IntelliJ IDEA, par exemple, supporte l'auto-complétion et le surlignement syntaxique des annotations JDK 5.0. La nouvelle révision des spécifications des EJB (JSR-220) utilise les annotations JDK 5.0 comme mécanisme primaire pour les meta-données des beans entités. Hibernate3 implémente l'EntityManager de la JSR-220 (API de persistance), le support du mapping de meta-données est disponible via le package Hibernate Annotations, en tant que module séparé à télécharger. EJB3 (JSR-220) et les métadata Hibernate3 sont supportés.
Ceci est un exemple d'une classe POJO annotée comme un EJB entité :
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
@Id;
Long id;
String firstName;
String lastName;
Date birthday;
@Transient
Integer age;
@Embedded
private Address homeAddress;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="CUSTOMER_ID")
Set<Order> orders;
// Getter/setter and business methods
}Notez que le support des annotations JDK 5.0 (et de la JSR-220) est encore en cours et n'est pas terminé. Référez vous au module Hibernate Annotation pour plus de détails.
Les propriétés générées sont des propriétés dont les valeurs sont générées par la base de données. Typiquement, les applications Hibernate avaient besoin d'invoquer refresh sur les instances qui contenaient des propriétés pour lesquelles la base de données générait des valeurs. Marquer les propriétés comme générées permet à l'application de déléguer cette responsabilité à Hibernate. Principalement, à chaque fois qu'Hibernate réalise une insertion ou une mise à jour en base de données pour une entité marquée comme telle, cela provoque immédiatement un select pour récupérer les valeurs générées.
Les propriétés marquées comme générées doivent de plus ne pas être insérables et modifiables Seuls Section 5.1.7, « version (optionnel) », Section 5.1.8, « timestamp (optionnel) », et Section 5.1.9, « property » peuvent être marqués comme générées.
never (par défaut) - indique la valeur de la propriété n'est pas générée dans la base de données.
insert - indique que la valeur de la propriété donnée est générée à l'insertion mais pas lors des futures mises à jour de l'enregistrement. Les colonnes de type "date de création" sont le cas d'utilisation typique de cette option. Notez que même les propriétés Section 5.1.7, « version (optionnel) » et Section 5.1.8, « timestamp (optionnel) » peuvent être déclarées comme générées, cette option n'est pas disponible à cet endroit...
always - indique que la valeur de la propriété est générée à l'insert comme aux updates.
Permettent les ordres CREATE et DROP d'objets arbitraire de la base de donnéées, en conjonction avec les outils Hibernate d'évolutions de schéma, pour permettre de définir complètement un schéma utilisateur au sein des fichiers de mapping Hibernate. Bien que conçu spécifiquement pour créer et supprimer des objets tels que des triggers et des procédures stockées, ou toute commande pouvant être exécutée via une méthode de java.sql.Statement.execute() (ALTERs, INSERTS, etc). Il y a principalement deux modes pour définir les objets auxiliaires de base de données...
Le premier mode est de lister explicitement les commandes CREATE et DROP dans le fichier de mapping:
<hibernate-mapping>
...
<database-object>
<create>CREATE TRIGGER my_trigger ...</create>
<drop>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping>Le second mode est de fournir une classe particulière qui connait comment construire les commandes CREATE et DROP. Cette classe particulière doit implémenter l'interface org.hibernate.mapping.AuxiliaryDatabaseObject.
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping>Additionnellement, ces objets de base de données peuvent être optionnellement traités selon l'utilisation de dialectes particuliers..
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9Dialect"/>
<dialect-scope name="org.hibernate.dialect.OracleDialect"/>
</database-object>
</hibernate-mapping>Hibernate requiert que les champs contenant des collections persistantes soient déclarés comme des types d'interface, par exemple :
public class Product {
private String serialNumber;
private Set parts = new HashSet();
public Set getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
}L'interface réelle devrait être java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap ou ... n'importe quoi d'autre ! (Où "n'importe quoi d'autre" signifie que vous devrez écrire une implémentation de org.hibernate.usertype.UserCollectionType.)
Notez comment nous avons initialisé les variables d'instance avec une instance de HashSet. C'est le meilleur moyen pour initialiser les collections d'instances nouvellement créées (non persistantes). Quand nous fabriquons l'instance persistante - en appelant persist(), par exemple - Hibernate remplacera réellement le HashSet avec une instance d'une implémentation propre à Hibernate de Set. Prenez garde aux erreurs :
Cat cat = new DomesticCat(); Cat kitten = new DomesticCat(); .... Set kittens = new HashSet(); kittens.add(kitten); cat.setKittens(kittens); session.persist(cat); kittens = cat.getKittens(); // Ok, la collection kittens est un Set (HashSet) cat.getKittens(); // Erreur !
Les collections persistantes injectées par Hibernate se comportent de la même manière que HashMap, HashSet, TreeMap, TreeSet ou ArrayList, selon le type de l'interface.
Les instances des collections ont le comportement habituel des types des valeurs. Elles sont automatiquement persistées quand elles sont référencées par un objet persistant et automatiquement effacées quand elles sont déréférencées. Si une collection est passée d'un objet persistant à un autre, ses éléments pourraient être déplacés d'une table à une autre. Deux entités ne peuvent pas partager une référence vers une même instance d'une collection. Dû au modèle relationnel sous-jacent, les propriétés contenant des collections ne supportent pas la sémantique de la valeur null ; Hibernate ne distingue pas une référence vers une collection nulle d'une collection vide.
Vous ne devriez pas vous préoccuper trop de ça. Utilisez les collections persistantes de la même manière que vous utilisez des collections Java ordinaires. Assurez-vous de comprendre la sémantique des associations bidirectionnelles (traitée plus loin).
L'élément de mapping d'Hibernate utilisé pour mapper une collection dépend du type de l'interface. Par exemple, un élément <set> est utilisé pour mapper des propriétés de type Set.
<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class>À part <set>, il y aussi les éléments de mapping <list>, <map>, <bag>, <array> et <primitive-array>. L'élément <map> est représentatif :
<map
name="nomDePropriete" (1)
table="nom_de_table" (2)
schema="nom_du_schema" (3)
lazy="true|extra|false" (4)
inverse="true|false" (5)
cascade="all|none|save-update|delete|all-delete-orphan" (6)
sort="unsorted|natural|ClasseDeComparateur" (7)
order-by="nom_de_column asc|desc" (8)
where="condition sql where quelcconque" (9)
fetch="join|select|subselect" (10)
batch-size="N" (11)
access="field|property|NomDeClasse" (12)
optimistic-lock="true|false" (13)
mutable="true|false" (14)
node="nom-d-element|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map>| (1) | name : le nom de la propriété contenant la collection |
| (2) | table (optionnel - par défaut = nom de la propriété) : le nom de la table de la collection (non utilisé pour les associations one-to-many) |
| (3) | schema (optionnel) : le nom du schéma pour surcharger le schéma déclaré dans l'élément racine |
| (4) | lazy (optionnel - par défaut = true) : peut être utilisé pour désactiver l'initialisation tardive et spécifier que l'association est toujours rapportée, ou pour activer la récupération extra-paresseuse (NdT : extra-lazy) où la plupart des opérations n'initialisent pas la collection (approprié pour de très grosses collections) |
| (5) | inverse (optionnel - par défaut = false) : définit cette collection comme l'extrêmité "inverse" de l'association bidirectionnelle |
| (6) | cascade (optionnel - par défaut = none) : active les opérations de cascade vers les entités filles |
| (7) | sort (optionnel) : spécifie une collection triée via un ordre de tri naturel, ou via une classe comparateur donnée (implémentant Comparator) |
| (8) | order-by (optionnel, seulement à partir du JDK1.4) : spécifie une colonne de table (ou des colonnes) qui définit l'ordre d'itération de Map, Set ou Bag, avec en option asc ou desc |
| (9) | where (optionnel) : spécifie une condition SQL arbitraire WHERE à utiliser au chargement ou à la suppression d'une collection (utile si la collection ne doit contenir qu'un sous ensemble des données disponibles) |
| (10) | fetch (optionnel, par défaut = select) : à choisir entre récupération par jointures externes, récupération par selects séquentiels, et récupération par sous-selects séquentiels |
| (11) | batch-size (optionnel, par défaut = 1) : une taille de batch (batch size) utilisée pour charger plusieurs instances de cette collection en initialisation tardive |
| (12) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété |
| (13) | optimistic-lock (optionnel - par défaut = true) : spécifie que changer l'état de la collection entraîne l'incrémentation de la version appartenant à l'entité (Pour une association un vers plusieurs, il est souvent raisonnable de désactiver ce paramètre) |
| (14) | mutable (optionnel - par défaut = true) : une valeur à false spécifie que les éléments de la collection ne changent jamais (une optimisation mineure dans certains cas) |
Les instances d'une collection sont distinguées dans la base par la clef étrangère de l'entité qui possède la collection. Cette clef étrangère est référencée comme la(es) colonne(s) de la clef de la collection de la table de la collection. La colonne de la clef de la collection est mappée par l'élément <key>.
Il peut y avoir une contrainte de nullité sur la colonne de la clef étrangère. Pour les associations unidirectionnelles un vers plusieurs, la colonne de la clef étrangère peut être nulle par défaut, donc vous pourriez avoir besoin de spécifier not-null="true".
<key column="productSerialNumber" not-null="true"/>
La contraite de la clef étrangère peut utiliser ON DELETE CASCADE.
<key column="productSerialNumber" on-delete="cascade"/>
Voir le chapitre précédent pour une définition complète de l'élément <key>.
Les collections peuvent contenir la plupart des autres types Hibernate, dont tous les types basiques, les types utilisateur, les composants, et bien sûr, les références vers d'autres entités. C'est une distinction importante : un objet dans une collection pourrait être géré avec une sémantique de "valeur" (sa durée de vie dépend complètement du propriétaire de la collection) ou il pourrait avoir une référence vers une autre entité, avec sa propre durée de vie. Dans le dernier cas, seul le "lien" entre les 2 objets est considéré être l'état retenu par la collection.
Le type contenu est référencé comme le type de l'élément de la collection. Les éléments de la collections sont mappés par <element> ou <composite-element>, ou dans le cas des références d'entité, avec <one-to-many> ou <many-to-many>. Les deux premiers mappent des éléments avec un sémantique de valeur, les deux suivants sont utilisés pour mapper des associations d'entité.
Tous les mappings de collection, exceptés ceux avec les sémantiques d'ensemble (NdT : set) et de sac (NdT : bag), ont besoin d'une colonne d'index dans la table de la collection - une colonne qui mappe un index de tableau, ou un index de List, ou une clef de Map. L'index d'une Map peut être n'importe quel type basique, mappé avec <map-key>, ça peut être une référence d'entité mappée avec <map-key-many-to-many>, ou ça peut être un type composé, mappé avec <composite-map-key>. L'index d'un tableau ou d'une liste est toujours de type integer et est mappé en utilisant l'élément <list-index>. Les colonnes mappées contiennent des entiers séquentiels (numérotés à partir de zéro par défaut).
<list-index
column="nom_de_colonne" (1)
base="0|1|..."/>| (1) | nom_de_colonne (requis) : le nom de la colonne contenant les valeurs de l'index de la collection |
| (1) | base (optionnel, par défaut = 0) : la valeur de la colonne de l'index qui correspond au premier élément de la liste ou du tableau |
<map-key
column="nom_de_colonne" (1)
formula="n'importe quelle expression(2) SQL"
type="nom_du_type" (3)
node="@nom-d-attribut"
length="N"/>| (1) | column (optionnel) : le nom de la colonne contenant les valeurs de l'index de la collection |
| (2) | formula (optionnel) : une formule SQL utilisée pour évaluer la clef de la map |
| (3) | type (reguis): le type des clefs de la map |
<map-key-many-to-many
column="nom_de_colonne" (1)
formula="n'importe quelle expression(2)(3) SQL"
class="NomDeClasse"
/>| (1) | column (optionnel) : le nom de la colonne de la clef étrangère pour les valeurs de l'index de la collection |
| (2) | formula (optionnel) : une formulre SQL utilisée pour évaluer la clef étrangère de la clef de la map |
| (3) | class (requis): la classe de l'entité utilisée comme clef de la map |
Si votre table n'a pas de colonne d'index, et que vous souhaitez tout de même utiliser List comme type de propriété, vous devriez mapper la propriété comme un <bag> Hibernate. Un sac (NdT : bag) ne garde pas son ordre quand il est récupéré de la base de données, mais il peut être optionnellement trié ou ordonné.
Il y a pas mal de variétés de mappings qui peuvent être générés pour les collections, couvrant beaucoup des modèles relationnels communs. Nous vous suggérons d'expérimenter avec l'outil de génération de schéma pour avoir une idée de comment traduire les différentes déclarations de mapping vers des table de la base de données.
N'importe quelle collection de valeurs ou association plusieurs-vers-plusieurs requiert une table de collection avec une(des) colonne(s) de clef étrangère, une(des) colonne(s) d'élément de la collection ou des colonnes et possiblement une(des) colonne(s) d'index.
Pour une collection de valeurs, nous utilisons la balise <element>.
<element
column="nom_de_colonne" (1)
formula="n'importe quelle expression SQL"(2)
type="nomDeType" (3)
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="nom-d-element"
/>| (1) | column (optionnel) : le nom de la colonne contenant les valeurs de l'élément de la collection |
| (2) | formula (optionnel) : une formule SQL utilisée pour évaluer l'élément |
| (3) | type (requis) : le type de l'élément de la collection |
Une association plusieurs-vers-plusieurs est spécifiée en utilisant l'élément <many-to-many>.
<many-to-many
column="nom_de_colonne" (1)
formula="n'importe quelle expression SQL" (2)
class="NomDeClasse" (3)
fetch="select|join" (4)
unique="true|false" (5)
not-found="ignore|exception" (6)
entity-name="NomDEntite" (7)
property-ref="nomDeProprieteDeLaClasseAssociee" (8)
node="nom-d-element"
embed-xml="true|false"
/>| (1) | column (optionnel) : le nom de la colonne de la clef étrangère de l'élément |
| (2) | formula (optionnel) : une formule SQL utilisée pour évaluer la valeur de la clef étrangère de l'élément |
| (3) | class (requis) : le nom de la classe associée |
| (4) | fetch (optionnel - par défaut join) : active les récupérations par jointures externes ou par selects séquentiels pour cette association. C'est un cas spécial ; pour une récupération complète sans attente (dans un seul SELECT) d'une entité et de ses relations plusieurs-vers-plusieurs vers d'autres entités, vous devriez activer la récupération join non seulement sur la collection elle-même, mais aussi avec cet attribut sur l'élément imbriqué <many-to-many>. |
| (5) | unique (optionnel) : activer la génération DDL d'une contrainte d'unicité pour la colonne de la clef étrangère. Ça rend la pluralité de l'association effectivement un-vers-plusieurs. |
| (6) | not-found (optionnel - par défaut exception) : spécifie comment les clefs étrangères qui référencent la lignes manquantes seront gérées : ignore traitera une ligne manquante comme une association nulle. |
| (7) | entity-name (optionnel) : le nom de l'entité de la classe associée, comme une alternative à class |
| (8) | property-ref (optionnel) : le nom d'une propriété de la classe associée qui est jointe à cette clef étrangère. Si non spécifiée, la clef primaire de la classe associée est utilisée. |
Quelques exemples, d'abord, un ensemble de chaînes de caractères :
<set name="names" table="person_names">
<key column="person_id"/>
<element column="person_name" type="string"/>
</set>Un bag contenant des entiers (avec un ordre d'itération déterminé par l'attribut order-by) :
<bag name="sizes"
table="item_sizes"
order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag>Un tableau d'entités - dans ce cas, une association plusieurs-vers-plusieurs :
<array name="addresses"
table="PersonAddress"
cascade="persist">
<key column="personId"/>
<list-index column="sortOrder"/>
<many-to-many column="addressId" class="Address"/>
</array>Une map de chaînes de caractères vers des dates :
<map name="holidays"
table="holidays"
schema="dbo"
order-by="hol_name asc">
<key column="id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>Une liste de composants (discute dans le prochain chapitre) :
<list name="carComponents"
table="CarComponents">
<key column="carId"/>
<list-index column="sortOrder"/>
<composite-element class="CarComponent">
<property name="price"/>
<property name="type"/>
<property name="serialNumber" column="serialNum"/>
</composite-element>
</list>Une association un vers plusieurs lie les tables de deux classes par une clef étrangère, sans l'intervention d'une table de collection. Ce mapping perd certaines sémantiques des collections Java normales :
Une instance de la classe de l'entité contenue ne peut pas appartenir à plus d'une instance de la collection
Une instance de la classe de l'entité contenue ne peut pas apparaître plus plus d'une valeur d'index de la collection
Une association de Product vers Part requiert l'existence d'une clef étrangère et possiblement une colonne d'index pour la table Part. Une balise <one-to-many> indique que c'est une association un vers plusieurs.
<one-to-many
class="NomDeClasse" (1)
not-found="ignore|exception" (2)
entity-name="NomDEntite" (3)
node="nom-d-element"
embed-xml="true|false"
/>| (1) | class (requis) : le nom de la classe associée |
| (2) | not-found (optionnel - par défaut exception) : spécifie comment les identifiants cachés qui référencent des lignes manquantes seront gérés : ignore traitera une ligne manquante comme une association nulle |
| (3) | entity-name (optionnel) : le nom de l'entité de la classe associée, comme une alternative à class. |
Notez que l'élément <one-to-many> n'a pas besoin de déclarer de colonnes. Il n'est pas non plus nécessaire de spécifier le nom de la table nulle part.
Note très importante : si la colonne de la clef d'une association <one-to-many> est déclarée NOT NULL, vous devez déclarer le mapping de <key> avec not-null="true" ou utiliser une association bidirectionnelle avec le mapping de la collection marqué inverse="true". Voir la discussion sur les associations bidirectionnelles plus tard dans ce chapitre.
Cet exemple montre une map d'entités Part par nom (où partName est une propriété persistante de Part). Notez l'utilisation d'un index basé sur une formule.
<map name="parts"
cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map>Hibernate supporte des collections implémentant java.util.SortedMap et java.util.SortedSet. Vous devez spécifier un comparateur dans le fichier de mapping :
<set name="aliases"
table="person_aliases"
sort="natural">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" sort="my.custom.HolidayComparator">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map>Les valeurs permises pour l'attribut sort sont unsorted, natural et le nom d'une classe implémentant java.util.Comparator.
Les collections triées se comportent réellement comme java.util.TreeSet ou java.util.TreeMap.
Si vous voulez que la base de données elle-même ordonne les éléments de la collection, utilisez l'attribut order-by des mappings set, bag ou map. Cette solution est seulement disponible à partir du JDK 1.4 (c'est implémenté en utilisant LinkedHashSet ou LinkedHashMap). Ceci exécute le tri dans la requête SQL, pas en mémoire.
<set name="aliases" table="person_aliases" order-by="lower(name) asc">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date, hol_name">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date type="date"/>
</map>Notez que la valeur de l'attribut order-by est un ordre SQL, pas un ordre HQL !
Les associations peuvent même être triées sur des critères arbitraires à l'exécution en utilisant un filter() de collection.
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
Une association bidirectionnelle permet une navigation à partir de la "fin" de l'association. Deux sortes d'associations bidirectionnelles sont supportées :
ensemble ou sac à une extrémité, une seule valeur à l'autre
ensemble ou sac aux deux extrémités
Vous pouvez spécifier une association plusieurs-vers-plusieurs bidirectionnelle simplement en mappant deux associations plusieurs-vers-plusieurs vers la même table de base de données et en déclarant une extrémité comme inverse (celle de votre choix, mais ça ne peut pas être une collection indexée).
Voici un exemple d'association bidirectionnelle plusieurs-vers-plusieurs ; chaque catégorie peut avoir plusieurs objets et chaque objet peut être dans plusieurs catégories :
<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<id name="id" column="CATEGORY_ID"/>
...
<!-- inverse end -->
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class>Les changements faits uniquement sur l'extréminté inverse de l'association ne sont pas persistés. Ceci signifie qu'Hibernate a deux représentations en mémoire pour chaque association bidirectionnelles, un lien de A vers B et un autre de B vers A. C'est plus facile à comprendre si vous pensez au modèle objet de Java et comment nous créons une relation plusieurs-vers-plusieurs en Java :
category.getItems().add(item); // La catégorie est maintenant "au courant" de la relation item.getCategories().add(category); // L'objet est maintenant "au courant" de la relation session.persist(item); // La relation ne sera pas sauvegardée ! session.persist(category); // La relation sera sauvegardée
La partie non-inverse est utilisée pour sauvegarder la représentation en mémoire dans la base de données.
Vous pouvez définir une association un-vers-plusieurs bidirectionnelle en mappant une association un-vers-plusieurs vers la(es) même(s) colonne(s) de table qu'une association plusieurs-vers-un et en déclarant l'extrémité pluri-valuée inverse="true".
<class name="Parent">
<id name="id" column="parent_id"/>
....
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>Mapper une extrémité d'une association avec inverse="true" n'affecte pas l'opération de cascades, ce sont des concepts orthogonaux !
Une association bidirectionnelle où une extrémité est représentée comme une <list> ou une <map> requiert une considération spéciale. Si il y a une propriété de la classe enfant qui mappe la colonne de l'index, pas de problème, nous pouvons continuer à utiliser inverse="true" sur le mapping de la collection :
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children" inverse="true">
<key column="parent_id"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<property name="name"
not-null="true"/>
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class>Mais, si il n'y a pas de telle prorpriété sur la classe enfant, nous ne pouvons pas penser à l'association comme vraiment bidirectionnelle (il y a des informations disponibles à une extrémité de l'association qui ne sont pas disponibles à l'autre extrémité). Dans ce cas, nous ne pouvons pas mapper la collection inverse="true". À la place, nous pourrions utiliser le mapping suivant :
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children">
<key column="parent_id"
not-null="true"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class>Notez que dans ce mapping, l'extrémité de l'association contenant la collection est responsable des mises à jour de la clef étrangère. À faire : cela entraîne-t-il réellement des expressions updates inutiles ?
Il y a trois approches possibles pour mapper une association ternaire. L'une est d'utiliser une Map avec une association en tant qu'index :
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map><map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map>Une seconde approche est simplement de remodeler l'association comme une classe d'entité. C'est l'approche la plus commune.
Une alternative finale est d'utiliser des éléments composites, dont nous discuterons plus tard.
Si vous embrassez pleinement notre vue que les clefs composées sont une mauvaise chose et que des entités devraient avoir des identifiants artificiels (des clefs subrogées), alors vous pourriez trouver un peu curieux que les associations plusieurs-vers-plusieurs et les collections de valeurs que nous avons montré jusqu'ici mappent toutes des tables avec des clefs composées ! Maintenant, ce point est assez discutable ; une table d'association pure ne semble pas beaucoup bénéficier d'une clef subrogée (bien qu'une collection de valeur composées le pourrait). Néanmoins, Hibernate fournit une foncionnalité qui vous permet de mapper des associations plusieurs-vers-plusieurs et des collections de valeurs vers une table avec une clef subrogée.
L'élément <idbag> vous laisse mapper une List (ou une Collection) avec une sémantique de sac.
<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag>Comme vous pouvez voir, un <idbag> a un généréteur d'id artificiel, comme une classe d'entité ! Une clef subrogée différente est assignée à chaque ligne de la collection. Cependant, Hibernate ne fournit pas de mécanisme pour découvrir la valeur d'une clef subrogée d'une ligne particulière.
Notez que les performances de la mise à jour d'un <idbag> sont bien meilleures qu'un <bag> ordinaire ! Hibernate peut localiser des lignes individuelles efficacement et les mettre à jour ou les effacer individuellement, comme une liste, une map ou un ensemble.
Dans l'implémentation actuelle, la stratégie de la génération de l'identifiant native n'est pas supportée pour les identifiants de collection <idbag>.
Les sections précédentes sont assez confuses. Donc prenons un exemple. Cette classe :
package eg;
import java.util.Set;
public class Parent {
private long id;
private Set children;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private Set getChildren() { return children; }
private void setChildren(Set children) { this.children=children; }
....
....
}a une collection d'instances de Child. Si chaque enfant a au plus un parent, le mapping le plus naturel est une association un-vers-plusieurs :
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>Ceci mappe les définitions de tables suivantes :
create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255), parent_id bigint ) alter table child add constraint childfk0 (parent_id) references parent
Si le parent est requis, utilisez une association un-vers-plusieurs unidirectionnelle :
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping>Notez la contrainte NOT NULL :
create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parentAlternativement, si vous insistez absolument pour que cette association soit unidirectionnelle, vous pouvez déclarer la contrainte NOT NULL sur le mapping <key> :
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>D'un autre côté, si un enfant pouvait avoir plusieurs parent, une association plusieurs-vers-plusieurs est plus appropriée :
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping>Définitions des tables :
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
child_id bigint not null,
primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references childPour plus d'exemples et une revue complète du mapping de la relation parent/enfant, voir see Chapitre 21, Exemple : Père/Fils.
Des mappings d'association plus exotiques sont possibles, nous cataloguerons toutes les possibilités dans le prochain chapitre.
Correctement mapper les associations est souvent la tâche la plus difficile. Dans cette section nous traiterons les cas classiques les uns après les autres. Nous commencerons d'abbord par les mappings unidirectionnels, puis nous aborderons la question des mappings bidirectionnels. Nous illustrerons tous nos exemples avec les classes Person et Address.
Nous utiliserons deux critères pour classer les associations : le premier sera de savoir si l'association est bâti sur une table supplémentaire d'association et le deuxieme sera basé sur la multiplicité de cette association.
Autoriser une clé étrangère nulle est considéré comme un mauvais choix dans la construction d'un modèle de données. Nous supposerons donc que dans tous les exemples qui vont suivre on aura interdit la valeur nulle pour les clés étrangères. Attention, ceci ne veut pas dire que Hibernate ne supporte pas les clés étrangères pouvant prendre des valeurs nulles, les exemples qui suivent continueront de fonctionner si vous décidiez ne plus imposer la contrainte de non-nullité sur les clés étrangères.
Une association plusieurs-à-un (many-to-one) unidirectionnelle est le type que l'on rencontre le plus souvent dans les associations unidirectionnelles.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
une association un-à-un (one-to-one) sur une clé étrangère est presque identique. La seule différence est sur la contrainte d'unicité que l'on impose à cette colonne.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Une association un-à-un (one-to-one) unidirectionnelle sur une clé primaire utilise un générateur d'identifiant particulier. (Remarquez que nous avons inversé le sens de cette association dans cet exemple.)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
Une association un-à-plusieurs (one-to-many) unidirectionnelle sur une clé étrangère est vraiment inhabituelle, et n'est pas vraiment recommandée.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId"
not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )
Nous pensons qu'il est préférable d'utiliser une table de jointure pour ce type d'association.
Une association unidirectionnelle un-à-plusieurs (one-to-many) avec une table de jointure est un bien meilleur choix. Remarquez qu'en spécifiant unique="true", on a changé la multiplicité plusieurs-à-plusieurs (many-to-many) pour un-à-plusieurs (one-to-many).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
Une assiociation plusieurs-à-un (many-to-one) unidirectionnelle sur une table de jointure est très fréquente quand l'association est optionnelle.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
Une association unidirectionnelle un-à-un (one-to-one) sur une table de jointure est extrèmement rare mais envisageable.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Finallement, nous avons l'association unidirectionnelle plusieurs-à-plusieurs (many-to-many).
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
Une association bidirectionnelle plusieurs à un (many-to-one) est le type d'association que l'on rencontre le plus souvent. (c'est la façon standard de créer des relations parents/enfants.)
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
Si vous utilisez une List (ou toute autre collection indexée) vous devez paramétrer la colonne key de la clé étrangère à not null, et laisser Hibernate gérer l'association depuis l'extrémité collection pour maintenir l'index de chaque élément (rendant l'autre extrémité virtuellement inverse en paramétrant update="false" et insert="false"):
<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class>Une association bidirectionnelle un à un (one-to-one) sur une clé étrangère est aussi très fréquente.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person"
property-ref="address"/>
</class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Une association bidirectionnelle un-à-un (one-to-one) sur une clé primaire utilise un générateur particulier d'id.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person"
constrained="true"/>
</class>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
Une association bidirectionnelle un-à-plusieurs (one-to-many) sur une table de jointure . Remarquez que inverse="true" peut s'appliquer sur les deux extrémités de l' association, sur la collection, ou sur la jointure.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses"
table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
inverse="true"
optional="true">
<key column="addressId"/>
<many-to-one name="person"
column="personId"
not-null="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
Une association bidirectionnelle un-à-un (one-to-one) sur une table de jointure est extrèmement rare mais envisageable.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true"
inverse="true">
<key column="addressId"
unique="true"/>
<many-to-one name="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
Finallement nous avons l'association bidirectionnelle plusieurs à plusieurs.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
Des associations encore plus complexes sont extrêmement rares. Hibernate permet de gérer des situations plus complexes en utilisant des parties SQL dans les fichiers de mapping. Par exemple, si une table avec l'historiques des informations d'un compte définit les colonnes accountNumber, effectiveEndDate et effectiveStartDate, mappées de telle sorte:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>alors nous pouvons mapper une association à l'instance courante (celle avec une effectiveEndDate) nulle en utilisant:
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula>'1'</formula>
</many-to-one>Dans un exemple plus complexe, imaginez qu'une association entre Employee et Organization est gérée dans une table Employment pleines de données historiques. Dans ce cas, une association vers l'employeur le plus récent (celui avec la startDate la plus récente) pourrait être mappée comme cela:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join>Vous pouvez être créatif grace à ces possibilités, mais il est généralement plus pratique d'utiliser des requêtes HQL ou criteria dans ce genre de situation.
La notion de composants est réutilisé dans différents contextes, avec différents objectifs, à travers Hibernate.
Le composant est un objet inclu dans un autre qui est sauvegardé comme une valeur, et non pas comme une entité. Le composant fait référence à la notion (au sens objet) de composition (et non pas de composant au sens d'architecture de composants). Par exemple on pourrait modélisé l'objet personne de cette façon:
public class Person {
private java.util.Date birthday;
private Name name;
private String key;
public String getKey() {
return key;
}
private void setKey(String key) {
this.key=key;
}
public java.util.Date getBirthday() {
return birthday;
}
public void setBirthday(java.util.Date birthday) {
this.birthday = birthday;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}public class Name {
char initial;
String first;
String last;
public String getFirst() {
return first;
}
void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
void setLast(String last) {
this.last = last;
}
public char getInitial() {
return initial;
}
void setInitial(char initial) {
this.initial = initial;
}
}Maintenant Name peut-être sauvegardé comme un composant de Person. Remarquer que Name définit des methodes d'accès et de modification pour ses propriétés persistantes, mais il n'a pas besoin des interfaces ou des propriétés d'identification ( par exemple getId() ) qui sont propres aux entités.
Nous serions alors amené à mapper ce composant de cette façon:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class>La table person aurai les colonnes pid, birthday, initial, first and last.
Comme tous les types valeurs, les composants ne supportent pas les références partagés. En d'autres mots, deux instances de person peuvent avoir un même nom, mais ces noms sont indépendants, ils peuvent être identiques si on les compare par valeur mais ils représentent deux objets distincts en mémoire. La notion de nullité pour un composant est ad hoc. Quand il recharge l'objet qui contient le composant, Hibernate supposera que si tous les champs du composants sont nuls alors le composant sera positionné à la valeur null. Ce choix programmatif devrait être satisfaisant dans la plupart des cas.
Les propriétés d'un composant peuvent être de tous les types qu'Hibernate supporte habituellement (collections, many-to-one associations, autres composants, etc). Les composants inclus ne doivent pas être vus comme quelque chose d'exotique. Hibernate a été conçu pour supporter un modèle objet très granulaire.
Le <component> peut inclure dans la liste de ses propriétés une référence au <parent> conteneur.
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name" unique="true">
<parent name="namedPerson"/> <!-- référence arrière à Person -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class>Les collections d'objets dépendants sont supportés (exemple: un tableau de type Name). Déclarer la collection de composants en remplaçant le tag <element> par le tag <composite-element>.
<set name="someNames" table="some_names" lazy="true">
<key column="id"/>
<composite-element class="eg.Name"> <!-- class attribute required -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</composite-element>
</set>Remarque: Si vous définissez un Set d'élément composite, il est très important d'implémenter la méthode equals() et hashCode() correctement.
Les élements composite peuvent aussi contenir des composants mais pas des collections. Si votre élément composite contient aussi des composants, utilisez l'élément <nested-composite-element> . Une collections de composants qui ccontiennent eux-mêmes des composants est un cas très exotique. A ce stade demandez-vous si une association un-à-plusieurs ne serait pas plus approprié. Essayez de re remodeler votre élément composite comme une entité ( Dans ce cas même si le modèle Java est le même la logique de persitence et de relation sont tout de même différentes)
Remarque, le mapping d'éléments composites ne supporte pas la nullité des propriétés lorsqu'on utilise un <set>. Hibernate lorsqu'il supprime un objet utilise chaque colonne pour identifier un objet (on ne peut pas utiliser des clés primaires distinctes dans une table d'éléments composites), ce qui n'est pas possible avec des valeurs nulles. Vous devez donc choisir d'interdire la nullité des propriétés d'un élément composite ou choisir un autre type de collection comme : <list>, <map>, <bag> ou <idbag>.
Un cas particulier d'élément composite est un élément composite qui inclut un élément <many-to-one>. Un mapping comme celui-ci vous permet d'associer les colonnes d'une table d'association plusieurs à plusieurs (many-to-many) à la classse de l'élément composite. L'exemple suivant est une association plusieurs à plusieurs de Order à Item à purchaseDate, price et quantity sont des propriétés de l'association.
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.Purchase">
<property name="purchaseDate"/>
<property name="price"/>
<property name="quantity"/>
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
</composite-element>
</set>
</class>Bien sûr, il ne peut pas y avoir de référence à l'achat (purchase) depuis l'article (item), pour pouvoir naviguer de façon bidirectionnelle dans l'association. N'oubliez pas que les composants sont de type valeurs et n'autorise pas les références partagées.
Même les associations ternaires ou quaternaires sont possibles:
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">
<composite-element class="eg.OrderLine">
<many-to-one name="purchaseDetails class="eg.Purchase"/>
<many-to-one name="item" class="eg.Item"/>
</composite-element>
</set>
</class>Les éléments composites peuvent apparaître dans les requêtes en utilisant la même syntaxe que associations
l'élément <composite-map-key> vous permet d'utiliser une classe de composant comme indice de Map. Assurez-vous d'avoir surdéfini hashCode() et equals() dans la classe du composant.
Vous pouvez utiliser un composant comme identifiant d'une entité. Mais pour cela la classe du composant doit respecter certaines règles.
Elle doit implémenter java.io.Serializable.
Elle doit redéfinir equals() et hashCode(), de façon cohérente avec le fait qu'elle définit une clé composite dans la base de données.
Remarque: avec hibernate3, la seconde règle n'est plus absolument necessaire mais faîtes le quand même.
Vous ne pouvez pas utiliser de IdentifierGenerator pour générer une clé composite, l'application devra définir elle même ses propres identifiants.
Utiliser l'élément <composite-id> (en incluant l'élément <key-property>) à la place de l'habituel déclaration <id>. Par exemple la classe OrderLine qui dépend de la clé primaire (composite) de Order.
<class name="OrderLine">
<composite-id name="id" class="OrderLineId">
<key-property name="lineId"/>
<key-property name="orderId"/>
<key-property name="customerId"/>
</composite-id>
<property name="name"/>
<many-to-one name="order" class="Order"
insert="false" update="false">
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>
....
</class>Maintenant toutes clés étrangères référençant la table OrderLine devra aussi être composite. Vous devez en tenir compte lorsque vous écrivez vos mapping d'association pour les autres classes. Une association à OrderLine devrait être mappé de la façon suivante :
<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-one>(Remarque: l'élément <column> est une alternative à l'attribut column que l'on utilise partout.)
Une association plusieurs-à-plusieurs (many-to-many) à OrderLine utilisera aussi une clé étrangère composite:
<set name="undeliveredOrderLines">
<key column name="warehouseId"/>
<many-to-many class="OrderLine">
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</many-to-many>
</set>La collection des OrderLines dans Order utilisera:
<set name="orderLines" inverse="true">
<key>
<column name="orderId"/>
<column name="customerId"/>
</key>
<one-to-many class="OrderLine"/>
</set>(L'élément <one-to-many>, comme d'habitude, ne déclare pas de colonne.)
Si OrderLine lui-même possède une collection, celle-ci aura aussi une clé composite étrangère.
<class name="OrderLine">
....
....
<list name="deliveryAttempts">
<key> <!-- a collection inherits the composite key type -->
<column name="lineId"/>
<column name="orderId"/>
<column name="customerId"/>
</key>
<list-index column="attemptId" base="1"/>
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set>
</class>Vous pouvez même mapper une propriété de type Map:
<dynamic-component name="userAttributes">
<property name="foo" column="FOO"/>
<property name="bar" column="BAR"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>La sémantique de l'association à un <dynamic-component> est identique à celle que l'on utilise pour les composants. L'avantage de