Les mappings Objet/relationnel sont généralement définis dans un document XML. Le document de mapping est conçu pour être lisible et éditable à la main. Le langage de mapping est Java-centrique, c'est à dire que les mappings sont construits à partir des déclarations des classes persistantes et non des déclarations des tables.
Remarquez que même si beaucoup d'utilisateurs de Hibernate préfèrent écrire les fichiers de mappings à la main, plusieurs outils existent pour générer ce document, notamment XDoclet, Middlegen et AndroMDA.
Démarrons avec un exemple de mapping :
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="cats" discriminator-value="C"> <id name="id"> <generator class="native"/> </id> <discriminator column="subclass" type="character"/> <property name="weight"/> <property name="birthdate" type="date" not-null="true" update="false"/> <property name="color" type="eg.types.ColorUserType" not-null="true" update="false"/> <property name="sex" not-null="true" update="false"/> <property name="litterId" column="litterId" update="false"/> <many-to-one name="mother" column="mother_id" update="false"/> <set name="kittens" inverse="true" order-by="litter_id"> <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"> <!-- mapping for Dog could go here --> </class> </hibernate-mapping>
Etudions le contenu du document de mapping. Nous décrirons uniquement les éléments et attributs du document utilisés par Hibernate
à l'exécution. Le document de mapping contient aussi des attributs et éléments optionnels qui agissent sur le schéma de base
de données exporté par l'outil de génération de schéma. (Par exemple l'attribut not-null
.)
Tous les mappings XML devraient utiliser le doctype indiqué. Ce fichier est présent à l'URL ci-dessus, dans le répertoire
hibernate-x.x.x/src/org/hibernate
ou dans hibernate3.jar
. Hibernate va toujours chercher la DTD dans son classpath en premier lieu. Si vous constatez des recherches de la DTD sur
Internet, vérifiez votre déclaration de DTD par rapport au contenu de votre classpath.
As mentioned previously, Hibernate will first attempt to resolve DTDs in its classpath. The manner in which it does this is
by registering a custom org.xml.sax.EntityResolver
implementation with the SAXReader it uses to read in the xml files. This custom EntityResolver
recognizes two different systemId namespaces.
a hibernate namespace
is recognized whenever the resolver encounteres a systemId starting with http://hibernate.sourceforge.net/
; the resolver attempts to resolve these entities via the classlaoder which loaded the Hibernate classes.
a user namespace
is recognized whenever the resolver encounteres a systemId using a classpath://
URL protocol; the resolver will attempt to resolve these entities via (1) the current thread context classloader and (2)
the classloader which loaded the Hibernate classes.
An example of utilizing user namespacing:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [ <!ENTITY types SYSTEM "classpath://your/domain/types.xml"> ]> <hibernate-mapping package="your.domain"> <class name="MyEntity"> <id name="id" type="my-custom-id-type"> ... </id> <class> &types; </hibernate-mapping>
Where types.xml
is a resource in the your.domain
package and contains a custom typedef.
Cet élément a plusieurs attributs optionnels. Les attributs schema
et catalog
indiquent que les tables référencées par ce mapping appartiennent au schéma nommé et/ou au catalogue. S'ils sont spécifiés,
les noms de tables seront qualifiés par les noms de schéma et catalogue. L'attribut default-cascade
indique quel type de cascade sera utlisé par défaut pour les propriétés et collections qui ne précisent pas l'attribut cascade
. L'attribut auto-import
nous permet d'utiliser par défaut des noms de classes non qualifiés dans le langage de requête.
<hibernate-mapping schema="schemaName" (1) catalog="catalogName" (2) default-cascade="cascade_style" (3) default-access="field|property|ClassName" (4) default-lazy="true|false" (5) auto-import="true|false" (6) package="package.name" (7) />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Si deux classes possèdent le même nom de classe (non qualifié), vous devez indiquer auto-import="false"
. Hibernate lancera une exception si vous essayez d'assigner à deux classes le même nom importé.
Notez que l'élément hibernate-mapping
vous permet d'imbriquer plusieurs mappings de <class>
persistantes, comme dans l'exemple ci-dessus. Cependant la bonne pratique (ce qui est attendu par certains outils) est de
mapper une seule classe (ou une seule hiérarchie de classes) par fichier de mapping et de nommer ce fichier d'après le nom
de la superclasse, par exemple Cat.hbm.xml
, Dog.hbm.xml
, ou en cas d'héritage, Animal.hbm.xml
.
Déclarez une classe persistante avec l'élément class
:
<class name="ClassName" (1) table="tableName" (2) discriminator-value="discriminator_value" (3) mutable="true|false" (4) schema="owner" (5) catalog="catalog" (6) proxy="ProxyInterface" (7) dynamic-update="true|false" (8) dynamic-insert="true|false" (9) select-before-update="true|false" (10) polymorphism="implicit|explicit" (11) where="arbitrary sql where condition" (12) persister="PersisterClass" (13) batch-size="N" (14) optimistic-lock="none|version|dirty|all" (15) lazy="true|false" (16) entity-name="EntityName" (17) check="arbitrary sql check condition" (18) rowid="rowid" (19) subselect="SQL expression" (20) abstract="true|false" (21) node="element-name" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Il est tout à fait possible d'utiliser une interface comme nom de classe persistante. Vous devez alors déclarer les classes
implémentant cette interface en utilisant l'élément <subclass>
. Vous pouvez faire persister toute classe interne static. Vous devez alors spécifier le nom de la classe par la notation habituelle des classes internes c'est à dire eg.Foo$Bar
.
Les classes immuables, mutable="false"
, ne peuvent pas être modifiées ou supprimées par l'application. Cela permet à Hibernate de faire quelques optimisations mineures
sur les performances.
L'attribut optionnnel proxy
permet les intialisations différées des instances persistantes de la classe. Hibernate retournera initialement des proxies
CGLIB qui implémentent l'interface nommée. Le véritable objet persistant ne sera chargé que lorsque une méthode du proxy sera
appelée. Voir plus bas le paragraphe abordant les proxies et le chargement différé (lazy initialization).
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 chacune
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 utilisée pour la classe. Vous pouvez, par exemple, spécifier votre propre sous-classe
de org.hibernate.persister.EntityPersister
ou vous pourriez aussi créer une nouvelle implémentation de l'interface org.hibernate.persister.ClassPersister
qui proposerait une persistance via, par exemple, des appels de procédures stockées, de la sérialisation vers des fichiers
plats ou un annuaire LDAP. Voir org.hibernate.test.CustomPersister
pour un exemple simple (d'une "persistance" vers une Hashtable
).
Notez que les paramètres dynamic-update
et dynamic-insert
ne sont pas hérités par les sous-classes et peuvent donc être spécifiés pour les éléments <subclass>
ou <joined-subclass>
Ces paramètres peuvent améliorer les performances dans certains cas, mais peuvent aussi les amoindrir. A utiliser en connaissance
de causes.
L'utilisation de select-before-update
va généralement faire baisser les performances. Ce paramètre est pratique pour prévenir l'appel inutile d'un trigger sur
modification quand on réattache un graphe d'instances à une Session
.
Si vous utilisez le dynamic-update
, les différentes stratégies de verrouillage optimiste (optimistic locking) sont les suivantes:
version
vérifie les colonnes version/timestamp
all
vérifie toutes les colonnes
dirty
vérifie les colonnes modifiées, permettant des updates concurrents
none
pas de verrouillage optimiste
Nous encourageons très fortement l'utilisation de colonnes de version/timestamp pour le verrouillage optimiste avec Hibernate. C'est la meilleure
stratégie en regard des performances et la seule qui gère correctement les modifications sur les objets détachés (c'est à
dire lorsqu'on utilise Session.merge()
).
Il n'y a pas de différence entre table et vue pour le mapping Hibernate, tant que c'est transparent au niveau base de données (remarquez que certaines BDD ne supportent pas les vues correctement, notamment pour les updates). Vous rencontrerez peut-être des cas où vous souhaitez utiliser une vue mais ne pouvez pas en créer sur votre BDD (par exemple à cause de schémas anciens et figés). Dans ces cas, vous pouvez mapper une entité immuable en lecture seule sur un sous-select SQL donné:
<class name="Summary"> <subselect> select item.name, max(bid.amount), count(*) from item join bid on bid.item_id = item.id group by item.name </subselect> <synchronize table="item"/> <synchronize table="bid"/> <id name="name"/> ... </class>
Déclarez les tables à synchroniser avec cette entité pour assurer que le flush automatique se produise correctement, et pour
que les requêtes sur l'entité dérivée ne renvoient pas des données périmées. Le litéral <subselect>
est disponible comme attribut ou comme élément de mapping.
Les classes mappées doivent déclarer la clef primaire de la table en base de données. La plupart des classes auront aussi une propriété de type javabean
présentant l'identifiant unique d'une instance. L'élément <id>
sert à définir le mapping entre cette propriété et la clef primaire en base.
<id name="propertyName" (1) type="typename" (2) column="column_name" (3) unsaved-value="null|any|none|undefined|id_value" (4) access="field|property|ClassName"> (5) node="element-name|@attribute-name|element/@attribute|." <generator class="generatorClass"/> </id>
|
|
|
|
|
|
|
|
|
|
Si l'attribut name
est absent, Hibernate considère que la classe ne possède pas de propriété identifiant.
L'attribut unsaved-value
est important ! Si l'identifiant de votre classe n'a pas une valeur par défaut compatible avec le comportement standard de
Java (zéro ou null), vous devez alors préciser la valeur par défaut.
La déclaration alternative <composite-id>
permet l'acccès aux données d'anciens systèmes qui utilisent des clefs composées. Son utilisation est fortement déconseillée
pour d'autres cas.
L'élément fils <generator>
nomme une classe Java utilisée pour générer les identifiants uniques pour les instances des classes persistantes. Si des
paramètres sont requis pour configurer ou initialiser l'instance du générateur, ils sont passés en utilisant l'élément <param>
.
<id name="id" type="long" column="cat_id"> <generator class="org.hibernate.id.TableHiLoGenerator"> <param name="table">uid_table</param> <param name="column">next_hi_value_column</param> </generator> </id>
All generators implement the interface org.hibernate.id.IdentifierGenerator
. This is a very simple interface; some applications may choose to provide their own specialized implementations. However,
Hibernate provides a range of built-in implementations. There are shortcut names for the built-in generators:
increment
Génère des identifiants de type long
, short
ou int
qui ne sont uniques que si aucun autre processus n'insère de données dans la même table. Ne pas utiliser en environnement clusterisé.
identity
Utilisation de la colonne identity de DB2, MySQL, MS SQL Server, Sybase et HypersonicSQL. L'identifiant renvoyé est de type
long
, short
ou int
.
sequence
Utilisation des séquences dans DB2, PostgreSQL, Oracle, SAP DB, McKoi ou d'un générateur dans Interbase. L'identifiant renvoyé
est de type long
, short
ou int
hilo
Utilise un algorithme hi/lo pour générer de façon efficace des identifiants de type long
, short
ou int
, en prenant comme source de valeur "hi" une table et une colonne (par défaut hibernate_unique_key
et next_hi
respectivement). L'algorithme hi/lo génère des identifiants uniques pour une base de données particulière seulement.
seqhilo
Utilise un algorithme hi/lo pour générer efficacement des identifiants de type long
, short
ou int
, étant donné un nom de séquence en base.
uuid
Utilise un algorithme de type UUID 128 bits pour générer des identifiants de type string, unique au sein d'un réseau (l'adresse IP est utilisée). Le UUID en codé en une chaîne de nombre héxadécimaux de longueur 32.
guid
Utilise une chaîne GUID générée par la base pour MS SQL Server et MySQL.
native
Choisit identity
, sequence
ou hilo
selon les possibilités offertes par la base de données sous-jacente.
assigned
Laisse l'application affecter un identifiant à l'objet avant que la métode save()
soit appelée. Il s'agit de la stratégie par défaut si aucun <generator>
n'est spécifié.
select
Récupère une clef primaire assignée par un trigger en sélectionnant la ligne par une clef unique quelconque.
foreign
Utilise l'identifiant d'un objet associé. Habituellement utilisé en conjonction avec une association <one-to-one>
sur la clef primaire.
sequence-identity
a specialized sequence generation strategy which utilizes a database sequence for the actual value generation, but combines this with JDBC3 getGeneratedKeys to actually return the generated identifier value as part of the insert statement execution. This strategy is only known to be supported on Oracle 10g drivers targetted for JDK 1.4. Note comments on these insert statements are disabled due to a bug in the Oracle drivers.
Les générateurs hilo
et seqhilo
proposent deux implémentations alternatives de l'algorithme hi/lo, une approche largement utilisée pour générer des identifiants.
La première implémentation nécessite une table "spéciale" en base pour héberger la prochaine valeur "hi" disponible. La seconde
utilise une séquence de type Oracle (quand la base sous-jacente le propose).
<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 pas utilisez hilo
quand vous apportez votre propre Connection
à Hibernate. Quand Hibernate utilise une datasource du serveur d'application pour obtenir des connexions inscrites avec JTA,
vous devez correctement configurer hibernate.transaction.manager_lookup_class
.
Le contenu du UUID est : adresse IP, date de démarrage de la JVM (précis au quart de seconde), l'heure système et un compteur (unique au sein de la JVM). Il n'est pas possible d'obtenir l'adresse MAC ou une adresse mémoire à partir de Java, c'est donc le mieux que l'on puisse faire sans utiliser JNI.
Pour les bases qui implémentent les colonnes "identité" (DB2, MySQL, Sybase, MS SQL), vous pouvez utiliser la génération de
clef par identity
. Pour les bases qui implémentent les séquences (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) vous pouvez utiliser la
génération de clef par sequence
. Ces deux méthodes nécessitent deux requêtes SQL pour insérer un objet.
<id name="id" type="long" column="person_id"> <generator class="sequence"> <param name="sequence">person_id_sequence</param> </generator> </id>
<id name="id" type="long" column="person_id" unsaved-value="0"> <generator class="identity"/> </id>
Pour le développement multi-plateformes, la stratégie native
choisira entre les méthodes identity
, sequence
et hilo
, selon les possibilités offertes par la base sous-jacente.
Si vous souhaitez que l'application assigne des identifiants (par opposition à la génération par Hibernate), vous pouvez utiliser
le générateur assigned
. Ce générateur spécial utilisera une valeur d'identifiant déjà utilisé par la propriété identifiant l'objet. Ce générateur
est utilisé quand la clef primaire est une clef naturelle plutôt qu'une clef secondaire. C'est le comportement par défaut
si vous ne précisez pas d'élément <generator>
.
Choisir le générateur assigned
fait utiliser unsaved-value="undefined"
par Hibernate, le forçant à interroger la base pour déterminer si l'instance est transiente ou détachée, à moins d'utiliser
une propriété version ou timestamp, ou alors de définir Interceptor.isUnsaved()
.
Pour les schémas de base hérités d'anciens systèmes uniquement (Hibernate ne génère pas de DDL avec des triggers)
<id name="id" type="long" column="person_id"> <generator class="select"> <param name="key">socialSecurityNumber</param> </generator> </id>
Dans l'exemple ci-dessus, socialSecurityNumber
a une valeur unique définie par la classe en tant que clef naturelle et person_id
est une clef secondaire dont la valeur est générée par trigger.
Starting with release 3.2.3, there are 2 new generators which represent a re-thinking of 2 different aspects of identifier generation. The first aspect is database portability; the second is optimization (not having to query the database for every request for a new identifier value). These two new generators are intended to take the place of some of the named generators described above (starting in 3.3.x); however, they are included in the current releases and can be referenced by FQN.
The first of these new generators is org.hibernate.id.enhanced.SequenceStyleGenerator
which is intended firstly as a replacement for the sequence
generator and secondly as a better portability generator than native
(because native
(generally) chooses between identity
and sequence
which have largely different semantics which can cause subtle isssues in applications eyeing portability). org.hibernate.id.enhanced.SequenceStyleGenerator
however achieves portability in a different manner. It chooses between using a table or a sequence in the database to store
its incrementing values depending on the capabilities of the dialect being used. The difference between this and native
is that table-based and sequence-based storage have the same exact semantic (in fact sequences are exactly what Hibernate
tries to emmulate with its table-based generators). This generator has a number of configuration parameters:
sequence_name
(optional, defaults to hibernate_sequence
): The name of the sequence (or table) to be used.
initial_value
(optional, defaults to 1
): The initial value to be retrieved from the sequence/table. In sequence creation terms, this is analogous to the clause
typical named "STARTS WITH".
increment_size
(optional, defaults to 1
): The value by which subsequent calls to the sequence/table should differ. In sequence creation terms, this is analogous
to the clause typical named "INCREMENT BY".
force_table_use
(optional, defaults to false
): Should we force the use of a table as the backing structure even though the dialect might support sequence?
value_column
(optional, defaults to next_val
): Only relevant for table structures! The name of the column on the table which is used to hold the value.
optimizer
(optional, defaults to none
): See Section 5.1.6, « Identifier generator optimization »
The second of these new generators is org.hibernate.id.enhanced.TableGenerator
which is intended firstly as a replacement for the table
generator (although it actually functions much more like org.hibernate.id.MultipleHiLoPerTableGenerator
) and secondly as a re-implementation of org.hibernate.id.MultipleHiLoPerTableGenerator
utilizing the notion of pluggable optimiziers. Essentially this generator defines a table capable of holding a number of
different increment values simultaneously by using multiple distinctly keyed rows. This generator has a number of configuration
parameters:
table_name
(optional, defaults to hibernate_sequences
): The name of the table to be used.
value_column_name
(optional, defaults to next_val
): The name of the column on the table which is used to hold the value.
segment_column_name
(optional, defaults to sequence_name
): The name of the column on the table which is used to hold the "segement key". This is the value which distinctly identifies
which increment value to use.
segment_value
(optional, defaults to default
): The "segment key" value for the segment from which we want to pull increment values for this generator.
segment_value_length
(optional, defaults to 255
): Used for schema generation; the column size to create this segment key column.
initial_value
(optional, defaults to 1
): The initial value to be retrieved from the table.
increment_size
(optional, defaults to 1
): The value by which subsequent calls to the table should differ.
optimizer
(optional, defaults to ): See Section 5.1.6, « Identifier generator optimization »
For identifier generators which store values in the database, it is inefficient for them to hit the database on each and every call to generate a new identifier value. Instead, you'd ideally want to group a bunch of them in memory and only hit the database when you have exhausted your in-memory value group. This is the role of the pluggable optimizers. Currently only the two enhanced generators (Section 5.1.5, « Enhanced identifier generators » support this notion.
none
(generally this is the default if no optimizer was specified): This says to not perform any optimizations, and hit the database
each and every request.
hilo
: applies a hi/lo algorithm around the database retrieved values. The values from the database for this optimizer are expected
to be sequential. The values retrieved from the database structure for this optimizer indicates the "group number"; the increment_size
is multiplied by that value in memory to define a group "hi value".
pooled
: like was discussed for hilo
, this optimizers attempts to minimize the number of hits to the database. Here, however, we simply store the starting value
for the "next group" into the database structure rather than a sequential value in combination with an in-memory grouping
algorithm. increment_size
here refers to the values coming from the database.
<composite-id name="propertyName" class="ClassName" mapped="true|false" access="field|property|ClassName"> node="element-name|." <key-property name="propertyName" type="typename" column="column_name"/> <key-many-to-one name="propertyName class="ClassName" column="column_name"/> ...... </composite-id>
Pour une table avec clef composée, vous pouvez mapper plusieurs attributs de la classe comme propriétés identifiantes. L'élement
<composite-id>
accepte les mappings de propriétés <key-property>
et les mappings <key-many-to-one>
comme fils.
<composite-id> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id>
Vos classes persistantes doivent surcharger les méthodes equals()
et hashCode()
pour implémenter l'égalité d'identifiant composé. Elles doivent aussi implenter l'interface Serializable
.
Malheureusement cette approche sur les identifiants composés signifie qu'un objet persistant est son propre identifiant. Il
n'y a pas d'autre moyen pratique de manipuler l'objet que par l'objet lui-même. Vous devez instancier une instance de la classe
persistante elle-même et peupler ses attributs identifiants avant de pouvoir appeler la méthode load()
pour charger son état persistant associé à une clef composée. Nous appelons cette approche "identifiant composé embarqué" et ne la recommandons pas pour des applications complexes.
Une seconde approche, appelée identifiant composé mappé, consiste à encapsuler les propriétés identifiantes (celles contenues dans <composite-id>
) dans une classe particulière.
<composite-id class="MedicareId" mapped="true"> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id>
Dans cet exemple, la classe d'identifiant composée,MedicareId
et la classe mappée elle-même, possèdent les propriétés medicareNumber
et dependent
. La classe identifiante doit redéfinir equals()
et hashCode()
et implémenter Serializable
. Le désavantage de cette approche est la duplication du code.
Les attributs suivants servent à configurer un identifiant composé mappé :
mapped
(optionnel, défaut à false
) : indique qu'un identifiant composé mappé est utilisé, et que les propriétés contenues font référence aux deux classes (celle
mappée et la classe identifiante).
class
(optionnel, mais requis pour un identifiant composé mappé) : La classe composant utilisée comme identifiant composé.
Nous décrirons une troisième approche beaucoup plus efficace ou l'identifiant composé est implémenté comme une classe composant dans Section 8.4, « Utiliser un composant comme identifiant ». Les attributs décrits ci dessous, ne s'appliquent que pour cette dernière approche :
name
(optionnel, requis pour cette approche) : une propriété de type composant qui contient l'identifiant composé (voir chapitre
9).
access
(optional - defaults to property
): The strategy Hibernate should use for accessing the property value.
class
(optionnel - défaut au type de la propriété déterminé par réflexion) : La classe composant utilisée comme identifiant (voir
prochaine section).
Cette dernière approche est celle que nous recommandons pour toutes vos applications.
L'élément <discriminator>
est nécessaire pour la persistance polymorphique qui utilise la stratégie de mapping de table par hiérarchie de classe. La
colonne discriminante contient une valeur marqueur qui permet à la couche de persistance de savoir quelle sous-classe instancier
pour une ligne particulière de table en base. Un nombre restreint de types peuvent être utilisés : string
, character
, integer
, byte
, short
, boolean
, yes_no
, true_false
.
<discriminator column="discriminator_column" (1) type="discriminator_type" (2) force="true|false" (3) insert="true|false" (4) formula="arbitrary sql expression" (5) />
|
|
|
|
|
|
|
|
|
|
Les véritables valeurs de la colonne discriminante sont spécifiées par l'attribut discriminator-value
des éléments <class>
et <subclass>
.
L'attribut force
n'est utile que si la table contient des lignes avec des valeurs "extra" discriminantes qui ne sont pas mappées à une classe
persistante. Ce ne sera généralement pas le cas.
En utilisant l'attribut formula
vous pouvez déclarer une expression SQL arbitraire qui sera utilisée pour évaluer le type d'une ligne :
<discriminator formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" type="integer"/>
L'élément <version>
est optionnel et indique que la table contient des données versionnées. C'est particulièrement utile si vous avez l'intention
d'utiliser des transactions longues (voir plus-bas).
<version column="version_column" (1) name="propertyName" (2) type="typename" (3) access="field|property|ClassName" (4) unsaved-value="null|negative|undefined" (5) generated="never|always" (6) insert="true|false" (7) node="element-name|@attribute-name|element/@attribute|." />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Les numéros de version doivent avoir les types Hibernate long
, integer
, short
, timestamp
ou calendar
.
Une propriété de version ou un timestamp ne doit jamais être null pour une instance détachée, ainsi Hibernate pourra détecter
toute instance ayant une version ou un timestamp null comme transient, quelles que soient les stratégies unsaved-value
spécifiées. Déclarer un numéro de version ou un timestamp "nullable" est un moyen pratique d'éviter tout problème avec les réattachements
transitifs dans Hibernate, particulièrement utile pour ceux qui utilisent des identifiants assignés ou des clefs composées
!
L'élément optionnel <timestamp>
indique que la table contient des données horodatées (timestamp). Cela sert d'alternative à l'utilisation de numéros de version.
Les timestamps (ou horodatage) sont par nature une implémentation moins fiable pour l'optimistic locking. Cependant, l'application
peut parfois utiliser l'horodatage à d'autres fins.
<timestamp column="timestamp_column" (1) name="propertyName" (2) access="field|property|ClassName" (3) unsaved-value="null|undefined" (4) source="vm|db" (5) generated="never|always" (6) node="element-name|@attribute-name|element/@attribute|." />
|
|
|
|
|
|
|
|
|
|
|
|
Notez que <timestamp>
est équivalent à <version type="timestamp">
.
L'élément <property>
déclare une propriété de la classe au sens JavaBean.
<property name="propertyName" (1) column="column_name" (2) type="typename" (3) update="true|false" (4) insert="true|false" (4) formula="arbitrary SQL expression" (5) access="field|property|ClassName" (6) lazy="true|false" (7) unique="true|false" (8) not-null="true|false" (9) optimistic-lock="true|false" (10) generated="never|insert|always" (11) node="element-name|@attribute-name|element/@attribute|." index="index_name" unique_key="unique_key_id" length="L" precision="P" scale="S" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typename peut être:
Nom d'un type basique Hibernate (ex: integer, string, character, date, timestamp, float, binary, serializable, object, blob
).
Nom d'une classe Java avec un type basique par défaut (ex: int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob
).
Nom d'une classe Java sérialisable.
Nom d'une classe ayant un type spécifique (ex: com.illflow.type.MyCustomType
).
Si vous n'indiquez pas un type, Hibernate utlisera la réflexion sur le nom de la propriété pour tenter de trouver le type
Hibernate correct. Hibernate essayera d'interprêter le nom de la classe retournée par le getter de la propriété en utilisant
les régles 2, 3, 4 dans cet ordre. Cependant, ce n'est pas toujours suffisant. Dans certains cas vous aurez encore besoin
de l'attribut type
(Par exemple, pour distinguer Hibernate.DATE
et Hibernate.TIMESTAMP
, ou pour préciser un type spécifique).
L'attribut access
permet de contrôler comment Hibernate accèdera à la propriété à l'exécution. Par défaut, Hibernate utilisera les méthodes
set/get. Si vous indiquez access="field"
, Hibernate ignorera les getter/setter et accèdera à la propriété directement en utilisant la réflexion. Vous pouvez spécifier
votre propre stratégie d'accès aux propriété en donnant une classe qui implémente l'interface org.hibernate.property.PropertyAccessor
.
Une fonctionnalité particulièrement intéressante est les propriétés dérivées. Ces propriétés sont par définition en lecture
seule, la valeur de la propriété est calculée au chargement. Le calcul est déclaré comme une expression SQL, qui se traduit
par une sous-requête SELECT
dans la requête SQL qui charge une instance :
<property name="totalPrice" formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p WHERE li.productId = p.productId AND li.customerId = customerId AND li.orderNumber = orderNumber )"/>
Remarquez que vous pouvez référencer la propre table des entités en ne déclarant pas un alias sur une colonne particulière
(customerId
dans l'exemple donné). Notez aussi que vous pouvez utiliser le sous-élément de mapping <formula>
plutôt que d'utiliser l'attribut si vous le souhaitez.
Une association ordinaire vers une autre classe persistante est déclarée en utilisant un élément many-to-one
. Le modèle relationnel est une association de type many-to-one : une clef étrangère dans une table référence la ou les clef(s)
primaire(s) dans la table cible.
<many-to-one name="propertyName" (1) column="column_name" (2) class="ClassName" (3) cascade="cascade_style" (4) fetch="join|select" (5) update="true|false" (6) insert="true|false" (6) property-ref="propertyNameFromAssociatedClass" (7) access="field|property|ClassName" (8) unique="true|false" (9) not-null="true|false" (10) optimistic-lock="true|false" (11) lazy="proxy|no-proxy|false" (12) not-found="ignore|exception" (13) entity-name="EntityName" (14) formula="arbitrary SQL expression" (15) node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" index="index_name" unique_key="unique_key_id" foreign-key="foreign_key_name" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Donner une valeur significative à l'attribut cascade
autre que none
propagera certaines opérations à l'objet associé. Les valeurs significatives sont les noms des opérations Hibernate basiques,
persist, merge, delete, save-update, evict, replicate, lock, refresh
, ainsi que les valeurs spéciales delete-orphan
et all
et des combinaisons de noms d'opérations séparées par des virgules, comme par exemple cascade="persist,merge,evict"
ou cascade="all,delete-orphan"
. Voir Section 10.11, « Persistance transitive » pour une explication complète. Notez que les assocations many-to-one et one-to-one ne supportent pas orphan delete.
Une déclaration many-to-one
typique est aussi simple que :
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
L'attribut property-ref
devrait être utilisé pour mapper seulement des données provenant d'un ancien système où les clefs étrangères font référence
à une clef unique de la table associée et qui n'est pas la clef primaire. C'est un cas de mauvaise conception relationnelle.
Par exemple, supposez que la classe Product
a un numéro de série unique qui n'est pas la clef primaire. (L'attribut unique
contrôle la génération DDL par Hibernate avec l'outil SchemaExport.)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
Ainsi le mapping pour OrderItem
peut utiliser :
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
bien que ce ne soit certainement pas encouragé.
Si la clef unique référencée comprend des propriétés multiples de l'entité associée, vous devez mapper ces propriétés à l'intérieur
d'un élément <properties>
.
one-to-one
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
name
: Le nom de la propriété.
<one-to-one name="propertyName" (1) class="ClassName" (2) cascade="cascade_style" (3) constrained="true|false" (4) fetch="join|select" (5) property-ref="propertyNameFromAssociatedClass" (6) access="field|property|ClassName" (7) formula="any SQL expression" (8) lazy="proxy|no-proxy|false" (9) entity-name="EntityName" (10) node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" foreign-key="foreign_key_name" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
associations par clef primaire
association par clef étrangère unique
Les associations par clef primaire ne nécessitent pas une colonne supplémentaire en table ; si deux lignes sont liés par l'association alors les deux lignes de la table partagent la même valeur de clef primaire. Donc si vous voulez que deux objets soient liés par une association par clef primaire, vous devez faire en sorte qu'on leur assigne la même valeur d'identifiant !
Pour une association par clef primaire, ajoutez les mappings suivants à Employee
et Person
, respectivement.
Maintenant, vous devez faire en sorte que les clefs primaires des lignes liées dans les tables PERSON et EMPLOYEE sont égales.
On utilise une stratégie Hibernate spéciale de génération d'identifiants appelée foreign
:
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Une instance fraîchement enregistrée de Person
se voit alors assignée la même valeur de clef primaire que l'instance de Employee
référencée par la propriété employee
de cette Person
.
<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>
Alternativement, une clef étrangère avec contrainte d'unicité de Employee
vers Person
peut être indiquée ainsi :
Et cette association peut être rendue bidirectionnelle en ajoutant ceci au mapping de Person
:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
natural-id
<one-to-one name="employee" class="Employee" property-ref="person"/>
<natural-id>
. Hibernate générera la clé unique nécessaire et les contraintes de non-nullité, et votre mapping s'auto-documentera.
<natural-id mutable="true|false"/> <property ... /> <many-to-one ... /> ...... </natural-id>
Nous vous recommandons fortement d'implémenter equals()
et hashCode()
pour comparer les clés naturelles de l'entité.
Ce mapping n'est pas destiné à être utilisé avec des entités qui ont des clés naturelles.
mutable
(optionel, par défaut à false
) : Par défaut, les identifiants naturels sont supposés être immuable (constants).
component, dynamic-component
name
: Nom de la propriété
<component name="propertyName" (1) class="className" (2) insert="true|false" (3) update="true|false" (4) access="field|property|ClassName" (5) lazy="true|false" (6) optimistic-lock="true|false" (7) unique="true|false" (8) node="element-name|." > <property ...../> <many-to-one .... /> ........ </component>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
L'élément <component>
permet de déclarer sous-élément <parent>
qui associe une propriété de la classe composant comme une référence arrière vers l'entité contenante.
L'élément <dynamic-component>
permet à une Map
d'être mappée comme un composant, quand les noms de la propriété font référence aux clefs de cette Map, voir Section 8.5, « Composant Dynamique ».
properties
<properties>
permet la définition d'un groupement logique nommé des propriétés d'une classe. L'utilisation la plus importante de cette
construction est la possibilité pour une combinaison de propriétés d'être la cible d'un property-ref
. C'est aussi un moyen pratique de définir une contrainte d'unicité multi-colonnes.
name
: Le nom logique d'un regroupement et non le véritable nom d'une propriété.
<properties name="logicalName" (1) insert="true|false" (2) update="true|false" (3) optimistic-lock="true|false" (4) unique="true|false" (5) > <property ...../> <many-to-one .... /> ........ </properties>
|
|
|
|
|
|
|
|
|
|
Alors nous pourrions avoir une association sur des données d'un ancien système (legacy) qui font référence à cette clef unique
de la table Person
au lieu de la clef primaire :
<class name="Person"> <id name="personNumber"/> ... <properties name="name" unique="true" update="false"> <property name="firstName"/> <property name="initial"/> <property name="lastName"/> </properties> </class>
Nous ne recommandons pas l'utilisation de ce genre de chose en dehors du contexte de mapping de données héritées d'anciens systèmes.
<many-to-one name="person" class="Person" property-ref="name"> <column name="firstName"/> <column name="initial"/> <column name="lastName"/> </many-to-one>
subclass
name
: Le nom complet de la sous-classe.
<subclass name="ClassName" (1) discriminator-value="discriminator_value" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" entity-name="EntityName" node="element-name" extends="SuperclassName"> <property .... /> ..... </subclass>
|
|
|
|
|
|
|
|
Pour plus d'infos sur le mapping d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
Une autre façon possible de faire est la suivante, chaque sous-classe peut être mappée vers sa propre table (stratégie de
mapping de type table-per-subclass). L'état hérité est récupéré en joignant la table de la super-classe. L'élément <joined-subclass>
est utilisé.
<joined-subclass name="ClassName" (1) table="tablename" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name"> <key .... > <property .... /> ..... </joined-subclass>
|
|
|
|
|
|
|
|
Aucune colonne discriminante n'est nécessaire pour cette stratégie de mapping. Cependant, chaque sous-classe doit déclarer
une colonne de table contenant l'objet identifiant qui utilise l'élément <key>
. Le mapping au début de ce chapitre serait ré-écrit ainsi :
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.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"> <!-- mapping for Dog could go here --> </class> </hibernate-mapping>
Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
Une troisième option est de seulement mapper vers des tables les classes concrètes d'une hiérarchie d'héritage, (stratégie
de type table-per-concrete-class) où chaque table définit tous les états persistants de la classe, y compris les états hérités.
Dans Hibernate il n'est absolument pas nécessaire de mapper explicitement de telles hiérarchies d'héritage. Vous pouvez simplement
mapper chaque classe avec une déclaration <class>
différente. Cependant, si vous souhaitez utiliser des associations polymorphiques (càd une association vers la superclasse
de la hiérarchie), vous devez utiliser le mapping <union-subclass>
.
<union-subclass name="ClassName" (1) table="tablename" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" abstract="true|false" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name"> <property .... /> ..... </union-subclass>
|
|
|
|
|
|
|
|
Aucune colonne discriminante ou colonne clef n'est requise pour cette stratégie de mapping.
Pour des informations sur les mappings d'héritage, voir Chapitre 9, Mapping d'héritage de classe.
En utilisant l'élément <join>
, il est possible de mapper des propriétés d'une classe sur plusieurs tables.
<join table="tablename" (1) schema="owner" (2) catalog="catalog" (3) fetch="join|select" (4) inverse="true|false" (5) optional="true|false"> (6) <key ... /> <property ... /> ... </join>
|
|
|
|
|
|
|
|
|
|
|
|
Par exemple, les informations d'adresse pour une personne peuvent être mappées vers une table séparée (tout en préservant des sémantiques de type valeur pour toutes ses propriétés) :
<class name="Person" table="PERSON"> <id name="id" column="PERSON_ID">...</id> <join table="ADDRESS"> <key column="ADDRESS_ID"/> <property name="address"/> <property name="zip"/> <property name="country"/> </join> ...
Cette fonctionnalité est souvent seulement utile pour les modèles de données hérités d'anciens systèmes (legacy), nous recommandons d'utiliser moins de tables que de classes et un modèle de domaine à granularité fine. Cependant, c'est utile pour passer d'une stratégie de mapping d'héritage à une autre dans une hiérarchie simple ainsi qu'il est expliqué plus tard.
Nous avons rencontré l'élément <key>
à plusieurs reprises maintenant. Il apparaît partout que l'élément de mapping parent définit une jointure sur une nouvele
table, et définit la clef étrangère dans la table jointe, ce qui référence la clef primaire de la table d'origine.
<key column="columnname" (1) on-delete="noaction|cascade" (2) property-ref="propertyName" (3) not-null="true|false" (4) update="true|false" (5) unique="true|false" (6) />
|
|
|
|
|
|
|
|
|
|
|
|
Nous recommandons pour les systèmes où les suppressions doivent être performantes de définir toutes les clefs on-delete="cascade"
, ainsi Hibernate utilisera une contrainte ON CASCADE DELETE
au niveau base de données, plutôt que de nombreux DELETE
individuels. Attention, cette fonctionnalité court-circuite la stratégie habituelle de verrou optimiste pour les données
versionnées.
Les attributs not-null
et update
sont utiles pour mapper une association one-to-many unidirectionnelle. Si vous mappez un one-to-many unidirectionnel vers
une clef étrangère non nulle, vous devez déclarer la colonne de la clef en utilisant <key not-null="true">
.
Tout élément de mapping qui accepte un attribut column
acceptera alternativement un sous-élément <column>
. De façon identique, <formula>
est une alternative à l'attribut formula
.
<column name="column_name" length="N" precision="N" scale="N" not-null="true|false" unique="true|false" unique-key="multicolumn_unique_key_name" index="index_name" sql-type="sql_type_name" check="SQL expression" default="SQL expression"/>
<formula>SQL expression</formula>
Les attributs column
et formula
peuvent même être combinés au sein d'une même propriété ou mapping d'association pour exprimer, par exemple, des conditions
de jointure exotiques.
<many-to-one name="homeAddress" class="Address" insert="false" update="false"> <column name="person_id" not-null="true" length="10"/> <formula>'MAILING'</formula> </many-to-one>
Supposez que votre application possède deux classes persistantes du même nom, et vous ne voulez pas préciser le nom Java complet
(packages inclus) dans les queries Hibernate. Les classes peuvent alors être "importées" explicitement plutôt que de compter
sur auto-import="true"
.Vous pouvez même importer des classes et interfaces qui ne sont pas mappées explicitement.
<import class="java.lang.Object" rename="Universe"/>
<import class="ClassName" (1) rename="ShortName" (2) />
|
|
|
|
Il existe encore un type de mapping de propriété. L'élément de mapping <any>
définit une association polymorphique vers des classes de tables multiples. Ce type de mapping requiert 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 clef étrangère pour ce type d'association, donc ce n'est certainement pas considéré
comme le moyen habituel de mapper des associations (polymorphiques). Vous devriez utiliser cela uniquement dans des cas particuliers
(par exemple des logs d'audit, des données de session utilisateur, etc...).
L'attribut meta-type
permet à l'application de spécifier un type personnalisé qui mappe des valeurs de colonnes de le base de données sur des
classes persistantes qui ont un attribut identifiant du type spécifié par id-type
. Vous devez spécifier le mapping à partir de valeurs du méta-type sur les noms des classes.
<any name="being" 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="propertyName" (1) id-type="idtypename" (2) meta-type="metatypename" (3) cascade="cascade_style" (4) access="field|property|ClassName" (5) optimistic-lock="true|false" (6) > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any>
|
|
|
|
|
|
|
|
|
|
|
|