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) :
Génération du schéma DDL depuis un fichier de mapping (aussi appelé SchemaExport, hbm2ddl)
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 :
Génération de source Java à partir d'un fichier de mapping (CodeGenerator, hbm2java)
Génération de fichiers de mapping à partir des classes java compilées ou à partir des sources Java marquées avec XDoclet (MapGenerator, class2hbm)
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 :
Middlegen (génération du fichier de mapping à partir d'un schéma de base de données existant)
AndroMDA (génération du code des classes persistantes à partir de diagrammes UML et de leur représentation XML/XMI en utilisant une stratégie MDA - Model-Driven Architecture)
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).
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.
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é
Attribut | Valeur | Interprétation |
---|---|---|
length | numérique | précision d'une colonne (longueur ou décimal) |
not-null | true|false | spécifie que la colonne doit être non-nulle |
unique | true|false | spécifie que la colonne doit avoir une contrainte d'unicité |
index | index_name | spécifie le nom d'un index (multi-colonnes) |
unique-key | unique_key_name | spécifie le nom d'une contrainte d'unicité multi-colonnes |
foreign-key | foreign_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-type | column_type | surcharge le type par défaut (attribut de l'élément <column> uniquement) |
check | SQL expression | créé une contrainte de vérification sur la table ou la colonne |
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
Option | Description |
---|---|
--quiet | ne pas écrire le script vers la sortie standard |
--drop | supprime seuleument les tables |
--text | ne pas exécuter sur la base de données |
--output=my_schema.ddl | écrit le script ddl vers un fichier |
--config=hibernate.cfg.xml | lit la configuration Hibernate à partir d'un fichier XML |
--properties=hibernate.properties | lit les propriétés de la base de données à partir d'un fichier |
--format | formatte proprement le SQL généré dans le script |
--delimiter=x | paramè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);
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_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 l'utilisateur |
hibernate.dialect | dialecte |
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.
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
Option | Description |
---|---|
--quiet | ne pas écrire vers la sortie standard |
--properties=hibernate.properties | lire 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);
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>
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
Option | Description |
---|---|
--output=repertoire_de_sortie | répertoire racine pour le code généré |
--config=fichier_de_configuration | fichier optionnel de configuration |
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.
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
Attribut | Description |
---|---|
class-description | inséré dans les javadoc des classes |
field-description | inséré dans les javadoc des champs/propriétés |
interface | Si true une interface est générée au lieu d'une classe |
implements | interface que la classe doit implémenter |
extends | classe que la classe doit étendre (ignoré pour les classes filles) |
generated-class | surcharge le nom de la classe générée |
scope-class | visibilité de la classe |
scope-set | visibilité des méthodes setter |
scope-get | visibilité des méthodes getter |
scope-field | visibilité du champs |
use-in-tostring | inclus cette propriété dans toString() |
implement-equals | inclus equals() et hashCode() dans cette classe. |
use-in-equals | inclus cette propriété dans equals() et hashCode(). |
bound | ajoute le support de propertyChangeListener pour une propriété |
constrained | support de bound + vetoChangeListener pour une propriété |
gen-property | la propriété ne sera pas générée si false (à utiliser avec précaution) |
property-type | Surcharge le type par défaut de la propriété. Utilisez le pour spécifier un type concret au lieu de Object |
class-code | Code supplémentaire qui sera inséré en fin de classe |
extra-import | Import supplémentaire qui sera inséré à la fin de tous les imports |
finder-method | voir "Générateur de requêteurs basiques" |
session-method | voir "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.
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.xml où xxx.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).
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.
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 :
Si le type java est Hibernate.basic(), alors la propriété est une simple colonne de ce type.
Pour les types utilisateurs hibernate.type.Type et PersistentEnum une simple colonne est aussi utilisée.
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.
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.
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.
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=XXX où XXX 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
Option | Description |
---|---|
--quiet | ne pas afficher le mapping O-R vers la sortie standard |
--setUID=uid | paramètre la liste des UIDs candidats sur le singleton uid |
--addUID=uid | ajouter uid au début de la liste des UIDs candidats |
--select=mode | sé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.xml | envoie le mapping O-R vers un fichier |
nom.de.classe.Qualifie | ajoute la classe au mapping |
--abstract=nom.de.classe.Qualifie | voir 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.