Chapitre 15. Guide des outils

Des outils en ligne de commande permettre de gérer de cycles de développement complet de projets utilisant Hibernate. Ces outils font partie du projet Hibernate lui-même. Ils peuvent être utilisés conjointement avec d'autres outils qui supportent nativement Hibernate : XDoclet, Middlegen and AndroMDA.

La distribution principale d'Hibernate est livrée avec l'outil le plus important (il peut même être utilisé "à l'intérieur" d'Hibernate, à la volée) :

D'autres outils directement fournis par le projet Hibernate sont distribués dans un package séparé, Hibernate Extensions. Ce package inclus des outils pour les tâches suivantes :

Il existe un autre outil distribué avec les extensions Hibernate : ddl2hbm. Il est considéré comme obsolète et ne sera plus maintenu, Middlegen faisant un meilleur travail pour cette tâche.

D'autres outils tiers fournissent un support Hibernate :

Ces outils tiers ne sont pas documentés dans ce guide. Référez-vous au site Hibernate pour des informations à jour (une photo du site est inclus dans la distribution principale d'Hibernate).

15.1. Génération de Schéma

Le DDL peut être généré à partir de vos fichiers de mapping par une ligne de commande. Un fichier .bat est localisé dans le répertoire hibernate-x.x.x/bin de la distribution principale.

Le schéma généré inclut les contraintes d'intégrité du référentiel (clés primaires et étrangères) pour les tables d'entités et de collections. Les tables et les séquences sont aussi créées pour les générateurs d'identifiants mappés.

Vous devez spécifier un Dialecte SQL via la propriété hibernate.dialect lorsque vous utilisez cet outil.

15.1.1. Personnaliser le schéma

Plusieurs éléments du mapping hibernate définissent un attribut optionnel nommé length. Vous pouvez paramétrer la longueur d'une colonne avec cet attribut (ou, pour les types de données numeric/decimal, la précision).

Certains éléments acceptent aussi un attribut not-null (utilisé pour générer les contraintes de colonnes NOT NULL) et un attribut unique (pour générer une contrainte de colonne UNIQUE).

Quelques éléments acceptent un attribut index pour spécifier le nom d'un index pour cette colonne. Un attribut unique-key peut être utilisé pour grouper des colonnes dans une seule contrainte de clé. Actuellement, la valeur spécifiée pour l'attribut unique-key n'est pas utilisée pour nommer la contrainte, mais uniquement pour grouper les colonnes dans le fichier de mapping.

Exemples :

<property name="foo" type="string" length="64" not-null="true"/>

<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>

<element column="serial_number" type="long" not-null="true" unique="true"/>

Sinon, ces éléments acceptent aussi un éléments fils <column>. Ceci est particulièrement utile pour des types multi-colonnes :

<property name="foo" type="string">
    <column name="foo" length="64" not-null="true" sql-type="text"/>
</property>

<property name="bar" type="my.customtypes.MultiColumnType"/>
    <column name="fee" not-null="true" index="bar_idx"/>
    <column name="fi" not-null="true" index="bar_idx"/>
    <column name="fo" not-null="true" index="bar_idx"/>
</property>

L'attribut sql-type permet à l'utilisateur de surcharger le mapping par défaut d'un type Hibernate vers un type de données SQL.

L'attribut check permet de spécifier une contrainte de vérification.

<property name="foo" type="integer">
    <column name="foo" check="foo > 10"/>
</property>

<class name="Foo" table="foos" check="bar < 100.0">
    ...
    <property name="bar" type="float"/>
</class>

Tableau 15.1. Résumé

AttributValeurInterprétation
lengthnumériqueprécision d'une colonne (longueur ou décimal)
not-nulltrue|falsespécifie que la colonne doit être non-nulle
uniquetrue|falsespécifie que la colonne doit avoir une contrainte d'unicité
indexindex_namespécifie le nom d'un index (multi-colonnes)
unique-keyunique_key_namespécifie le nom d'une contrainte d'unicité multi-colonnes
foreign-keyforeign_key_name spécifie le nom d'une contrainte de clé étrangère générée pour une association, utilisez-la avec les éléments de mapping <one-to-one>, <many-to-one>, <key>, et <many-to-many> Notez que les extrêmités inverse="true" se seront pas prises en compte par SchemaExport.
sql-typecolumn_type surcharge le type par défaut (attribut de l'élément <column> uniquement)
checkSQL expression créé une contrainte de vérification sur la table ou la colonne

15.1.2. Exécuter l'outil

L'outil SchemaExport génère un script DDL vers la sortie standard et/ou exécute les ordres DDL.

java -cp classpath_hibernate net.sf.hibernate.tool.hbm2ddl.SchemaExport options fichiers_de_mapping

Tableau 15.2. SchemaExport Options de la ligne de commande

OptionDescription
--quietne pas écrire le script vers la sortie standard
--dropsupprime seuleument les tables
--textne pas exécuter sur la base de données
--output=my_schema.ddlécrit le script ddl vers un fichier
--config=hibernate.cfg.xmllit la configuration Hibernate à partir d'un fichier XML
--properties=hibernate.propertieslit les propriétés de la base de données à partir d'un fichier
--formatformatte proprement le SQL généré dans le script
--delimiter=xparamètre un délimiteur de fin de ligne pour le script

Vous pouvez même intégrer SchemaExport dans votre application :

Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);

15.1.3. Propriétés

Les propriétés de la base de données peuvent être spécifiées

  • comme propriétés système avec -D<property>

  • dans hibernate.properties

  • dans un fichier de propriétés déclaré avec --properties

Les propriétés nécessaires sont :

Tableau 15.3. Propriétés de connexion nécessaires à SchemaExport

Nom de la propriétéDescription
hibernate.connection.driver_classclasse du driver JDBC
hibernate.connection.urlURL JDBC
hibernate.connection.usernameutilisateur de la base de données
hibernate.connection.passwordmot de passe de l'utilisateur
hibernate.dialectdialecte

15.1.4. Utiliser Ant

Vous pouvez appeler SchemaExport depuis votre script de construction Ant :

<target name="schemaexport">
    <taskdef name="schemaexport"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
        classpathref="class.path"/>
    
    <schemaexport
        properties="hibernate.properties"
        quiet="no"
        text="no"
        drop="no"
        delimiter=";"
        output="schema-export.sql">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaexport>
</target>

Si vous ne spécifiez ni properties, ni fichier dans config, la tâche SchemaExportTask tentera d'utiliser les propriétés du projet Ant. Autrement dit, if vous ne voulez pas ou n'avez pas besoin d'un fichier externe de propriétés ou de configuration, vous pouvez mettre les propriétés de configuration hibernate.* dans votre build.xml ou votre build.properties.

15.1.5. Mises à jour incrémentales du schéma

L'outil SchemaUpdate mettra à jour un schéma existant en effectuant les changement par "incrément". Notez que SchemaUpdate dépends beaucoup de l'API JDBC metadata, il ne fonctionnera donc pas avec tous les drivers JDBC.

java -cp classpath_hibernate net.sf.hibernate.tool.hbm2ddl.SchemaUpdate options fichiers_de_mapping

Tableau 15.4. SchemaUpdate Options de ligne de commande

OptionDescription
--quietne pas écrire vers la sortie standard
--properties=hibernate.propertieslire les propriétés de la base de données à partir d'un fichier

Vous pouvez intégrer SchemaUpdate dans votre application :

Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);

15.1.6. Utiliser Ant pour des mises à jour de schéma par incrément

Vous pouvez appeler SchemaUpdate depuis le script Ant :

<target name="schemaupdate">
    <taskdef name="schemaupdate"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaUpdateTask"
        classpathref="class.path"/>
    
    <schemaupdate
        properties="hibernate.properties"
        quiet="no">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaupdate>
</target>

15.2. Génération de code

Le générateur de code Hibernate peut être utilisé pour générer les squelettes d'implémentation des classes depuis un fichier de mapping. Cet outil est inclus dans la distribution des extensions Hibernate (téléchargement séparé).

hbm2java analyse les fichiers de mapping et génère les sources Java complètes. Ainsi, en fournissant les fichiers .hbm, on n'a plus à écire à la main les fichiers Java.

java -cp classpath_hibernate net.sf.hibernate.tool.hbm2java.CodeGenerator options fichiers_de_mapping

Tableau 15.5. Options de ligne de commande pour le générateur de code

OptionDescription
--output=repertoire_de_sortierépertoire racine pour le code généré
--config=fichier_de_configurationfichier optionnel de configuration

15.2.1. Le fichier de configuration (optionnel)

Le fichier de configuration fournit un moyen de spécifier de multiples "renderers" de code source et de déclarer des <meta> attributs qui seront globaux. Voir la section sur l'attribut <meta>.

<codegen>
    <meta attribute="implements">codegen.test.IAuditable</meta>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate
        package="autofinders.only"
        suffix="Finder"
        renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

Ce fichier de configuration déclare un méta attribut global "implements" et spécifie deux "renderers", celui par défaut (BasicRenderer) et un renderer qui génère des "finder" (requêteurs) (voir "Génération basique de finder" ci-dessous).

Le second renderer est paramétré avec un attribut package et suffixe.

L'attribut package spécifie que les fichiers sources générés depuis ce renderer doivent être placés dans ce package au lieu de celui spécifié dans les fichiers .hbm.

L'attribut suffixe spécifie le suffixe pour les fichiers générés. Ex: ici un fichier nommé Foo.java génèrera un fichier FooFinder.java.

Il est aussi possible d'envoyer des paramètres arbitraires vers les renderers en ajoutant les attributs <param> dans les éléments <generate>.

hbm2java supporte actuellement un tel paramètre appellé, generate-concrete-empty-classes qui informe le BasicRenderer de ne générer que les classes concrêtes qui étendent une classe de base pour toutes vos classes. Le fichier config.xml suivant illustre cette fonctionnalité.

            <codegen>
              <generate prefix="Base" renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/> 
              <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer">
                <param name="generate-concrete-empty-classes">true</param>
                <param name="baseclass-prefix">Base</param>
              </generate>
            </codegen>

Notez que ce config.xml configure deux renderers. Un qui génère les classes Base, et un second qui génère les classes concrêtes creuses.

15.2.2. L'attribut meta

L'attribut <meta> est un moyen simple d'annoter les fichiers hbm.xml avec des informations utiles aux outils. Ces informations, bien que non nécessaires au noyau d'Hibernate, se trouvent dont à un endroit naturel.

Vous pouvez utiliser l'élément <meta> pour dire à hbm2java de générer des setters "protected", d'implémenter toujours un certain nombre d'interfaces ou même d'étendre une classe de base particulière...

L'exemple suivant :

<class name="Person">
    <meta attribute="class-description">
        Javadoc de la classe Person
        @author Frodon
    </meta>
    <meta attribute="implements">IAuditable</meta>
    <id name="id" type="long">
        <meta attribute="scope-set">protected</meta>
        <generator class="increment"/>
    </id>
    <property name="name" type="string">
        <meta attribute="field-description">Le nom de la personne</meta>
    </property>
</class>

produira le code suivant (le code a été raccourci pour une meilleure compréhension). Notez la javadoc et les setters protected :

// package par défaut

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/** 
 *         Javadoc de la classe Person
 *         @author Frodon
 *     
 */
public class Person implements Serializable, IAuditable {

    /** identifier field */
    public Long id;

    /** nullable persistent field */
    public String name;

    /** full constructor */
    public Person(java.lang.String name) {
        this.name = name;
    }

    /** default constructor */
    public Person() {
    }

    public java.lang.Long getId() {
        return this.id;
    }

    protected void setId(java.lang.Long id) {
        this.id = id;
    }

    /** 
     * Le nom de la personne
     */
    public java.lang.String getName() {
        return this.name;
    }

    public void setName(java.lang.String name) {
        this.name = name;
    }

}

Tableau 15.6. Attributs meta supportés

AttributDescription
class-descriptioninséré dans les javadoc des classes
field-descriptioninséré dans les javadoc des champs/propriétés
interfaceSi true une interface est générée au lieu d'une classe
implementsinterface que la classe doit implémenter
extendsclasse que la classe doit étendre (ignoré pour les classes filles)
generated-classsurcharge le nom de la classe générée
scope-classvisibilité de la classe
scope-setvisibilité des méthodes setter
scope-getvisibilité des méthodes getter
scope-fieldvisibilité du champs
use-in-tostringinclus cette propriété dans toString()
implement-equalsinclus equals() et hashCode() dans cette classe.
use-in-equalsinclus cette propriété dans equals() et hashCode().
boundajoute le support de propertyChangeListener pour une propriété
constrainedsupport de bound + vetoChangeListener pour une propriété
gen-propertyla propriété ne sera pas générée si false (à utiliser avec précaution)
property-typeSurcharge le type par défaut de la propriété. Utilisez le pour spécifier un type concret au lieu de Object
class-codeCode supplémentaire qui sera inséré en fin de classe
extra-importImport supplémentaire qui sera inséré à la fin de tous les imports
finder-methodvoir "Générateur de requêteurs basiques"
session-methodvoir "Générateur de requêteurs basiques"

Les attributs déclarés via l'élément <meta> sont par défaut "hérités" dans les fichiers hbm.xml.

Ce qui veut dire ? Ceci veut dire que si, par exemple, vous voulez que toutes vos classes implémentent IAuditable, vous n'avez qu'à ajouter <meta attribute="implements">IAuditable</meta> au début du fichier hbm.xml, après <hibernate-mapping>. Toutes les classes définies dans les fichiers hbm.xml implémenteront IAuditable ! (A l'exception des classes qui ont meta attribut "implements", car les méta tags spécifiés localement surchargent/remplacent toujours les meta tags hérités).

Note : Ceci s'applique à tous les <meta> attributs. Ceci peut aussi être utilisé, par exemple, pour spécifier que tous les champs doivent être déclarés protected, au lieu de private par défaut. Pour cela, on ajoute <meta attribute="scope-field">protected</meta> juste après l'attribut <class> et tous les champs de cette classe seront protected.

Pour éviter d'hériter un <meta> attribut, vous pouvez spécifier inherit="false" pour l'attribut, par exemple <meta attribute="scope-class" inherit="false">public abstract</meta> restreindra la visibilité de classe à la classe courante, pas les classes filles.

15.2.3. Générateur de Requêteur Basique (Basic Finder)

Il est désormais possible de laisser hbm2java génèrer des requêteur (finders) basiques pour les propriétés mappées par Hibernate. Ceci nécessite deux choses dans les fichiers hbm.xml.

La première est une indication des champs pour lesquels les finders doivent être générés. Vous indiquez cela à l'aide d'un élément meta au sein de l'élément property :

<property name="name" column="name" type="string">
     <meta attribute="finder-method">findByName</meta>
</property>

Le texte inclus dans l'élément donnera le nom de la méthode finder.

La seconde est de créer un fichier de configuration pour hbm2java en y ajoutant le renderer adéquat :

<codegen>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate suffix="Finder" renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

Et utiliser ensuite le paramètre hbm2java --config=xxx.xmlxxx.xml est le fichier de configuration que vous venez de créer.

Un paramètre optionnel est un meta attribut au niveau de la classe de la forme :

<meta attribute="session-method">
    com.whatever.SessionTable.getSessionTable().getSession();
</meta>

Qui représente le moyen d'obtenir des sessions si vous utilisez le pattern Thread Local Session (documenté dans la zone Design Patterns du site web Hibernate).

15.2.4. Renderer/Générateur basés sur Velocity

Il est désormais possible d'utiliser velocity comme mécanisme de rendering alternatif. Le fichier config.xml suivant montre comment configurer hbm2java pour utiliser ce renderer velocity.

    <codegen>
     <generate renderer="net.sf.hibernate.tool.hbm2java.VelocityRenderer">
      <param name="template">pojo.vm</param>
     </generate>
    </codegen>

Le paramètre nommé template est un chemin de ressource vers le fichier de macro velocity que vous souhaitez utiliser. Ce fichier doit être disponible dans le classpath utilisé par hbm2java. N'oubliez donc pas d'ajouter le répertoire où se trouve pojo.vm à votre tâche ant ou script shell (le répertoire par défaut est ./tools/src/velocity).

Soyez conscients que le pojo.vm actuel ne génère que les parties basiques des java beans. Il n'est pas aussi complet et riche que le renderer par défaut - il lui manque notamment le support de beaucoup de meta tags.

15.3. Génération des fichier de mapping

Un squelette de fichier de mapping peut être généré depuis les classes persistantes compilées en utilisant l'outil en ligne de commande appelé MapGenerator. Cet outil fait partie de la distribution des extensions Hibernate.

Le générateur de fichier de mapping fournit un mécanisme qui produit les mappings à partir des classes compilées. Il utilise la réflexion java pour trouver les propriétés et l'heuristique pour trouver un mapping approprié pour le type de la propriété. Le fichier généré n'est qu'un point de départ. Il n'y a aucun moyen de produire un mapping Hibernate complet sans informations supplémentaires de l'utlisateur. Cependant, l'outil génère plusieurs des parties répétitives à écrire dans les fichiers de mapping.

Les classes sont ajoutées une par une. L'outil n'acceptera que les classes qu'il considère comme persistable par Hibernate.

Pour être persistable par Hibernate une classe

  • ne doit pas être de type primitif

  • ne doit pas être un tableau

  • ne doit pas être une interface

  • ne doit pas être une classe imbriquée

  • doit avoir un constructeur par défaut (sans argument)

Notez que les interfaces et classes imbriquées sont persistables par Hibernate, mais l'utilisateur ne le désirera généralement pas.

MapGenerator remontera la hiérarchie de classe pour essayer d'ajouter autant de superclasses (persistables par Hibernate) que possible à la même table dans la base de données. La "recherche" stoppe dès qu'une propriété, ayant son nom figurant dans la liste des noms d'UID candidats est trouvée.

La liste par défaut des noms de propriété candidats pour UID est: uid, UID, id, ID, key, KEY, pk, PK.

Les propriétés sont trouvées quand la classe possède un getter et un setter associé, quand le type du setter ayant un argument unique est le même que le type retourné par le getter sans argument, et que le setter retourne void. De plus le nom du setter doit commencer par set, le nom du getter par get (ou is si le type de la propriété est boolean). Le reste du nommage doit alors correspondre au nom de la propriété (à l'exception de l'initiale qui passe de minuscule à majuscule).

Les règles de détermination du type de base de données de chaque propriété sont :

  1. Si le type java est Hibernate.basic(), alors la propriété est une simple colonne de ce type.

  2. Pour les types utilisateurs hibernate.type.Type et PersistentEnum une simple colonne est aussi utilisée.

  3. Si le type est un tableau, alors un tableau Hibernate est utilisé, et MapGenerator essaie d'utiliser la réflexion sur un élément du tableau.

  4. Si le type est java.util.List, java.util.Map, ou java.util.Set, alors les types Hibernate correspondant sont utilisés, MapGenerator ne peut aller plus loin dans la découverte de ces types.

  5. Si le type est une autre classe, MapGenerator repousse la décision de sa représentation en base de données quand toutes les classes auront été traitées. A ce moment, si la classe a été trouvée via la recherche des superclasses décrites plus haut, la propriété est une association plusieurs-vers-un. Si la classe a des propriétés, alors c'est un composant. Dans les autres cas elle est sérialisable, ou non persistable.

15.3.1. Exécuter l'outil

L'outil écrit des mappings XML vers la sortie standard et/ou un fichier.

Quand vous invoquez l'outil, vos classes compilées doivent être dans le classpath.

java -cp classpath_contenant_hibernate_et_vos_classes net.sf.hibernate.tool.class2hbm.MapGenerator options et noms_des_classes

Il y a deux modes opératoires : par ligne de commande ou interactif.

Le mode intéractif est lancé en plaçant l'argument --interact dans la ligne de commande. Ce mode fournit un prompt de réponse. En l'utilisant vous pouvez paramétrer le nom de la propriété UID pour chacune des classes via la commande uid=XXXXXX est le nom de la propriété UID. Les autres commande sont simplement le nom de la classe entièrement qualifiée, ou la commande done qui émet l'XML et termine.

Dans le mode ligne de commande les arguments sont les options ci-dessous espacées du nom qualifié de la classe à traiter. La plupart des options sont faites pour être utilisées plusieurs fois, chacune affectant les classes ajoutées.

Tableau 15.7. Options de la ligne de commande MapGenerator

OptionDescription
--quietne pas afficher le mapping O-R vers la sortie standard
--setUID=uidparamètre la liste des UIDs candidats sur le singleton uid
--addUID=uidajouter uid au début de la liste des UIDs candidats
--select=modesélectionne le mode utilisé. mode(e.g., distinct or all) pour les classes ajoutées par la suite
--depth=<small-int>limite le nombre de récursions utilisées pour les trouver les composants pour les classes ajoutées par la suite
--output=mon_mapping.xmlenvoie le mapping O-R vers un fichier
nom.de.classe.Qualifieajoute la classe au mapping
--abstract=nom.de.classe.Qualifievoir ci dessous

Le paramètre abstract configure l'outil map generator afin qu'il ignore les super classes spécifiques et donc pour que les classes hérités ne soient pas mappées dans une grande table Par exemple, regardons ces hiérarchies de classe :

Animal-->Mamiphère-->Humain

Animal-->Mamiphère-->Marsupial-->Kangourou

Si le paramètre --abstract n'est pas utilisé, toutes les classes seront mappées comme classes filles de Animal, ce qui donnera une grande table contenant toutes les propriétés de toutes les classes plus une colonne discriminante indiquant laquelle de classes fille est réellement stockée dans cet enregistrement. Si mamiphère est marquée comme abstract, Humain et Marsupial seront mappés à des déclarations de <class> séparées et stockées dans des tables différentes. Kangaroo sera une classe fille de Marsupial à moins que Marsupial soit aussi marquée comme abstract.