Les mappings objet/relationnel sont définis dans un document XML. Le document de mapping est conçu pour être lisible et éditable à la main. Le vocabulaire de mapping est orienté Java, ce qui signifie que les mappings sont construits autour des classes java et non autour des déclarations de tables.
Même si beaucoup d'utilisateurs d'Hibernate choisissent d'écrire les fichiers de mapping à la main, il existe des outils pour les générer, comme XDoclet, Middlegen et AndroMDA.
Enchaînons sur un exemple de mapping:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="CATS" discriminator-value="C"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <discriminator column="subclass" type="character"/> <property name="birthdate" type="date"/> <property name="color" not-null="true"/> <property name="sex" not-null="true" update="false"/> <property name="weight"/> <many-to-one name="mate" column="mate_id"/> <set name="kittens"> <key column="mother_id"/> <one-to-many class="Cat"/> </set> <subclass name="DomesticCat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </class> <class name="Dog"> <!-- Le mapping de dog peut être placé ici --> </class> </hibernate-mapping>
Nous allons parler du document de mapping. Nous aborderons uniquement les éléments du document utilisés à l'exécution par Hibernate. Ce document contient d'autres attributs et éléments optionnels qui agissent sur le schéma de base de données exporté par l'outil d'export de schéma.(par exemple l'attribut not-null.)
Tous les mappings XML doivent déclarer le doctype de l'exemple précédent. L'actuelle DTD peut être trouvée à l'URL du dessus, dans le répertoire hibernate-x.x.x/src/net/sf/hibernate ou dans hibernate2.jar. Hibernate cherchera toujours en priorité la DTD dans le classpath.
Cet élément possède trois attributs optionnels. L'attribut schema spécifie à quel schéma appartiennent les tables déclarées par ce mapping. S'il est spécifié, les noms des tables seront qualifiés par le nom de schéma donné. S'il est absent, les noms des tables ne seront pas qualifiées. L'attribut default-cascade spécifie quel style de cascade doit être adopté pour les propriétés et collections qui ne spécifient par leur propre attribut cascade. L'attribut auto-import nous permet d'utiliser, par défaut, des noms de classe non qualifiés dans le langage de requête.
<hibernate-mapping schema="nomDeSchema" (1) default-cascade="none|save-update" (2) auto-import="true|false" (3) package="nom.de.package" (4) />
(1) | schema (optionnel): Le nom du schéma de base de données. |
(2) | default-cascade (optionnel - par défaut = none): Un style de cascade par défaut. |
(3) | auto-import (optionnel - par défaut = true): Spécifie si l'on peut utiliser des noms de classes non qualifiés (pour les classes de ce mapping) dans le langage de requête. |
(4) | package (optionnel): Spécifie un préfixe de package à prendre en compte pour les noms de classes non qualifiées dans le mapping courant. |
Si vous avez deux classes persistantes avec le même nom (non qualifié), vous devriez utiliser auto-import="false". Hibernate lancera une exception si vous essayez d'assigner deux classes au même nom "importé".
Vous pouvez déclarer une classe persistante en utilisant l'élément class:
<class name="NomDeClasse" (1) table="NomDeTable" (2) discriminator-value="valeur_de_discriminant" (3) mutable="true|false" (4) schema="proprietaire" (5) proxy="InterfaceDeProxy" (6) dynamic-update="true|false" (7) dynamic-insert="true|false" (8) select-before-update="true|false" (9) polymorphism="implicit|explicit" (10) where="condition SQL where quelconque" (11) persister="ClasseDePersistance" (12) batch-size="N" (13) optimistic-lock="none|version|dirty|all" (14) lazy="true|false" (15) />
(1) | name : Le nom de classe entièrement qualifié pour la classe (ou l'interface) persistante. |
(2) | table : Le nom de sa table en base de données. |
(3) | discriminator-value (optionnel - valeur par défaut = nom de la classe) : Une valeur qui distingue les classes filles, utilisé pour le comportement polymorphique. Sont aussi autorisées les valeurs null et not null. |
(4) | mutable (optionnel, valeur par défaut = true) : Spécifie qu'une instance de classe est (ou n'est pas) mutable. |
(5) | schema (optionnel) : Surcharge le nom de schéma défini par l'élément racine <hibernate-mapping>. |
(6) | proxy (optionnel) : Spécifie une interface à utiliser pour initialiser tardivement (lazy) les proxies. Vous pouvez spécifier le nom de la classe elle-même. |
(7) | dynamic-update (optionnel, valeur par défaut = false): Spécifie si l'ordre SQL UPDATE doit être généré à l'exécution et ne contenir que les colonnes dont les valeurs ont changé. |
(8) | dynamic-insert (optionnel, valeur par défaut = false): Spécifie si l'ordre SQL INSERT doit être généré à l'exécution et ne contenir que les colonnes dont les valeurs ne sont pas null. |
(9) | select-before-update (optionnel, valeur par défaut = false): Spécifie qu'Hibernate ne doit jamais effectuer un UPDATE SQL à moins d'être certain qu'un objet ait réellement été modifié. Dans certains cas (en fait, lorsqu'un objet transiant a été associé à une nouvelle session en utilisant update()), cela signifie qu'Hibernate effectuera un SELECT SQL supplémentaire pour déterminer si un UPDATE est réellement requis. |
(10) | polymorphism (optionnel, par défaut = implicit): Détermine si, pour cette classe, une requête polymorphique implicite ou explicite est utilisée. |
(11) | where (optionnel) spécifie une clause SQL WHERE à utiliser lorsque l'on récupère des objets de cette classe. |
(12) | persister (optionnel): Spécifie un ClassPersister particulier. |
(13) | batch-size (optionnel, par défaut = 1) spécifie une taille de batch pour remplir les instances de cette classe par identifiant en une seule requête. |
(14) | optimistic-lock (optionnel, par défaut = version): Détermine la stratégie de verrou optimiste. |
(15) | lazy (optionnel): Déclarer lazy="true" est un raccourci pour spécifier le nom de la classe comme étant l'interface proxy. |
Il est parfaitement acceptable pour une classe persistante nommée, d'être une interface. Vous devriez alors déclarer les classes implémentant cette interface via l'élément <subclass>. Vous pouvez persister n'importe quelle classe interne statique. Vous devriez spécifier le nom de classe en utilisant la forme standard : eg.Foo$Bar.
Les classes non mutables, mutable="false", ne peuvent être modifiées ou effacées par l'application. Cela permet à Hibernate d'effectuer quelques optimisations de performance mineures.
L'attribut optionnel proxy active l'initialisation tardive des instances persistantes de la classe. Hibernate retournera d'abord des proxies CGLIB qui implémentent l'interface définie. Les objets persistants réels seront chargés lorsqu'une méthode du proxy est invoquée. Voir "Proxies pour initialisation tardive" ci dessous.
Le polymorphisme implicite signifie que les instances de la classe seront retournées par une requête qui utilise les noms de la classe ou de chacunes de ses superclasses ou encore des interfaces implémentées par cette classe ou ses superclasses. Les instances des classes filles seront retournées par une requête qui utilise le nom de la classe elle même. Le polymorphisme explicite signifie que les instances de la classe ne seront retournées que par une requête qui utilise explicitement son nom et que seules les instances des classes filles déclarées dans les éléments <subclass> ou <joined-subclass> seront retournées. Dans la majorités des cas la valeur par défaut, polymorphism="implicit", est appropriée. Le polymorphisme explicite est utile lorsque deux classes différentes sont mappées à la même table (ceci permet d'écrire une classe "légère" qui ne contient qu'une partie des colonnes de la table - voir la partie design pattern du site communautaire).
L'attribut persister vous permet de customiser la stratégie de persistance utilisée pour la classe. Vous pouvez, par exemple, spécifier votre propre classe fille de net.sf.hibernate.persister.EntityPersister ou vous pouvez même fournir une nouvelle implémentation de l'interface net.sf.hibernate.persister.ClassPersister qui implémente la persistance via, par exemple, des appels à une procédure stockée, la sérialisation dans des fichiers plats ou dans un LDAP. Voir net.sf.hibernate.test.CustomPersister pour un exemple simple (de "persistance" dans une Hashtable).
Notez que les paramètres dynamic-update et dynamic-insert ne sont pas hérités par les classes filles et peuvent donc être spécifiés dans les éléments <subclass> ou <joined-subclass>. Ces paramètres peuvent accroître les performances dans certains cas, mais peuvent aussi être plus lourds dans d'autres cas. A utiliser de manière judicieuse.
L'utilisation de select-before-update fera généralement baisser les performances. Il est cependant très pratique lorsque l'on veut empêcher un trigger de base de données qui se déclenche sur un update d'être appelé inutilement.
Si vous activez dynamic-update, vous aurez le choix entre les stratégies de verrou optimiste suivantes:
version vérifie les colonnes version/timestamp
all vérifie toutes les colonnes
dirty vérifie les colonnes modifiées
none n'utilise pas le verrou optimiste
Nous vous recommandons vivement d'utiliser les colonnes version/timestamp pour le verrou optimiste avec Hibernate. C'est la stratégie optimale qui respecte les performances et c'est la seule capable de gérer correctement les modifications faites en dehors de la session (c'est-à-dire : lorsque Session.update() est utilisée). Gardez à l'esprit qu'une propriété version ou timestamp ne devrait jamais être nulle, quelle que soit la stratégie d'unsaved-value, ou alors une instance sera détectée comme transiante.
Les classes mappées doivent déclarer la colonne clé primaire de la table. La plupart des classes auront aussi une propriété, respectant la convention JavaBean, contenant l'identifiant unique d'une instance. L'élément <id> définit le mapping entre cette propriété et cette colonne clé primaire.
<id name="nomDePropriete" (1) type="nomdetype" (2) column="nom_de_colonne" (3) unsaved-value="any|none|null|id_value" (4) access="field|property|NomDeClasse"> (5) <generator class="generatorClass"/> </id>
(1) | name (optionnel) : Le nom de la propriété d'identifiant. |
(2) | type (optionnel) : Le nom qui indique le type Hibernate. |
(3) | column (optionnel - par défaut le nom de la propriété) : Le nom de la colonne de la clé primaire. |
(4) | unsaved-value (optionnel - par défaut = null) : Une valeur de la propriété d'identifiant qui indique que l'instance est nouvellement instanciée (non sauvegardée), et qui la distingue des instances transiantes qui ont été sauvegardées ou chargées dans une session précédente. |
(5) | access (optionnel - par défaut = property): La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
Si l'attribut name est manquant, on suppose que la classe n'a pas de propriété d'identifiant.
L'attribut unsaved-value est important ! Si la propriété d'identifiant de votre classe n'est pas nulle par défaut, vous devriez alors spécifier cet attribut.
Il existe une déclaration alternative : <composite-id>. Elle permet d'accéder aux données d'une table ayant une clé composée. Nous vous conseillons fortement de ne l'utiliser que pour ce cas précis.
L'élément fils obligatoire <generator> définit la classe Java utilisée pour générer l'identifiant unique des instances d'une classe persistante. Si des paramètres sont requis pour configurer ou initialiser l'instance du générateur, ils seront passés via l'élément <param>.
<id name="id" type="long" column="uid" unsaved-value="0"> <generator class="net.sf.hibernate.id.TableHiLoGenerator"> <param name="table">uid_table</param> <param name="column">next_hi_value_column</param> </generator> </id>
Tous les générateurs implémentent l'interface net.sf.hibernate.id.IdentifierGenerator. C'est une interface très simple ; certaines applications peuvent choisir de fournir leur propre implémentation spécifique. Cependant, Hibernate fournit plusieurs implémentations de manière native. Il y a des diminutifs pour les générateurs natifs :
génère des identifiants du type long, short ou int qui sont uniques seulement lorsqu'aucun autre process n'insère de données dans la même table. Ne pas utiliser dans un cluster.
supporte les colonnes identity dans DB2, MySQL, MS SQL Server, Sybase et HypersonicSQL. L'identifiant retourné est du type long, short ou int.
utilise une séquence dans DB2, PostgreSQL, Oracle, SAP DB, McKoi ou un générateur dans Interbase. L'identifiant retourné est de type long, short ou int
utilise l'algorithme hi/lo pour générer de manière performante les identifiants de type long, short ou int, en donnant une table et une colonne (par défaut hibernate_unique_key et next_hi) comme source des valeurs "hi". L'algorithme hi/lo génère des identifiants qui sont uniques pour une base de données donnée. Ne pas utiliser ce générateur avec des connexions liées à JTA ou gérées par l'utilisateur.
utilise l'algorithme hi/lo pour générer les identifiants de type long, short ou int, en donnant le nom d'une séquence de base de données.
utilise l'algorithme à 128-bit UUID pour générer les identifiants de type string, uniques sur un réseau donné (l'adresse IP est utilisée). L'UUID est encodée comme une chaîne de 32 chiffres hexadécimaux.
utilise le même algorithme UUID. L'UUID est encodée comme une chaine de 16 caractères ASCII (n'importe lequel). Ne pas utiliser avec PostgreSQL.
choisit identity, sequence ou hilo en fonction des possibilités de la base de données.
laisse l'application assigner l'identifiant de l'objet avant l'appel à save().
utilise l'identifiant d'un autre objet associé. Généralement utilisé en conjonction d'une association <one-to-one> par clé primaire.
Les générateurs hilo et seqhilo fournissent deux implémentations alternatives de l'algorithme hi/lo, une approche très répandue pour la génération d'identifiant. La première implémentation nécessite une table "spéciale" pour gérer la prochaine valeur "hi". La seconde utilise une séquence de type Oracle (si supportée).
<id name="id" type="long" column="cat_id"> <generator class="hilo"> <param name="table">hi_value</param> <param name="column">next_value</param> <param name="max_lo">100</param> </generator> </id>
<id name="id" type="long" column="cat_id"> <generator class="seqhilo"> <param name="sequence">hi_value</param> <param name="max_lo">100</param> </generator> </id>
Malheureusement, vous ne pouvez utiliser hilo lorsque vous fournissez manuellement votre propre Connection à Hibernate, ou lorsqu'Hibernate utilise une datasource d'un serveur d'application enrolée dans un contexte JTA. Hibernate doit être capable de récupérer la valeur "hi" dans une nouvelle transaction (séparée de la transaction courante). Une approche classique dans un environnement EJB est d'implémenter l'algorithme hi/lo en utilisant un session bean stateless.
L'UUID contient : l'adresse IP, la date de démarrage de la JVM (arrondie au quart de seconde), la date système et une valeur de compteur (unique pour une JVM). Il n'est pas possible d'obtenir l'adresse MAC ou l'adresse mémoire d'un code Java, ceci est donc le mieux que l'on puisse faire sans utiliser JNI.
N'essayez pas d'utiliser uuid.string dans PostgreSQL.
Pour les bases de données qui supportent les colonnes identity (DB2, MySQL, Sybase, SQL Server), vous pouvez utiliser la génération de clé identity. Pour les bases de données qui supportent les séquences (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB), vous pouvez utiliser la génération de clé de style sequence. Ces deux stratégies nécessitent deux requêtes SQL pour insérer un nouvel objet.
<id name="id" type="long" column="uid"> <generator class="sequence"> <param name="sequence">uid_sequence</param> </generator> </id>
<id name="id" type="long" column="uid" unsaved-value="0"> <generator class="identity"/> </id>
Pour le développement multi plate formes, la stratégie native choisira entre identity, sequence et hilo, en fonction des possiblités de la base de données utilisée.
Si vous souhaitez que l'application assigne les identifiants (en opposition à la génération faite par Hibernate), utilisez le générateur assigned. Ce générateur spécial utilisera la valeur de l'identifiant déjà assigné à la propriété d'identifiant de l'objet. Attention lorsque vous utilisez cette possibilité, il faut utiliser des clés avec un sens métier (ce qui est toujours de design discutable).
A cause de leur nature même, les entités qui utilisent ce générateur ne peuvent être sauvées via la méthode saveOrUpdate de la session. Vous devez spécifier vous même si l'objet doit être sauvé ou mis à jour en appelant soit save() soit update() sur la session.
<composite-id name="nomDePropriete" class="NomDeClasse" unsaved-value="any|none" access="field|property|NomdeClasse"> <key-property name="nomDePropriete" type="nomdetype" column="nom_de_colonne"/> <key-many-to-one name="NomDePropriete" class="NomDeClasse" column="nom_de_colonne"/> ...... </composite-id>
Pour une table avec clé composée, vous pouvez mapper plusieurs propriétés de la classe comme propriétés identifiantes. L'élément <composite-id> accepte des mappings de propriétés via <key-property> et des mappings d'éléments fils via <key-many-to-one>.
<composite-id> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id>
Votre classe persistante doit surcharger equals() et hashCode() pour implémenter l'égalité des identifiants composés. Elle doit aussi implémenter Serializable.
Malheureusement, cette approche avec identifiant composée signifie qu'un objet persistant est son propre identifiant. Il n'y a pas d'autres "clients" potentiels que l'objet persistant lui même. Vous devez instancier une instance de la classe persistante, renseigner ses propriétés identifiantes avant de pouvoir charger (load()) l'état persistant associé à la clé composée. Nous décrirons une méthode plus pratique où la clé composée est implémentée dans une classe distincte dans Section 7.4, « composants en tant qu'identifiants composés ». Les attributs décris ci dessous s'appliquent uniquement à l'approche alternative:
name (optionnel) : Une propriété de type composant qui contient l'identifiant composé (voir section suivante).
class (optionnel - par défaut = le type de la propriété déterminé par réflexion) : La classe composant utilisée comme identifiant composé (voir section suivante).
unsaved-value (optionnel - par défaut = none) : Indique qu'une instance transiante doit être considérée comme nouvellement instanciée, si paramétré à any.
L'élément <discriminator> est requis pour la persistance polymorphique dans le cadre de la stratégie de mapping "table par hiérarchie de classe" (table-per-class-hierarchy) et spécifie une colonne discriminatrice de la table. La colonne discriminatrice contient une valeur qui indique à la couche de persistance quelle classe fille doit être instanciée pour un enregistrement particulier. Un ensemble restreint de types peut être utilisé : string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator column="colonne_du_discriminateur" (1) type="type_du_discriminateur" (2) force="true|false" (3) insert="true|false" (4) />
(1) | column (optionnel - par défaut = class) : le nom de la colonne discriminatrice. |
(2) | type (optionnel - par défaut = string) : un nom qui indique le type Hibernate |
(3) | force (optionnel - par défaut = false) : "force" Hibernate à spécifier les valeurs discriminatrices permises même lorsque toutes les instances de la classe "racine" sont récupérées. |
(4) | insert (optionnel - par défaut = true) : positionner le à false si votre colonne discriminatrice fait aussi partie d'un identifiant composé mappé. |
Les différentes valeurs de la colonne discriminatrice sont spécifiées par l'attribut discriminator-value des éléments <class> et <subclass>.
L'attribut force est utile si la table contient des lignes avec d'autres valeurs qui ne sont pas mappées à une classe persistante. Ce qui ne sera généralement pas le cas.
L'élément <version> est optionnel est indique que la table contient des données versionnées. Ceci est particulièrement utile si vous prévoyez d'utiliser des transations longues (voir plus loin).
<version column="colonne_de_version" (1) name="nomDePropriete" (2) type="nomdetype" (3) access="field|property|NomDeClasse" (4) unsaved-value="null|negative|undefined" (5) />
(1) | column (optionnel - par défaut = le nom de la propriété) : Le nom de la colonne contenant le numéro de version. |
(2) | name : Le nom de la propriété de classe persistante. |
(3) | type (optionnel - par défaut = integer) : Le type du numéro de version. |
(4) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
(5) | unsaved-value (optionnel - par défaut = undefined) : Une valeur de la propriété "version" qui indique qu'une instance est nouvellement instanciée (non sauvegardée), qui la distingue des instances transiantes qui ont été chargées ou sauvegardées dans une session précédente (undefined spécifie que la propriété d'identifiant doit être utilisée). |
Les numéros de version peuvent être de type long, integer, short, timestamp ou calendar.
L'élément optionnel <timestamp> indique que la table contient des données "timestampées". C'est une alternative au versioning. Les timestamps sont par nature une implémentation moins sûre du verrou optimiste. Cependant, l'application peut parfois utiliser les timestamps dans d'autres buts.
<timestamp column="colonne_de_timestamp" (1) name="nomDePropriete" (2) access="field|property|NomDeClasse" (3) unsaved-value="null|undefined" (4) />
(1) | column (optionnel - par défaut = le nom de la propriété) : Le nom de la colonne contenant le timestamp. |
(2) | name: Le nom de la propriété de type Java Date ou Timestamp dans la classe persistante. |
(3) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la propriété. |
(4) | unsaved-value (optionnel - par défaut = null) : Une valeur de la propriété "version" qui indique qu'une instance est nouvellement instanciée (non sauvegardée), qui la distingue des instances transiantes qui ont été chargées ou sauvegardées dans une session précédente (undefined spécifie que la propriété d'identifiant doit être utilisée). |
Notez que <timestamp> est équivalent à <version type="timestamp">.
L'élément <property> déclare une propriété persistante de la classe, respectant la convention JavaBean.
<property name="nomDePropriete" (1) column="nom_de_colonne" (2) type="nomdetype" (3) update="true|false" (4) insert="true|false" (4) formula="expression SQL quelconque" (5) access="field|property|NomDeClasse" (6) />
(1) | name : Le nom de la propriété, l'initiale étant en minuscule (cf conventions JavaBean). |
(2) | column (optionnel - par défaut = le nom de la propriété) : le nom de la colonne de base de données mappée. |
(3) | type (optionnel) : un nom indiquant le type Hibernate. |
(4) | update, insert (optionnel - par défaut = true) : spécifie que les colonnes mappées doivent être incluses dans l'ordre SQL UPDATE et/ou INSERT. Paramétrer les deux à false permet à la propriété d'être "dérivée", sa valeur étant initialisée par une autre propriété qui mappe la(les) même(s) colonne(s), par un trigger ou par une autre application. |
(5) | formula (optionnel) : une expression SQL qui définit une valeur pour une propriété calculée. Les propriétés n'ont pas de colonne mappée. |
(6) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la propriété. |
typename peut être :
Le nom d'un type Hibernate basique (ex. integer, string, character, date, timestamp, float, binary, serializable, object, blob).
Le nom d'une classe Java avec un type basique par défaut (eg. int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob).
Le nom d'une classe fille de PersistentEnum (ex. eg.Color).
Le nom d'une classe Java serialisable.
Le nom d'une classe Java implémentant un type personnalisé (ex. com.illflow.type.MyCustomType).
Si vous ne spécifiez pas de type, Hibernate utilisera la réflexion sur la propriété définie pour trouver la bonne correspondance avec le type Hibernate. Hibernate essaiera d'interpréter le nom de la classe retournée par le getter de la propriété en utilisant successivement les règles 2, 3, 4. Cependant, cela ne suffit pas toujours. Dans certains cas, vous aurez toujours besoin d'un attribut type (Par exemple, pour distinguer Hibernate.DATE de Hibernate.TIMESTAMP, ou pour spécifier un type personnalisé).
L'attribut access vous permet de contrôler la manière avec laquelle Hibernate accède à la propriété à l'exécution. Par défaut, Hibernate utilisera les accesseurs de l'attribut. Si vous spécifiez access="field", Hibernate court circuitera les accesseurs et accédera directement à l'attribut, en utilisant la réflexion. Vous pouvez spécifier votre propre stratégie en nommant une classe qui implémente l'interface net.sf.hibernate.property.PropertyAccessor.
Une association simple vers une autre classe persistante est déclarable en utilisant un élément many-to-one. Le modèle relationnel est une association many-to-one (Il s'agit au sens propre de la référence à un objet).
<many-to-one name="nomDePropriete" (1) column="nom_de_colonne" (2) class="NomDeClasse" (3) cascade="all|none|save-update|delete" (4) outer-join="true|false|auto" (5) update="true|false" (6) insert="true|false" (6) property-ref="nomDeProprieteDUneClasseAssociee" (7) access="field|property|NomDeClasse" (8) unique="true|false" (9) />
(1) | name : Le nom de la propriété. |
(2) | column (optionnel) : Le nom de la colonne. |
(3) | class (optionnel - par défaut = au type de la propriété déterminé par réflexion) : Le nom de la classe associée. |
(4) | cascade (optional) : Spécifie quelles opérations doivent être effectuées en cascade de l'objet parent vers l'objet associé. |
(5) | outer-join (optionnel - par défaut = auto) : active le chargement par outer-join lorsque hibernate.use_outer_join est activé. |
(6) | update, insert (optionnel - par défaut = true) : spécifie que les colonnes mappées doivent être incluses dans l'ordre SQL UPDATE et/ou INSERT. Paramétrer les deux à false permet à la propriété d'être "dérivée", sa valeur étant initialisée par une autre propriété qui mappe la(les) même(s) colonne(s), par un trigger ou par une autre application. |
(7) | property-ref : (optionnel) Le nom de la propriété de la classe associée qui est liée à cette clé étrangère. Si non spécifiée, la clé primaire de la classe associée est utilisée. |
(8) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
(9) | unique (optionnel) : Active la génération DDL d'une contrainte unique pour la colonne clé-étrangère. |
L'attribut cascade autorise les valeurs suivantes : all, save-update, delete, none. Fixer une valeur différente de none propagera certaines opérations à l'objet (fils) associé. Voir "Cycle de vie de l'objet" ci dessous.
L'attribut outer-join accepte trois valeurs différentes :
auto (par défaut) : Charge l'association en utilisant une jointure ouverte si la classe associée n'a pas de proxy.
true : Charge toujours l'association en utilisant une jointure ouverte.
false : Ne charge jamais l'association en utilisant une jointure ouverte.
Une déclaration typique de many-to-one est aussi simple que
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
L'attribut property-ref ne devrait être utilisé que pour mapper des données d'un système hérité (lecagy system) où une clé étrangère fait référence à une autre clé unique de la table associée. Ce genre de modèle relationnel peut être qualifié de... laid. Par example, supposez que la classe Product a un numéro de série unique, qui n'est pas la clé primaire (L'attribut unique contrôle la génération DDL d'Hibernate avec l'outil SchemaExport).
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
Voici le mapping que OrderItem pourrait utiliser:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
Cela n'est clairement pas encouragé.
Une association one-to-one vers une autre classe persistante est déclarée en utilisant un élément one-to-one.
<one-to-one name="nomDePropriete" (1) class="NomDeClasse" (2) cascade="all|none|save-update|delete" (3) constrained="true|false" (4) outer-join="true|false|auto" (5) property-ref="nomDeProprieteDUneClasseAssociee" (6) access="field|property|NomDeClasse" (7) />
(1) | name : Le nom de la propriété. |
(2) | class (optionnel - par défaut = le type de la propriété déterminée par réflexion) : le nom de la classe associée. |
(3) | cascade (optionnel) : spécifie quelles opérations doivent être réalisées en cascade de l'objet parent vers l'objet associé. |
(4) | constrained : (optionnel) spécifie qu'une contrainte sur la clé primaire de la table mappée fait référence à la table de la classe associée. Cette option affecte l'ordre dans lequel save() et delete() sont effectués en cascade (elle est aussi utilisée dans l'outil schema export). |
(5) | outer-join (optionnel - par défaut = auto) : Active le chargement par jointure ouverte de l'association lorsque hibernate.use_outer_join est activé. |
(6) | property-ref: (optionnel) : Le nom de la propriété de la classe associée qui est liée à cette clé étrangère. Si non spécifié, la clé primaire de la classe associée est utilisée. |
(7) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
Il y a deux types d'association one-to-one:
les associations sur clé primaire
les associations sur clé étrangère unique
Les associations sur clé primaire ne nécessitent pas de colonne supplémentaire dans la table ; si deux enregistrements sont liés par l'association alors les deux enregistrements partagent la même valeur de clé primaire. Ainsi, si vous souhaitez que deux objets soient liés par association sur clé primaire, vous devez vous assurer qu'ils aient la même valeur d'identifiant !
Pour une association par clé primaire, ajoutez les mappings suivant à Employee et Person, respectivement:
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Assurez vous que les clés primaires des lignes associées dans les tables PERSON et EMPLOYEE sont égales. Nous utilisons, dans ce cas, une stratégie de génération d'identifiant Hibernate spéciale, appelée foreign:
<class name="person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="foreign"> <param name="property">employee</param> </generator> </id> ... <one-to-one name="employee" class="Employee" constrained="true"/> </class>
Une nouvelle instance de Person voit alors son identifiant assigné à la même valeur de clé primaire que l'instance d'Employee référencée par la propriété employee de cette Person.
Par ailleurs, une clé étrangère avec une contrainte d'unicité, d'Employee vers Person, peut etre déclarée comme :
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
Et cette association peut être bidirectionnelle en ajoutant ceci dans le mapping de Person:
<one-to-one name"employee" class="Employee" property-ref="person"/>
L'élément <component> mappe des propriétés d'un objet fils à des colonnes de la table de la classe parent. Les composants peuvent eux aussi déclarer leurs propres propriétés, composants ou collections. Voir "Components" plus tard.
<component name="nomDePropriete" (1) class="NomDeClasse" (2) insert="true|false" (3) upate="true|false" (4) access="field|property|NomDeCLasse">(5) <property ...../> <many-to-one .... /> ........ </component>
(1) | name: Le nom de la propriété. |
(2) | class (optionnel - par défaut = le type de la propriété déterminé par réflexion) : Le nom de la classe du composant (fils). |
(3) | insert : Est ce que la colonne mappée apparait dans l'INSERT SQL? |
(4) | update : Est ce que la colonne mappée apparait dans l'UPDATE SQL? |
(5) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
Les tags <property> fils mappent les propriétés de la classe fille aux colonnes de la table.
L'élément <component> accepte un sous élément <parent> qui mappe une propriété du composant comme référence vers l'entité contenante.
L'élément <dynamic-component> accepte qu'une Map soit mappée comme un composant, où les noms des propriétés font référence aux clés de la map.
Enfin, les requêtes polymorphiques nécessitent une déclaration explicite de chaque classe héritée de la classe racine. Pour la stratégie de mapping (recommandée) table par hiérarchie de classes (table-per-class-hierarchy), la déclaration <subclass> est utilisée.
<subclass name="NomDeClasse" (1) discriminator-value="valeur_de_discriminant" (2) proxy="InterfaceDeProxy" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false"> <property .... /> ..... </subclass>
(1) | name : Le nom complet de la classe fille. |
(2) | discriminator-value (optionnel - par défaut le nom de la classe) : Une valeur qui permet de distinguer individuellement les classes filles. |
(3) | proxy (optionnel) : Spécifie une classe ou une interface à utiliser pour le chargement tardif par proxies. |
(4) | lazy (optionnel) : Paraméter lazy="true" est équivalent à définir la classe elle-même comme étant son interface de proxy. |
Chaque classe fille peut déclarer ses propres propriétés persistantes et classes filles. Les propriétés <version> et <id> sont supposées être héritées de la classe racine. Chaque classe fille dans la hiérarchie doit définir une discriminator-value unique. Si aucune n'est spécifiée, le nom complet de la classe java est utilisé.
D'autre part, une classe fille qui est persistée dans sa propre table (stratégie de mapping table par sous-classe - table-per-subclass) est déclarée en utilisant un élément <joined-subclass>.
<joined-subclass name="NomDeClasse" (1) proxy="InterfaceDeProxy" (2) lazy="true|false" (3) dynamic-update="true|false" dynamic-insert="true|false"> <key .... > <property .... /> ..... </subclass>
(1) | name : Le nom complet de la classe fille. |
(2) | proxy (optionnel) : Spécifie une classe ou interface à utiliser pour le chargement tardif par proxy. |
(3) | lazy (optionnel) : Fixer la valeur à lazy="true" est équivalent à spécifier le nom de la classe comme étant l'interface de proxy. |
Il n'y a pas de colonne discriminante pour cette stratégie de mapping. Chaque classe fille doit, cependant, déclarer une colonne de table contenant l'identifiant de l'objet en utilisant l'élément <key>. Le mapping écrit en début de chapitre serait réécrit comme:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="CATS"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <property name="birthdate" type="date"/> <property name="color" not-null="true"/> <property name="sex" not-null="true"/> <property name="weight"/> <many-to-one name="mate"/> <set name="kittens"> <key column="MOTHER"/> <one-to-many class="Cat"/> </set> <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> <key column="CAT"/> <property name="name" type="string"/> </joined-subclass> </class> <class name="eg.Dog"> <!-- le mapping de Dog pourrait être ici --> </class> </hibernate-mapping>
Supposez que votre application possède deux classes persistantes avec le même nom et que vous ne souhaitiez pas spécifier le nom qualifié (package) dans les requêtes Hibernate. Les classes peuvent être importées explicitement, plutôt que de compter sur auto-import="true". Vous pouvez même importer les classes qui ne sont pas explicitement mappées.
<import class="java.lang.Object" rename="Universe"/>
<import class="NomDeClasse" (1) rename="Alias" (2) />
(1) | class : Le nom complet de n'importe quelle classe. |
(2) | rename (optionnel - par défaut = le nom de la classe sans son package) : Un nom pouvant servir dans le langage de requête. |
Pour comprendre le comportement des différents objets (au sens java), dans le contexte d'un service de persistance, nous devons les séparer en deux groupes :
Une entité existe indépendamment de n'importe quel objet contenant une référence à l'entité. Ceci est contradictoire avec le modèle java habituel où un objet non référencé est un candidat pour le garbage collector. Les entités peuvent être explicitement sauvées et effacées (à l'exception que la sauvegarde et l'effacement peuvent être fait en cascade d'un objet parent vers ses enfants). C'est différent du modèle ODMG de persistance par atteignabilité - et correspond plus généralement à la façon d'utiliser les objets dans les grands systèmes. Les entités supportent les références partagées et circulaires. Elles peuvent aussi être versionnées.
Un état persistant d'une entité est constitué de références vers d'autres entités et instances de types valeur. Les valeurs sont des types primitifs, des collections, des composants et certains objets immuables. Contrairement aux entités, les valeurs (spécialement les collections et les composants) sont persistées et supprimées par atteignabilité. Puisque les objets de type valeur (et primitifs) sont persistés et effacés avec les entités qui les contiennent, ils ne peuvent pas être versionnés indépendamment. Les valeurs n'ont pas d'identifiant indépendant, elles ne peuvent donc pas être partagées entre deux entités ou collections.
Tous les types Hibernate, à l'exception des collections, supportent la sémantique null.
Jusqu'à présent, nous avons utilisé le terme "classes persistantes" pour faire référence aux entités. Nous allons continuer de le faire. Cependant, dans l'absolu, toutes les classes persistantes définies par un utilisateur, et ayant un état persistant, ne sont pas des entités. Un composant est une classe définie par l'utilisateur avec la sémantique d'une valeur.
Les types basiques peuvent etre grossièrement séparés en
Les types effectuant le mapping entre des types primitifs Java (ou leur classes d'encapsulation) et les types de colonnes SQL appropriés (spécifique au vendeur). boolean, yes_no et true_false sont des encodages possibles pour les boolean Java ou java.lang.Boolean, sa classe encapsulante.
Un type effectuant le mapping entre java.lang.String et VARCHAR (ou VARCHAR2 pour Oracle).
Des types effectuant le mapping entre java.util.Date (et ses classes filles) et les types SQL DATE, TIME et TIMESTAMP (ou équivalent).
Des types effectuant le mapping entre java.util.Calendar et les types SQL TIMESTAMP et DATE (ou équivalent).
Un type effectuant le mapping entr java.math.BigDecimal et NUMERIC (ou NUMBER pour Oracle).
Des types effetuant le mapping entre d'une part java.util.Locale, java.util.TimeZone et java.util.Currency et d'autre part VARCHAR (ou VARCHAR2 pour Oracle). Les instances de Locale et Currency sont mappées à leurs codes ISO. Les instances de TimeZone sont mappées à leur ID.
Un type effectuant le mapping entre java.lang.Class et VARCHAR (ou VARCHAR2 pour Oracle). Une Class est mappée à son nom entièrement qualifié.
Mappe un tableau de byte vers un type binaire SQL approprié.
Mappe de longues chaînes Java vers un type SQL CLOB ou TEXT.
Mappe les types java sérialisables vers un type binaire SQL approprié. Vous pouvez aussi indiquer le type Hibernate serializable avec le nom de la classe java sérialisable ou d'une interface qui ne fait ni référence à un type basique ni n'implémente PersistentEnum.
Mappe les types de classes JDBC java.sql.Clob et java.sql.Blob. Ces types peuvent être inopportun pour certaines applications, dans la mesure où les objets blob et clob ne peuvent être réutilisés en dehors d'une transaction (de plus, le support des drivers est plutôt inégal et imparfait).
Les identifiants des entités et collections peuvent être de tout type basique excepté binary, blob and clob (Les identifiants composés sont aussi admis, voir plus loin).
Les types de valeurs basiques ont des contantes Type correspondant dans net.sf.hibernate.Hibernate. Par exemple, Hibernate.STRING représente le type string.
Un type enum est un concept java classique où une classe contient un (petit) nombre constant d'instances immuables. Vous pouvez créer un type enum en implémentant net.sf.hibernate.PersistentEnum, définissant les opérations toInt() et fromInt() :
package eg; import net.sf.hibernate.PersistentEnum; public class Color implements PersistentEnum { private final int code; private Color(int code) { this.code = code; } public static final Color TABBY = new Color(0); public static final Color GINGER = new Color(1); public static final Color BLACK = new Color(2); public int toInt() { return code; } public static Color fromInt(int code) { switch (code) { case 0: return TABBY; case 1: return GINGER; case 2: return BLACK; default: throw new RuntimeException("Unknown color code"); } } }
Le nom du type Hibernate est simplement le nom de la classe énumérée, dans le cas présent eg.Color.
Il est relativement facile pour les développeurs de créer leurs propres types de valeurs. Par exemple, vous voulez persister des propriétés du type java.lang.BigInteger vers des colonnes VARCHAR. Hibernate ne fournit pas nativement un type pour cela. Les types personnalisés ne sont pas restreints à mapper une propriété (ou un élément de collection) à une simple colonne de table. Vous pouvez, par exemple, avoir une propriété Java getName()/setName() de type java.lang.String qui est persistée dans les colonnes FIRST_NAME, INITIAL, SURNAME.
Pour implémenter un type personnalisé, implémentez soit net.sf.hibernate.UserType, ou net.sf.hibernate.CompositeUserType et déclarer, dans les propriétés l'utilisant, le nom complet (avec package) de la classe dans l'élement type. Voir net.sf.hibernate.test.DoubleStringType pour comprendre ce qu'il est possible de faire.
<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType"> <column name="first_string"/> <column name="second_string"/> </property>
Notez l'utilisation des tags <column> pour mapper vers plusieurs colonnes.
Hibernate fournit un large éventail de types natifs et supporte les composants, vous ne devrez avoir besoin d'un type personnalisé que dans de rares cas. Néanmoins, il est bon d'utiliser les types personnalisés pour des classes (non-entité) qui reviennent souvent dans votre application. Par exemple une classe MonetoryAmount est un bon candidat pour un CompositeUserType, puisqu'elle pourrait facilement être mappée comme composant. Un des arguments en faveur de ce choix est l'abstraction. Avec les types personnalisés vos documents de mappings n'auraient pas à être modifiés lors de possibles modifications sur la définition des valeurs monétaires.
Il y a un dernier type de mapping de propriété. L'élément <any> définit une association polymorphique vers des classes de plusieurs tables. Ce type de mapping demande toujours plus d'une colonne. La première colonne contient le type de l'entité associée. Les colonnes restantes contiennent l'identifiant. Il est impossible de spécifier une contrainte de clé étrangère pour ce type d'association, il ne faut donc pas retenir cette option comme un moyen usuel de mapper des associations polymorphiques. Vous ne devez utiliser ceci que dans des cas très spécifiques (audit logs, données de session utilisateur, etc).
<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType"> <column name="table_name"/> <column name="id"/> </any>
L'attribut meta-type laisse l'application spécifier un type personnalisé qui mappe les valeurs des colonnes de base de données à des classes persistances qui ont comme type de propriété d'identifiant le type spécifié par id-type. Si le meta-type retourne une instance de java.lang.Class, rien d'autre n'est requis. Mais si meta-id fait référence à un type basique comme string ou character, vous devez spécifier explicitement le mapping entre les valeurs et les classes.
<any name="anyEntity" id-type="long" meta-type="string"> <meta-value value="TBL_ANIMAL" class="Animal"/> <meta-value value="TBL_HUMAN" class="Human"/> <meta-value value="TBL_ALIEN" class="Alien"/> <column name="table_name"/> <column name="id"/> </any>
<any name="nomDepropriete" (1) id-type="nomdutypedidentifiant" (2) meta-type="nomdumetatype" (3) cascade="none|all|save-update" (4) access="field|property|NomDeClasse" (5) > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any>
(1) | name : le nom de la propriété. |
(2) | id-type : le type de l'identifiant. |
(3) | meta-type (optionnel - par défaut = class) : un type qui mappe java.lang.Class à une seule colonne, ou un type admis pour un mapping de discrimination. |
(4) | cascade (optionnel - par défaut = none) : le style de cascade. |
(5) | access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété. |
L'ancien type object qui avait un rôle similaire dans Hibernate 1.2 est toujours supporté, mais est désormais semi-déprécié.
Vous pouvez forcer Hibernate à placer, dans le SQL généré, les noms des tables et des colonnes entre guillemets en incluant la table ou le nom de colonne entre guillemets simples dans le document de configuration. Hibernate utilisera la syntaxe appropriée dans le SQL généré en fonction du Dialect (généralement des guillemets doubles, mais des crochets pour SQL Server et des guillemets inversés pour MySQL).
<class name="LineItem" table="`Line Item`"> <id name="id" column="`Item Id`"/><generator class="assigned"/></id> <property name="itemNumber" column="`Item #`"/> ... </class>
Il est possible de définir les mappings subclass et joined-subclass dans des documents de mappings séparés, directement en dessous de hibernate-mapping. Ceci vous permet d'étendre une hiérarchie de classes en ajoutant simplement un fichier de mapping. Vous devez spécifier l'attribut extends du mapping de la classe fille, nommant une classe mère déjà définie. L'utilisation de cette option rend l'ordre des documents de mapping important !
<hibernate-mapping> <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>