Hibernate.orgCommunity Documentation

Chapitre 1. Tutoriel

1.1. Section 1 - Première application Hibernate
1.1.1. Configuration
1.1.2. La première classe
1.1.3. Le fichier de mappage
1.1.4. Configuration d'Hibernate
1.1.5. Construction avec Maven
1.1.6. Démarrage et aides
1.1.7. Charger et stocker des objets
1.2. Section 2 - Mapper des associations
1.2.1. Mapper la classe Person
1.2.2. Une association unidirectionnelle basée sur Set
1.2.3. Travailler avec l'association
1.2.4. Collection de valeurs
1.2.5. Associations bidirectionnelles
1.2.6. Travailler avec des liens bidirectionnels
1.3. Section 3 - L'application web EventManager
1.3.1. Écrire la servlet de base
1.3.2. Traiter et interpréter
1.3.3. Déployer et tester
1.4. Résumé

A l'intention des nouveaux utilisateurs, ce chapitre fournit une introduction étape par étape à Hibernate, en commençant par une application simple, avec une base de données en-mémoire. Le tutoriel est basé sur une tutoriel antérieur qui avait été développé par Michael Gloegl. Tout le code est contenu dans tutorials/web qui se trouve dans le répertoire source du projet.

Important

Ce tutoriel assume que l'utilisateur est déjà familier avec Java et SQL à la fois. Si vous ne possédez qu'une connaissance de Java et d'SQL limitée, il est conseillé de commencer par vous familiariser avec ces technologies avant d'aborder Hibernate.

Note

La distribution contient un autre exemple d'application qui se trouve dans le répertoire source du projet tutorial/eg.

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.

Note

Malgré que vous puissiez utiliser tout base de données qui vous convienne, on choisira HSQLDB (une base de données Java, en-mémoire) pour éviter de décrire l'installation et la configuration de n'importe quel serveur de base de données particulière.

La première chose que nous devons faire est de configurer l'environnement de développement. Nous utiliserons la "standard layout" préconisée par de nombreux outils de génération tels que Maven. Maven, en particulier, a une bonne ressource décrivant cette layout. Comme ce tutoriel va devenir une application web, nous allons créer et utiliser les répertoires src/main/java., src/main/ressources et src/main/webapp.

Nous utiliserons Maven dans ce tutoriel. Nous profiterons de ses capacités de gestion de dépendances transitives, ainsi que de la capacité des nombreux IDE à installer automatiquement un projet sur la base du descripteur Maven.


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion
>4.0.0</modelVersion>

    <groupId
>org.hibernate.tutorials</groupId>
    <artifactId
>hibernate-tutorial</artifactId>
    <version
>1.0.0-SNAPSHOT</version>
    <name
>First Hibernate Tutorial</name>

    <build>
         <!-- we dont want the version to be part of the generated war file name -->
         <finalName
>${artifactId}</finalName>
    </build>

    <dependencies>
        <dependency>
            <groupId
>org.hibernate</groupId>
            <artifactId
>hibernate-core</artifactId>
        </dependency>

        <!-- Because this is a web app, we also have a dependency on the servlet api. -->
        <dependency>
            <groupId
>javax.servlet</groupId>
            <artifactId
>servlet-api</artifactId>
        </dependency>

        <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
        <dependency>
            <groupId
>org.slf4j</groupId>
            <artifactId
>slf4j-simple</artifactId>
        </dependency>

        <!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
        <dependency>
            <groupId
>javassist</groupId>
            <artifactId
>javassist</artifactId>
        </dependency>
    </dependencies>

</project
>

Astuce

It is not a requirement to use Maven. If you wish to use something else to build this tutorial (such as Ant), the layout will remain the same. The only change is that you will need to manually account for all the needed dependencies. If you use something like Ivy providing transitive dependency management you would still use the dependencies mentioned below. Otherwise, you'd need to grab all dependencies, both explicit and transitive, and add them to the project's classpath. If working from the Hibernate distribution bundle, this would mean hibernate3.jar, all artifacts in the lib/required directory and all files from either the lib/bytecode/cglib or lib/bytecode/javassist directory; additionally you will need both the servlet-api jar and one of the slf4j logging backends.

Sauvegardez ce fichier sous la forme pom.xml dans le répertoire root du projet.

Ensuite, nous créons une classe qui représente l'évènement que nous voulons stocker dans notre base de données. Il s'agit d'une simple classe JavaBean avec quelques propriétés :

package org.hibernate.tutorial.domain;


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 constaterez 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.

La propriété id contient la valeur d'un identifiant unique pour un événement particulier. Toutes les classes d'entités persistantes (il y a également des classes dépendantes de moindre importance) auront besoin d'une telle propriété identifiante si nous voulons utiliser l'ensemble complet des fonctionnalités de Hibernate. En fait, la plupart des applications (surtout les applications web) ont besoin de distinguer des objets par des identifiants, par conséquent considérez cela comme une fonctionnalité et non 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é. Remarquez que 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. À vous de choisir, et vous pouvez également 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 proxies à l'exécution et une récupération efficace des données sans instrumentation du bytecode.

Sauvegardez ce fichier dans le répertoire src/main/java/org/hibernate/tutorial/domain.

Hibernate a besoin de savoir comment charger et stocker des objets d'une classe persistante. C'est là qu'intervient le fichier de mappage Hibernate. Le fichier de mappage indique à Hibernate à quelle table accéder dans la base de données, et les colonnes de cette table à utiliser.

La structure basique de ce fichier de mappage ressemble à ce qui suit :


<?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="org.hibernate.tutorial.domain">
[...]
</hibernate-mapping
>

Notez que la DTD Hibernate est très sophistiquée. Vous pouvez l'utiliser pour l'auto-finalisation des éléments et des attributs de mappage XML dans votre éditeur ou votre IDE. Ouvrez également 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 chemin de classe de l'application. Le fichier DTD est inclus dans hibernate-core.jar ainsi que dans le répertoire src de la distribution Hibernate).

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 mappage vers une table de la base de données SQL :


<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">

    </class>

</hibernate-mapping
>

Plus loin, nous indiquons à Hibernate comment persister et charger un objet de la classe Event dans la table EVENTS, chaque instance étant représentée par une ligne dans cette table. Maintenant nous continuons avec le mappage de la propriété de l'identifiant unique vers la clef primaire des tables. 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 Hibernate pour la colonne de la clé primaire subrogée :


<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
    </class>

</hibernate-mapping
>

L'élément ID est la déclaration de l'identifiant de propriété. L'attribut de mappage name="id" déclare le nom de la propriété JavaBean et indique à Hibernate d'utiliser les méthodes getId() et setId() pour accéder à la propriété. L'attribut de colonne indique à Hibernate quelle colonne de la table EVENTS contient la valeur de clé primaire.

L'élément imbriqué Générateur spécifie la stratégie de génération d'identifiant (c'est à dire comment les valeurs d'identifiant sont-elles générées?). Dans ce cas nous avons choisi native, qui offre un niveau de la portabilité selon le dialecte de base de données configurée. Mise en veille prolongée prend en charge la base de données générée, unique au monde, ainsi que l'application affectée, les identifiants. Génération de valeur d'identifiant est aussi l'un des nombreux points d'extension d'Hibernate et vous pouvez plug-in votre propre stratégie.

Enfin, nous incluons des déclarations pour les propriétés persistantes de la classe dans le fichier de mappage. Par défaut, aucune propriété de la classe n'est considérée comme persistante :



<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </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 quelles méthodes getters/setters utiliser. Par conséquent dans ce cas, Hibernate cherchera getDate()/setDate(), de même que getTitle()/setTitle().

Note

Pourquoi le mappage de la propriété date inclut-il l'attribut column, mais non le title ? Sans l'attribut column, Hibernate utilise par défaut le nom de la propriété comme nom de colonne. Cela fonctionne bien pour title. Cependant, date est un mot clé réservé dans la plupart des bases de données, donc nous utilisons un nom différent pour le mappage.

Il est intéressant de noter que le mappage de title manque également d'un attribut type. Les types que nous déclarons et utilisons dans les fichiers de mappage 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 types de mappage 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 mappage lui-même si l'attribut type n'est pas présent dans le mappage. 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 convertisseur timestamp.

Astuce

Hibernate rend cette détermination de type de mappage en utilisant la réflection au moment du traitement des fichiers de mappage. Cela prend du temps et consomme des ressources, donc, si la performance de démarrage est importante, vous devriez considérer définir explicitement quel type utiliser.

Sauvegardez ce fichier de mappage ainsi src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml.

A ce niveau là, vous devriez avoir la classe persistante et son fichier de mappage en place. Il est temps maintenant de configurer Hibernate. Tout d'abord, il nous faut configurer HSQLDB pour qu'il puisse exécuter en "server mode"

Vous utiliserez le lugin exec Maven pour lancer le serveur HSQLDB en exécutant : mvn exec:java -Dexec.mainClass="org.hsqldb.Server" -Dexec.args="-database.0 file:target/data/tutorial". Vous observez qu'elle démarre et ouvre un 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 (choisissez CTRL + C dans la fenêtre), effacez tous les fichiers dans le répertoire target/data et redémarrez HSQL DB.

Hibernate se connectera à la base de données pour le compte de votre application, donc il devra savoir comment obtenir des connexions. Pour ce tutoriel, nous devrons utliser un pool de connexions autonomes (et non pas javax.sql.DataSource). Hibernate bénéficie du support de deux pools de connexions JDBC open source de tierce partie : c3p0 and proxool. Cependant, nous utiliserons le pool de connexions intégré Hibernate pour ce tutoriel.

Attention

The built-in Hibernate connection pool is in no way intended for production use. It lacks several features found on any decent connection pool.

Pour la configuration de 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"
>update</property>

        <mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration
>

Note

Vous pourrez remarquer que cette configuration XML utilise une DTD différente.

Nous configurons une SessionFactory de 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.

Astuce

In most cases, Hibernate is able to properly determine which dialect to use. See Section 26.3, « Résolution de dialecte » for more information.

La gestion automatique des sessions d'Hibernate pour les contextes de persistance est bien pratique, comme vous pourrez le constater. 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 également ê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 mappage pour les classes persistantes.

Sauvegarder ce fichier en tant que hibernate.cfg.xml dans le répertoire src/main/resources.

Nous allons maintenant construire le tutoriel avec Maven. Vous aurez besoin d'installer Maven pour cela. Il est disponible dans la page Maven download page. Maven pourra lire le fichier /pom.xml que nous avons créé plus tôt et saura comment effectuer quelques tâches du projet de base. Tout d'abord, exécutons compile pour s'assurer que nous pouvons tout compiler jusqu'à maintenant :

[hibernateTutorial]$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building First Hibernate Tutorial
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009
[INFO] Final Memory: 5M/547M
[INFO] ------------------------------------------------------------------------

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 dans un lieu facile d'accès dans le code de l'application. Une SessionFactory peut ouvrir de nouvelles Sessions. Une Session représente une unité de travail simplement "threadée". La org.hibernate.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 org.hibernate.SessionFactory plus facile.

package org.hibernate.tutorial.util;


import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
    private static final SessionFactory sessionFactory = buildSessionFactory();
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return 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 SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

Sauvegardez ce code en tant que src/main/java/org/hibernate/tutorial/util/HibernateUtil.java

Cette classe ne produit pas seulement la org.hibernate.SessionFactory globale dans un initialiseur statique. Elle masque le fait qu'elle exploite un singleton statique. Nous aurions pu aussi bien vérouiller la référence org.hibernate.SessionFactory à partir de JNDI dans un serveur d'application ou dans n'importe quelle location en fait. Elle pourrait aussi obtenir la SessionFactory depuis JNDI dans un serveur d'applications.

Si vous nommez org.hibernate.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 expliquées plus loin.

Nous avons finalement besoin de configurer le système de journalisation - 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 de Hibernate (il est dans le répertoire etc/) dans votre répertoire src, puis faîtes 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 de Hibernate est affiché sur la sortie standard.

L'infrastructure de ce toturiel est complète - et nous sommes prêts à effectuer un travail réel avec Hibernate.

We are now ready to start doing some real work with Hibernate. Let's start by writing an EventManager class with a main() method:

package org.hibernate.tutorial;


import org.hibernate.Session;
import java.util.*;
import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.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 dans createAndStoreEvent(), et nous le remettons à Hibernate, qui s'occupe maintenant du SQL et exécute les INSERT s dans la base de données.

A org.hibernate.Session is designed to represent a single unit of work (a single atomic piece of work to be performed). For now we will keep things simple and assume a one-to-one granularity between a Hibernate org.hibernate.Session and a database transaction. To shield our code from the actual underlying transaction system we use the Hibernate org.hibernate.Transaction API. In this particular case we are using JDBC-based transactional semantics, but it could also run with JTA.

Quelle est la fonction de 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, l'unité de travail courante est liée au thread Java courant qui exécute notre application.

Une org.hibernate.Session commence lorsque le thread courant commence à appeler getCurrentSession(). Ensuite, elle est attachée par Hibernate au thread courant. Lorsque la transaction s'achève, par commit ou par rollback, Hibernate détache automatiquement la Session du thread et la ferme pour vous. Si vous invoquez getCurrentSession() une nouvelle fois, vous obtenez une nouvelle Session et pouvez entamer une nouvelle unité de travail.

A propos de la portée de l'unité de travail, la session org.hibernate.Session Hibernate devrait-elle être utilisée pour exécuter une ou plusieurs opérations en base de données ? L'exemple ci-dessus utilise une Session pour une opération. C'est une pure coïncidence, l'exemple n'est pas assez complexe pour montrer d'autres approches. La portée d'une Session Hibernate est flexible mais vous ne devriez jamais concevoir votre application de manière à utiliser une nouvelle Session Hibernate pour chaque opération en base de données. Donc même si vous le voyez quelquefois dans les exemples suivants, considérez une session par opération comme un anti-modèle. Une véritable application (web) est affichée plus loin dans ce tutoriel.

See Chapitre 12, Transactions et Accès concurrents for more information about transaction handling and demarcation. The previous example also skipped any error handling and rollback.

Pour pouvoir exécuter ceci, nous utiliserons le plugin exec Maven pour appeler notre classe avec la configuration de classpath qui convient : mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"

Note

Vous aurez sans doute besoin d'effectuer mvn compile pour commencer.

Vous devriez constater qu'Hibernate démarre et selon 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ée par Hibernate.

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;
    }

Here, we are using a Hibernate Query Language (HQL) query to load all existing Event objects from the database. Hibernate will generate the appropriate SQL, send it to the database and populate Event objects with the data. You can create more complex queries with HQL. See Chapitre 15, HQL : langage d'interrogation d'Hibernate for more information.

Nous pouvons maintenant appeler notre nouvelle fonctionnalité, en utilisant à nouveau le plugin exec Maven : mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list"

Pour l'instant, nous nous sommes contentés de mapper une classe d'une entité persistante vers une table. Profitons-en pour ajouter quelques associations de classe. D'abord nous ajouterons des gens à notre application, et stockerons une liste d'événements auxquels ils participent.

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 pour ces exemples :

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 bien, nous pourrions créer une autre collection sur Event, si nous voulons être capable de la parcourir de manière bidirectionnelle. 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 évènement particulier. Vous êtes libre de choisir la conception, ce qui est certain, c'est que la cardinalité de l'association : "plusieurs" valués des deux côtés, est appelée plusieurs-à-plusieurs. Par conséquent nous utilisons un mappage Hibernate plusieurs-à-plusieurs :


<class name="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 mappage de collection, un set étant le plus commun. Pour une association plusieurs-à-plusieurs (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 avec 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 mappage 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 constater, 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 (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 correspondants. 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.

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 nouvelle unité de travail, ainsi toutes les modifications que vous avez faites pendant qu'il était détaché peuvent être sauvegardées dans la base de données, cela inclut toute modification effectuées sur une collection de cet objet entité.

Cela n'a pas grand intérêt dans notre situation, mais c'est un concept important qu'il vous faut concevoir dans votre application. Pour le moment, complétez cet exercice en ajoutant une nouvelle action à la méthode principale de l'EventManager et invoquez-la depuis la ligne de commande. Si vous avez besoin des identifiants d'un client et d'un évènement - la méthode save() vous les retourne (vous devrez peut-être modifier certaines méthodes précédentes pour retourner ces identifiants) :

        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);
        }

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 n'existent pas seulement 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 dans Java.

Ajoutons un ensemble d'adresses email à l'entité Person qui sera représenté en tant que java.util.Set d'instance java.lang.String :

    private Set emailAddresses = new HashSet();


    public Set getEmailAddresses() {
        return emailAddresses;
    }
    public void setEmailAddresses(Set emailAddresses) {
        this.emailAddresses = emailAddresses;
    }

Le mappage 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 mappage vu plus tôt est la partie element, qui indique à Hibernate que la collection ne contient pas de référence 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 mappage 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 clé é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.

Considérons 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 clé primaire de la table de la collection est en fait une clé composée, utilisant les 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 dans Java.

Vous pouvez maintenant tester et ajouter des éléments à cette collection, juste comme nous l'avons fait auparavant en liant des personnes et des événements. C'est le même code dans Java.

    private void addEmailToPerson(Long personId, String emailAddress) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Person aPerson = (Person) session.load(Person.class, personId);
        // adding to the emailAddress collection might trigger a lazy load of the collection
        aPerson.getEmailAddresses().add(emailAddress);
        session.getTransaction().commit();
    }

Cette fois-ci, nous n'avons pas utilisé de requête de chargement fetch pour initialiser la collection. Traquez les logs SQL et tentez d'optimiser ce cas avec un chargement agressif.

Ensuite nous allons mapper une association bidirectionnelle - faire fonctionner l'association entre une personne et un événement à partir des deux côtés dans Java. Bien sûr, le schéma de la base de données ne change pas, nous avons toujours une pluralité plusieurs-à-plusieurs.

D'abord, ajoutez 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 mappez 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 mappages de sets normaux dans les deux documents de mappage. Notez que les noms de colonne dans key et many-to-many sont inversés dans les 2 documents de mappage. L'ajout le plus important ici est l'attribut inverse="true" dans l'élément set du mappage de la collection des Events.

Cela signifie que Hibernate devrait prendre l'autre côté - la classe Person - quand il a besoin de trouver 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é, en ajoutant une référence de Person à la collection dans 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 aux classes et aux sous-classes du même paquetage d'accéder aux méthodes, mais empêche quiconque 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 mappage inverse ? Pour vous, et pour Java, un lien bidirectionnel consiste simplement à 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é de l'association, indique à Hibernate de l'ignorer, pour le considérer comme un miroir de l'autre côté. Cela suffit à Hibernate pour gérer tous les 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-à-plusieurs ce doit être le côté plusieurs, dans une association plusieurs-à-plusieurs, vous pouvez choisir n'importe quel côté, il n'y pas de différence.

Une application web Hibernate utilise la Session et Transaction comme une application autonome. Cependant, quelques modèles communs sont utiles. Nous allons coder une EventManagerServlet. Ce servlet peut lister tous les évènements stockés dans la base de données, et fournir une formulaire HTML pour saisir de nouveaux évènements.

Tout d'abord, nous devons créer notre servlet de base. La servlet n'accepte que les requêtes HTTP GET, la méthode à implémenter est donc doGet() :

package org.hibernate.tutorial.web;


// Imports
public class EventManagerServlet extends HttpServlet {
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" );
        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();
            if ( ServletException.class.isInstance( ex ) ) {
                throw ( ServletException ) ex;
            }
            else {
                throw new ServletException( ex );
            }
        }
    }
}

Servir la servlet en tant que src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java

Le modèle appliqué ici est appelé session-per-request. Lorsqu'une requête appelle la servlet, une nouvelle Session Hibernate est ouverte à la première invocation de 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 transaction, peu importe que les données soient lues ou écrites (nous n'utilisons pas le mode auto-commit dans les applications).

N'utilisez pas une nouvelle Session Hibernate pour chaque opération en base de données. Utilisez une Session Hibernate qui porte sur l'ensemble de la requête. Utlisez getCurrentSession(), ainsi elle est automatiquement attachée au thread Java courant.

Ensuite, les actions possibles de la requêtes sont exécutées et la réponse HTML est rendue. Nous y reviendrons ultérieurement.

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 lancée et la transaction avec la base de données subit un rollback. Cela complète le modèle session-per-request. Au lieu d'avoir un code de délimitant les transactions au sein de chaque servlet, vous pouvez écrire un filtre de servlet. Voir le site Hibernate et le Wiki pour plus d'informations sur ce modèle, appelé Open Session in View - vous en aurez besoin dès que vous utiliserez des JSP et non 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, dateFormatter);
       // Write HTML footer
       out.println("</body
></html
>");
       out.flush();
       out.close();

Ce style de code avec une mixture de Java et d'HTML ne serait pas extensible dans une application plus complexe - gardez à l'esprit que nous ne faisons qu'illustrer les concepts basiques de Hibernate dans ce didacticiel. Ce code affiche une entê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 et 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, SimpleDateFormat dateFormatter) {


        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
>");
            Iterator it = result.iterator();
            while (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
>");
        }
    }

Enfin, 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 complétée. Une requête à la servlet sera exécutée par une seule Session et Transaction. Comme dans l'application autonome vue auparavant, 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 selon le moyen que vous aurez choisi. 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. Consultez le wiki Hibernate pour plus d'exemples.

Pour déployer cette application en vue de procéder à des tests, nous devons créer un WAR (Web ARchive). Tout d'abord, nous devons définir le descripteur WAR en tant que src/main/webapp/WEB-INF/web.xml


<?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
>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name
>Event Manager</servlet-name>
        <url-pattern
>/eventmanager</url-pattern>
    </servlet-mapping>
</web-app
>

Pour construire et déployer, appelez ant war dans votre projet et copiez le fichier hibernate-tutorial.war dans le répertoire webapp de Tomcat.

Note

If you do not have Tomcat installed, download it from http://tomcat.apache.org/ and follow the installation instructions. Our application requires no changes to the standard Tomcat configuration.

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. Vous trouverez des tutoriels supplémentaires dans le site Hibernate website.