Hibernate

Une des tâches les plus chronophages dans l'écriture d'un programme est celle qui consiste à coder les accès aux tables des bases de données. C'est fastidieux et sans réelle plus value. Heureusement, des outils, appelés ORM (Object Relational Mapping) se chargent de cet aspect : les données sont alors encapsulées dans des classes, avec une instance par enregistrement.

Dans le monde Java, le plus connu est Hibernate, qui contient, de plus, un mécanisme de cache permettant de stocker localement des enregistrements des tables. Ce mécanisme de cache est surtout intéressant en développement web, mais il présente aussi des inconvénients (moins bonne maîtrise de ce qui est chargé, possible incohérence entre la base de données et le cache d'Hibernate dans certaines situations, sollicitation plus importante de la base de données dans certains cas de figure, etc.). Néanmoins, les avantages sont tels, notamment en vitesse d'écriture, qu'il serait dommage de s'en priver.

Hibernate travaille avec une description des tables, réalisée au format XML. Heureusement, un outil permet de générer automatiquement ces fichiers.

Pour plus d'informations concernant l'utilisation d'Hibernate, voici quelques documents que j'ai été amené à consulter :

Installer Hibernate

Dans Maven (fichier pom.xml), déclarez la dépendance suivante :

<dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.5.Final</version>
</dependency>

Configurer Hibernate

Créez le fichier src/main/resources/hibernate.cfg.xml, avec les quelques lignes suivantes :

<?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 name="">
  <property name="hibernate.bytecode.use_reflection_optimizer">false</property>
  <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
  <property name="hibernate.connection.url">jdbc:hsqldb:mabase</property>
  <property name="hibernate.connection.username">sa</property>
  <property name="hibernate.default_schema">PUBLIC</property>
  <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
  <property name="hibernate.search.autoregister_listeners">false</property>
  <property name="hibernate.max_fetch_depth"/>
  <property name="hibernate.show_sql">true</property>
 </session-factory>
</hibernate-configuration>

Il s'agit d'un exemple avec une base de données hsqldb ; vous devrez adapter la configuration à votre contexte.

Générer automatiquement les fichiers xml de mapping

Voici un exemple qui fonctionne bien. Basculez dans la perspective Hibernate, puis :

  1. Click on [File -> New -> Other -> Hibernate -> Hibernate Configuration File] and create a cfg file. The following properties should be specified : jdbc url , username, password, DB schema, driver class and dialect.
  2. Click on [File -> New -> Other -> Hibernate -> Hibernate Console Configuration ] and create a new console configuration. Add the jar file that contains your DB driver in the classpath section at the bottom.
  3. Enter the name of the console configuration. Click Browse button against the Configuration file and select the cfg.xml file created in step 1.
  4. Click on [File -> New -> Other -> Hibernate -> Hibernate Reverse Engineering File(reveng.xml) ] and select the location of the file.
  5. Select the cfg.xml file created in step 1 as the Console Configuration. Click on include button and specift the schema and table name(s) to reverse engineer. Multiple table names can be specifed using sql wild char (eg: TBL_%). Click Finish.
  6. Click on the Hibernate icon in the tool bar and select the [Hibernate Code Generation ..] option.
  7. Right click on the [Hibernate Code Generation]tree node and select New.
  8. Give the name of the configuration and select the console configuration created in step 1 and reveng.xml created in step 4. Also select the Output Directory,
  9. Go to the Exporters tab and check Generate domain code(.java) and Generate mappings (hbm.xml). Click run.
  10. The hibernate mapping xml files and Java classes will be created in the output directory specified in step 8.

Source : http://www.yuvalararat.com/2008/03/hibernate-auto-generate-hbm-and-java/

Utiliser Hibernate dans votre application

Créez la classe suivante :

@SuppressWarnings("deprecation")
public class HibernateUtil {
    public static Session session;
    static Configuration cfg;
    private static final SessionFactory sessionFactory;
    static {
        try {
            boolean debug = false;
            if (Parametre.mode.equals("debug"))
                debug = true;
            if (debug)
                System.out
                        .println("debut de la creation de la session Hibernate");
            cfg = new Configuration()
                    .configure("hibernate.cfg.xml")
                    .setProperty("hibernate.connection.url",
                            Parametre.getStringValue("database", "url"))
                    .setProperty("hibernate.connection.driver_class",
                            Parametre.getStringValue("database", "DriverClass"))
                    .setProperty("hibernate.connection.username",
                            Parametre.getStringValue("database", "Username"))
                    .setProperty("hibernate.connection.password",
                            Parametre.getStringValue("database", "Password"))
                    .setProperty("hibernate.show_sql", String.valueOf(debug));
            if (debug)
                System.out.println("configuration Hibernate chargée");
            sessionFactory = cfg.buildSessionFactory();
            if (debug)
                System.out.println("sessionFactory hibernate créée");
            session = getSessionFactory().openSession();
            session.beginTransaction();
            if (debug)
                System.out.println("Session hibernate ouverte");
        } catch (Throwable ex) {
            String nl = System.getProperty("line.separator");
            String message = "La base de données n'est pas reconnue. Vérifiez qu'elle soit bien en cours de fonctionnement."
                    + nl
                    + "URL de connexion : "
                    + Parametre.getStringValue("database", "url");
            JOptionPane.showMessageDialog(Controleur.fenetre.getFenetre(),
                    message, "Erreur de connexion à la base de données",
                    JOptionPane.ERROR_MESSAGE);
            Controleur.sessionError = true;
            System.exit(1);
            throw new ExceptionInInitializerError(ex);
        }
    }
    
    public Session getSession() {
        session = HibernateUtil.getSessionFactory().getCurrentSession();
        if (!session.isOpen()) {
            session = HibernateUtil.getSessionFactory().openSession();
            session.beginTransaction();
        }
        if (!session.getTransaction().isActive())
            session.beginTransaction();
        return session;
    }
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

Dans cet exemple, vous pouvez vous passer des lignes setProperty, qui ont été rajoutées pour gérer dynamiquement la connexion à la base de données. La classe contient aussi l'affichage de messages en mode "debug", mode debug qui est initialisé dans un fichier de paramètres.

Un exemple d'utilisation

Il s'agit de manipuler une table appelée Trait.

public abstract class Modele {
    public Session getSession() {
        return HibernateUtil.getSession();
    }
}
public class Mtrait extends Modele {
    Trait trait;
public Mtrait () {}
public List<Trait> getListe() {
    return getSession().createQuery("select trait from Trait trait ").list();
    }
   /**
     * Met a jour le trait considere
     * @param trait
     */
    public void update (Trait trait) {
        getSession().saveOrUpdate(trait);
        getSession().getTransaction().commit();
    }
    /**
     * Supprime le trait considere
     * @param trait
     */
    public void delete(Trait trait) {
        getSession().delete(trait);
        getSession().getTransaction().commit();
    }
}

Gérer les opérations en cascade

Dans l'exemple précédent, nous avons simplement mis à jour la table trait, ou plutôt l'objet Trait. Si celui-ci contient plusieurs classes imbriquées, c'est à dire des tables en cascade permettant de décrire les différents composants de la table principale, et si vous souhaitez que toutes vos tables imbriquées soient mis à jour automatiquement quand vous enregistrez l'objet Trait, vous devrez probablement modifier manuellement le ou les fichiers hbm.xml impliqués.

Il faut modifier tous les fichiers porteurs de relations, si vous avez plusieurs niveaux de tables.

Attention : après avoir modifié manuellement vos fichiers hbm.xml, ne relancez plus de reverse-ingeneering : vous perdriez toutes vos modifications !

Les exemples sont issus des modifications affichées dans un lecteur GIT.

Cas d'une relation 1-n

Voici un exemple portant sur une table fille, appelée IINDIVIDUS_EEL :

- <set name="individuEels" table="INDIVIDU_EEL" inverse="true" lazy="true" fetch="select">
+ <set name="individuEels" table="INDIVIDU_EEL" inverse="true" lazy="true" fetch="select" cascade="all,delete-orphan">

Nous avons rajouté l'attribut cascade="all,delete-orphan", qui va permettre d'assurer la mise à jour automatique.

Cas d'une relation 1-1

- <one-to-one name="traitGeom" class="database.TraitGeom"></one-to-one>
+ <one-to-one name="traitGeom" class="database.TraitGeom" cascade="all,delete-orphan"></one-to-one>