Hibernate.orgCommunity Documentation
Les mappages objet/relationnel sont généralement définis dans un document XML. Le document de mappage est conçu pour être lisible et éditable à la main. Le langage de mappage est Java-centrique, c'est-à-dire que les mappages sont construits à partir de déclarations de classes persistantes et non à partir de déclarations de tables.
Remarquez que même si beaucoup d'utilisateurs de Hibernate préfèrent écrire les fichiers de mappages XML à la main, plusieurs outils existent pour générer ce document, notamment XDoclet, Middlegen et AndroMDA.
Commençons avec un exemple de mappage :
<?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
>
Étudions le contenu du document de mappage. Nous ne décrirons que les éléments et attributs du document utilisés par Hibernate à l'exécution. Le document de mappage 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 mappages XML devraient utiliser le doctype indiqué. En effet vous trouverez le fichier DTD à 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.
Comme mentionné précédemment, Hibernate tentera en premier lieu de résoudre les DTD dans leur classpath. Il réussit à le faire en enregistrant une implémentation personnalisée de org.xml.sax.EntityResolver
avec le SAXReader qu'il utilise pour lire les fichiers xml. Cet EntityResolver
personnalisé reconnaît deux espaces de nommage systemId différents :
un espace de nommage hibernate
est reconnu dès que le résolveur rencontre un systemId commençant par http://hibernate.sourceforge.net/
. Le résolveur tente alors de résoudre ces entités via le chargeur de classe qui a chargé les classes Hibernate.
un espace de nommage utilisateur
est reconnu dès que le résolveur rencontre un systemId qui utilise un protocole URL classpath://
. Le résolveur tentera alors de résoudre ces entités via (1) le chargeur de classe du contexte du thread courant et (2) le chargeur de classe qui a chargé les classes Hibernate.
Un exemple d'utilisation de l'espace de nommage utilisateur:
<?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 version "3.5.6-Final">
<!ENTITY today "September 15, 2010">
<!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 mentionnées dans ce mappage 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 de catalogue. L'attribut default-cascade
indique quel type de cascade sera utilisé 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, par défaut.
<hibernate-mapping schema="schemaName" catalog="catalogName" default-cascade="cascade_style" default-access="field|property|ClassName" default-lazy="true|false" auto-import="true|false" package="package.name" />
| |
| |
| |
| |
| |
| |
|
Si deux classes persistantes possèdent le même nom de classe (non qualifié), vous devez configurer auto-import="false"
. Hibernate lancera une exception si vous essayez d'assigner le même nom "importé" à deux classes.
Notez que l'élément hibernate-mappage
vous permet d'imbriquer plusieurs mappages de <class>
persistantes, comme dans l'exemple ci-dessus. Cependant il est recommandé (et c'est parfois une exigence de certains outils) de mapper une seule classe persistante (ou une seule hiérarchie de classes) par fichier de mappage et de nommer ce fichier d'après le nom de la superclasse persistante, 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
. Part exemple :
<class name="ClassName" table="tableName" discriminator-value="discriminator_value" mutable="true|false" schema="owner" catalog="catalog" proxy="ProxyInterface" dynamic-update="true|false" dynamic-insert="true|false" select-before-update="true|false" polymorphism="implicit|explicit" where="arbitrary sql where condition" persister="PersisterClass" batch-size="N" optimistic-lock="none|version|dirty|all" lazy="(16)true|false" entity(17)-name="EntityName" check=(18)"arbitrary sql check condition" rowid=(19)"rowid" subsel(20)ect="SQL expression" abstra(21)ct="true|false" node="element-name" />
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
(16) |
|
(17) |
|
(18) |
|
(19) |
|
(20) |
|
(21) |
|
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 optionnel proxy
permet les initialisations 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 lorsqu'une méthode du proxy sera appelée. Voir plus bas le paragraphe abordant les Proxies et leur initialisation différée (lazy initialization).
Le polymorphisme implicite signifie que des 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 personnaliser la stratégie de persistance utilisée pour la classe. Vous pouvez, par exemple, spécifier votre propre sous-classe de org.hibernate.persister.EntityPersister
ou vous pourriez aussi fournir 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. À 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 éviter l'appel inutile par un déclenchement de mise à jour de base de donnée, quand on ré-attache un graphe d'instances à une Session
.
Si vous utilisez le dynamic-update
, les différentes stratégies de verrouillage optimiste sont les suivantes :
version
vérifie les colonnes version/timestamp
all
vérifie toutes les colonnes
dirty
vérifie les colonnes modifiées, permettant quelques mise à jour concurrentes
none
n'utilisez pas le 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 ce qui concerne les performances et la seule qui gère correctement les modifications sur les instances détachées (c'est à dire lorsqu'on utilise Session.merge()
).
Il n'y a pas de différence entre table et vue pour le mappage Hibernate, comme on peut s'y attendre, cela est transparent au niveau base de données (remarquez que certaines BDD ne supportent pas les vues correctement, notamment pour les mise à jour). Il est possible que vous souhaitiez utiliser une vue mais vous ne puissiez pas en créer une sur votre BDD (c'est-à-dire avec un schéma ancien). Dans ces cas, vous pouvez mapper une entité immuable en lecture seule sur une expression sous-select SQL donnée :
<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 <subselect>
est disponible comme attribut ou comme élément de mappage imbriqué.
Les classes mappées doivent déclarer la clé primaire de la table en base de données. La plupart des classes auront aussi une propriété de type JavaBeans présentant l'identifiant unique d'une instance. L'élément <id>
sert à définir le mappage entre cette propriété et la colonne de la clé primaire.
<id name="propertyName" type="typename" column="column_name" unsaved-value="null|any|none|undefined|id_value" access="field|property|ClassName"> 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é d'identifiant.
L'attribut unsaved-value
n'est presque jamais nécessaire dans Hibernate3.
La déclaration alternative <composite-id>
permet l'accès aux données d'anciens systèmes qui utilisent des clés composées. Son utilisation est fortement déconseillée pour d'autres cas.
L'élément enfant <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
>
Tous les générateurs implémentent l'interface org.hibernate.id.IdentifierGenerator
. C'est une interface très simple ; certaines applications peuvent proposer leurs propres implémentations spécialisées. Cependant, Hibernate propose une série d'implémentations intégrées. Il existe des noms raccourcis pour les générateurs intégrés :
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
prend en charge les colonnes d'identité dans DB2, MySQL, MS SQL Server, Sybase et HypersonicSQL. L'identifiant renvoyé est de type long
, short
ou int
.
sequence
utilise une séquence dans DB2, PostgreSQL, Oracle, SAP DB, McKoi ou 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 valeurs "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
, en prenant une séquence en base nommée.
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 est encodé 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
permet à l'application d'affecter un identifiant à l'objet avant que la méthode 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 clé primaire assignée par un déclencheur (trigger) de base de données en sélectionnant la ligne par une clé unique quelconque et en extrayant la valeur de la clé primaire.
foreign
utilise l'identifiant d'un autre objet associé. Habituellement utilisé en conjonction avec une association <one-to-one>
sur la clé primaire.
sequence-identity
Une stratégie de génération de séquence spécialisée qui utilise une séquence de base de données pour la génération réelle de valeurs, tout en utilisant JDBC3 getGeneratedKeys pour retourner effectivement la valeur d'identifiant générée, comme faisant partie de l'exécution de la déclaration insert. Cette stratégie est uniquement prise en charge par les pilotes Oracle 10g pour JDK 1.4. Notez que les commentaires sur ces déclarations insert sont désactivés à cause d'un bogue dans les pilotes d'Oracle.
Les générateurs hilo
et seqhilo
proposent deux implémentations alternatives de l'algorithme hi/lo. 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 utiliser 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 : l'adresse IP, la date de démarrage de la JVM (précis au quart de seconde), l'heure système et une contre-valeur (unique au sein de la JVM). Il n'est pas possible d'obtenir une 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 clé 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 clé par sequence
. Ces deux méthodes nécessitent deux requêtes SQL pour insérer un nouvel objet. Par exemple :
<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ée par la propriété identifiant l'objet. Ce générateur est utilisé quand la clé primaire est une clé naturelle plutôt qu'une clé 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 que Hibernate utiliseunsaved-value="undefined"
, le forçant ainsi à interroger la base de données pour déterminer si une 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, il y a une valeur de propriété unique appelée socialSecurityNumber
. Elle est définie par la classe en tant que clé naturelle et il y a également une clé secondaire appelée person_id
dont la valeur est générée par un trigger.
A partir de la version 3.2.3, 2 générateurs représentent une nouvelle conception de 2 aspects séparés de la génération d'identifiants. Le premier aspect est la portabilité de la base de données; le second est l'optimization, c'est à dire que vous n'avez pas à interroger la base de données pour chaque requête de valeur d'identifiant. Ces deux nouveaux générateurs sont sensés prendre la place de générateurs décrits ci-dessus, ayant pour préfixe 3.3.x. Cependant, ils sont inclus dans les versions actuelles, et peuvent être référencés par FQN.
Le premier de ces nouveaux générateurs est org.Hibernate.ID.Enhanced.SequenceStyleGenerator
qui est destiné, tout d'abord, comme un remplacement pour le générateur séquence
et, deuxièmement, comme un générateur de portabilité supérieur à natif
. C'est parce que natif
a généralement le choix entre identité
et séquence
qui ont des sémantiques largement différentes, ce qui peut entraîner des problèmes subtils en observant la portabilité des applications. org.Hibernate.ID.Enhanced SequenceStyleGenerator.
, cependant, réalise la portabilité d'une manière différente. Il choisit entre une table ou une séquence dans la base de données pour stocker ses valeurs s'incrémentant, selon les capacités du dialecte utilisé. La différence avec natif
c'est que de stockage basé sur les tables ou basé sur la séquence ont la même sémantique. En fait, les séquences sont exactement ce qu'Hibernate essaie d'émuler avec ses générateurs basée sur les tables. Ce générateur a un certain nombre de paramètres de configuration :
sequence_name
(en option, par défaut = hibernate_sequence
): le nom de la séquence ou table à utiliser.
initial_value
(en option - par défaut = 1
) : la première valeur à extraire de la séquence/table. En termes de création de séquences, c'est semblable à la clause qui s'appelle "STARTS WITH" normalement.
increment_size
(en option - par défaut = 1
): la valeur par laquelle les appels suivants à la séquence / table doivent différer. En termes de création de séquence, c'est analogue à la clause généralement nommé "INCREMENT BY".
force_table_use
(optionnel - par défaut = false
) : doit-on forcer l'utilisation de la table en tant que structure de soutien même si le dialecte peut supporter la séquence ?
value_column
(en option - par défaut = next_val
): uniquement utile pour les structures de tables. Correspond au nom de la colonne de la table qui est utilisée pour contenir la valeur.
optimizer
(optional - defaults to none
): See Section 5.1.6, « Optimisation du générateur d'identifiants »
Le deuxième de ces nouveaux générateurs est org.Hibernate.ID.Enhanced.TableGenerator
, qui est destiné, tout d'abord, comme un remplacement pour le générateur de la table
, même si elle fonctionne effectivement beaucoup plus comme org.Hibernate.ID.MultipleHiLoPerTableGenerator
et deuxièmement, comme une remise en œuvre de org.Hibernate.ID.MultipleHiLoPerTableGenerator
, qui utilise la notion d'optimizers enfichables. Essentiellement ce générateur définit une table susceptible de contenir un certain nombre de valeurs d'incrément différents simultanément à l'aide de plusieurs lignes distinctement masquées. Ce générateur a un certain nombre de paramètres de configuration :
table_name
(en optin - valeur par défaut = hibernate_sequences
): le nom de la table à utiliser.
value_column_name
(en option - valeur par défaut =next_val
): le nom de la colonne contenue dans la table utilisée pour la valeur.
segment_column_name
(en option - par défaut = sequence_name
): le nom de la colonne de la table qui est utilisée pour contenir la "segment key". Il s'agit de la valeur qui identifie la valeur d'incrément à utiliser.
segment_value
(en option - par défaut = par défaut
): La "segment key"valeur pour le segment à partir de laquelle nous voulons extraire des valeurs d'incrémentation pour ce générateur.
segment_value_length
(en option - par défaut = 255
): Utilisée pour la génération de schéma ; la taille de la colonne pour créer cette colonne de clé de segment.
initial_value
(en option - par défaut est 1
: La valeur initiale à récupérer à partir de la table.
increment_size
(en option - par défaut = 1
): La valeur par laquelle les appels à la table, qui suivent, devront différer.
optimizer
(optional - defaults to ): See Section 5.1.6, « Optimisation du générateur d'identifiants »
For identifier generators that 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 can 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, « La méthode getter de l'identifiant » support this operation.
aucun
(en général il s'agit de la valeur par défaut si aucun optimizer n'a été spécifié): n'effectuera pas d'optimisations et n'interrogera pas la base de données à chaque demande.
hilo
: applique un algorithme hi/lo autour des valeurs extraites des base de données. Les valeurs de la base de données de cet optimizer sont censées être séquentielles. Les valeurs extraites de la structure des base de données pour cet optimizer indique le "numéro de groupe". Le increment_size
est multiplié par cette valeur en mémoire pour définir un groupe de "hi value".
mise en commun
: tout comme dans le cas de hilo
, cet optimizer tente de réduire le nombre d'interrogations vers la base de données. Ici, cependant, nous avons simplement stocké la valeur de départ pour le "prochain groupe"dans la structure de la base de données plutôt qu'une valeur séquentielle en combinaison avec un algorithme de regroupement en mémoire. Ici, increment_size
fait référence aux valeurs provenant de la base de données.
<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 clé composée, vous pouvez mapper plusieurs attributs de la classe comme propriétés identifiantes. L'élément <composite-id>
accepte les mappages de propriétés <key-property>
et les mappages <key-many-to-one>
comme éléments enfants.
<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 composite. Elles doivent aussi implémenter l'interface Serializable
.
Malheureusement, cette approche 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 clé 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 à dupliquer les propriétés identifiantes nommées dans l'élément <composite-id>
) à la fois dans la classe persistante et dans une classe identifiante 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 mappages de propriétés contenues font référence aux deux classes, la classe mappée et la classe identifiante composée.
class
(optionnel, mais requis pour un identifiant composé mappé) : la classe utilisée comme identifiant composé.
We will describe a third, even more convenient approach, where the composite identifier is implemented as a component class in Section 8.4, « Les composants en tant qu'identifiants composites ». The attributes described below apply only to this alternative approach:
name
(optionnel, requis pour cette approche) : une propriété de type composant qui contient l'identifiant composé (voir chapitre 9).
access
(optionnel - par défaut property
) : la stratégie que doit utiliser Hibernate pour accéder aux valeurs des propriétés.
class
(optionnel - par défaut le type de la propriété déterminé par réflexion) : la classe composant utilisée comme identifiant (voir prochaine section).
La troisième approche, un composant d'identifiant, 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 mappage de table par hiérarchie de classe et déclare une colonne discriminante de la table. La colonne discriminante contient des valeurs marqueur qui permettent à 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" type="discriminator_type" force="true|false" insert="true|false" formula="arbitrary sql expression" />
| |
| |
| |
| |
|
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" name="propertyName" type="typename" access="field|property|ClassName" unsaved-value="null|negative|undefined" generated="never|always" insert="true|false" 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 clés composées .
L'élément optionnel <timestamp>
indique que la table contient des données horodatées (timestamped). Cela sert d'alternative à l'utilisation de numéros de version. Les timestamps (ou horodatage) sont par nature une implémentation moins fiable pour le verrouillage optimiste. Cependant, l'application peut parfois utiliser l'horodatage à d'autres fins.
<timestamp column="timestamp_column" name="propertyName" access="field|property|ClassName" unsaved-value="null|undefined" source="vm|db" generated="never|always" node="element-name|@attribute-name|element/@attribute|." />
| |
| |
| |
| |
| |
|
Notez que <timestamp>
est équivalent à <version type="timestamp">
et <timestamp source="db">
équivaut à <version type="dbtimestamp">
L'élément <property>
déclare une propriété persistante de la classe au sens JavaBean.
<property name="propertyName" column="column_name" type="typename" update="true|false" insert="true|false" formula="arbitrary SQL expression" access="field|property|ClassName" lazy="true|false" unique="true|false" not-null="true|false" optimistic-lock="true|false" generated="never|insert|always" node="element-name|@attribute-name|element/@attribute|." index="index_name" unique_key="unique_key_id" length="L" precision="P" scale="S" />
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
typename peut être :
Le nom d'un type basique Hibernate (par ex : integer, string, character, date, timestamp, float, binary, serializable, object, blob
etc.).
le nom d'une classe Java avec un type basique par défaut (par ex : int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob
etc.).
Le nom d'une classe Java sérialisable.
Le nom d'une classe avec un type personnalisé (par ex : com.illflow.type.MyCustomType
).
Si vous n'indiquez pas un type, Hibernate utilisera 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. 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 personnalisé).
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és en nommant une classe qui implémente l'interface org.hibernate.propertexige une instrumentation de code d'octets build-timey.PropertyAccessor
.
Les propriétés dérivées représentent une fonctionnalité particulièrement intéressante. 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 mappage <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 plusieurs-à-un : une clé étrangère dans une table référence la ou les clé(s) primaire(s) dans la table cible.
<many-to-one name="propertyName" column="column_name" class="ClassName" cascade="cascade_style" fetch="join|select" update="true|false" insert="true|false" property-ref="propertyNameFromAssociatedClass" access="field|property|ClassName" unique="true|false" not-null="true|false" optimistic-lock="true|false" lazy="proxy|no-proxy|false" not-found="ignore|exception" entity-name="EntityName" formula="arbitrary SQL expression" node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" index="index_name" unique_key="unique_key_id" foreign-key="foreign_key_name" />
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
Setting a value of the cascade
attribute to any meaningful value other than none
will propagate certain operations to the associated object. The meaningful values are divided into three categories. First, basic operations, which include: persist, merge, delete, save-update, evict, replicate, lock and refresh
; second, special values: delete-orphan
; and third, all
comma-separated combinations of operation names: cascade="persist,merge,evict"
or cascade="all,delete-orphan"
. See Section 10.11, « Persistance transitive » for a full explanation. Note that single valued, many-to-one and one-to-one, associations do not support 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 clés étrangères font référence à une clé unique de la table associée et qui n'est pas la clé primaire. C'est un cas de mauvaise conception relationnelle. Par exemple, supposez que la classe Product
ait un numéro de série unique qui n'est pas la clé 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 mappage 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 clé 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 nommé <properties>
.
Si la clé unique référencée est la propriété d'un composant, vous pouvez spécifier le chemin de propriété :
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
Une association un-à-un vers une autre classe persistante est déclarée avec l'élément one-to-one
.
<one-to-one name="propertyName" class="ClassName" cascade="cascade_style" constrained="true|false" fetch="join|select" property-ref="propertyNameFromAssociatedClass" access="field|property|ClassName" formula="any SQL expression" lazy="proxy|no-proxy|false" entity-name="EntityName" node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" foreign-key="foreign_key_name" />
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
Il existe deux types d'associations un-à-un :
association par clé primaire
association par clé étrangère unique
Les associations par clé primaire ne nécessitent pas une colonne supplémentaire en table ; si deux lignes sont liées par l'association alors les deux lignes de la table partagent la même valeur de clé primaire. Donc si vous voulez que deux objets soient liés par une association par clé primaire, vous devez faire en sorte qu'on leur assigne la même valeur d'identifiant.
Pour une association par clé primaire, ajoutez les mappages suivants à Employee
et Person
, respectivement :
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Maintenant, vous devez faire en sorte que les clés 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
:
<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 instance fraîchement enregistrée de Person
se voit alors assignée la même valeur de clé primaire que l'instance de Employee
référencée par la propriété employee
de cette Person
.
Alternativement, une clé étrangère avec contrainte d'unicité de Employee
vers Person
peut être indiquée ainsi :
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
Et cette association peut être rendue bidirectionnelle en ajoutant ceci au mappage de Person
:
<one-to-one name="employee" class="Employee" property-ref="person"/>
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id
>
Bien que nous recommandions l'utilisation de clés primaires générées, vous devriez toujours essayer d'identifier des clés métier (naturelles) pour toutes vos entités. Une clé naturelle est une propriété ou une combinaison de propriétés unique et non nulle. Si elle est aussi immuable, c'est encore mieux. Mappez les propriétés de la clé naturelle dans l'élément <natural-id>
. Hibernate générera la clé unique nécessaire et les contraintes de non-nullité, et votre mappage s'auto-documentera.
Nous vous recommandons fortement d'implémenter equals()
et hashCode()
pour comparer les propriétés clés naturelles de l'entité.
Ce mappage n'est pas destiné à être utilisé avec des entités qui ont des clés naturelles.
mutable
(optionnel, par défaut à false
) : par défaut, les identifiants naturels sont supposés être immuables (constants).
L'élément <component>
mappe les propriétés d'un objet enfant aux colonnes d'une classe parente. Les composants peuvent en retour déclarer leurs propres propriétés, composants ou collections. Voir "Components" plus bas :
<component name="propertyName" class="className" insert="true|false" update="true|false" access="field|property|ClassName" lazy="true|false" optimistic-lock="true|false" unique="true|false" node="element-name|." > <property ...../> <many-to-one .... /> ........ </component >
| |
| |
| |
| |
| |
| |
| |
|
Les balises enfant <property>
mappent les propriétés de la classe enfant sur les colonnes de la table.
L'élément <component>
permet de déclarer un sous-élément <parent>
qui associe une propriété de la classe composant comme une référence arrière vers l'entité contenante.
The <dynamic-component>
element allows a Map
to be mapped as a component, where the property names refer to keys of the map. See Section 8.5, « Les composants dynamiques » for more information.
L'élément <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. Par exemple :
<properties name="logicalName" insert="true|false" update="true|false" optimistic-lock="true|false" unique="true|false" > <property ...../> <many-to-one .... /> ........ </properties >
| |
| |
| |
| |
|
Par exemple, si nous avons le mappage de <properties>
suivant :
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class
>
Alors nous pourrions avoir une association sur des données d'un ancien système qui font référence à cette clé unique de la table Person
au lieu de la clé primaire :
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one
>
Nous ne recommandons pas une telle utilisation, en dehors du contexte de mappage de données héritées d'anciens systèmes.
Pour finir, la persistance polymorphique nécessite la déclaration de chaque sous-classe de la classe racine persistante. Pour la stratégie de mappage de type table-per-class-hierarchy, on utilise la déclaration <subclass>
.
<subclass name="ClassName" discriminator-value="discriminator_value" proxy="ProxyInterface" lazy="true|false" dynamic-update="true|false" dynamic-insert="true|false" entity-name="EntityName" node="element-name" extends="SuperclassName"> <property .... /> ..... </subclass >
| |
| |
| |
|
Chaque sous-classe devrait déclarer ses propres propriétés persistantes et sous-classes. Les propriétés <version>
et <id>
sont implicitement hérités de la classe racine. Chaque sous-classe dans une hiérarchie doit définir une unique discriminator-value
. Si non spécifiée, le nom complet de la classe Java est utilisé.
For information about inheritance mappings see Chapitre 9, Mapping d'héritage de classe .
Il est également possible de mapper chaque sous-classe vers sa propre table (stratégie de mappage 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é. Par exemple :
<joined-subclass name="ClassName" table="tablename" proxy="ProxyInterface" lazy="true|false" 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 mappage. Cependant, chaque sous-classe doit déclarer une colonne de table contenant l'objet identifiant qui utilise l'élément <key>
. Le mappage 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
>
For information about inheritance mappings see Chapitre 9, Mapping d'héritage de classe .
Une troisième option est de mapper uniquement les classes concrètes d'une hiérarchie d'héritage vers des tables, (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'est-à-dire une association vers la superclasse de votre hiérarchie), vous devez utiliser le mappage <union-subclass>
. Par exemple :
<union-subclass name="ClassName" table="tablename" proxy="ProxyInterface" lazy="true|false" 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 clé n'est requise pour cette stratégie de mappage.
For information about inheritance mappings see 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 quand il existe une relation un-à-un entre les tables. Par exemple :
<join table="tablename" schema="owner" catalog="catalog" fetch="join|select" inverse="true|false" optional="true|false"> <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, 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 mappage d'héritage à une autre dans une hiérarchie simple, comme nous le verrons plus tard.
Nous avons rencontré l'élément <key>
à plusieurs reprises maintenant. Il apparaît partout que l'élément de mappage parent définit une jointure sur une nouvelle table, et définit la clé étrangère dans la table jointe, qui référence la clé primaire de la table d'origine :
<key column="columnname" on-delete="noaction|cascade" property-ref="propertyName" not-null="true|false" update="true|false" unique="true|false" />
| |
| |
| |
| |
| |
|
Là où les suppressions doivent être performantes, nous recommandons pour les systèmes de définir toutes les clés 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 un-à-plusieurs unidirectionnelle. Si vous mappez un un-à-plusieurs unidirectionnel vers une clé étrangère non nulle, vous devez déclarer la colonne de la clé en utilisant <key not-null="true">
.
Tout élément de mappage qui accepte un attribut column
acceptera alternativement un sous-élément <column>
. Pareillement <formula>
est une alternative à l'attribut formula
. Par exemple :
<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"
read="SQL expression"
write="SQL expression"/>
<formula
>SQL expression</formula
>
Most of the attributes on column
provide a means of tailoring the DDL during automatic schema generation. The read
and write
attributes allow you to specify custom SQL that Hibernate will use to access the column's value. For more on this, see the discussion of column read and write expressions.
The column
and formula
elements can even be combined within the same property or association mapping to express, for example, exotic join conditions.
<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 (paquetage) dans les requêtes 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" rename="ShortName" />
| |
|
Il existe encore un type de mappage de propriété. L'élément de mappage <any>
définit une association polymorphique vers des classes de tables multiples. Ce type de mappage 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 clé étrangère pour ce type d'association, donc ce n'est certainement pas considéré comme le moyen habituel de mapper des associations (polymorphiques). Ne doit être utilisé que 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 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 mappage à 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" id-type="idtypename" meta-type="metatypename" cascade="cascade_style" access="field|property|ClassName" optimistic-lock="true|false" > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any >
| |
| |
| |
| |
| |
|
Pour le service de persistance, les objets sont classés en deux groupes au niveau langage Java :
Une entité existe indépendamment de tout autre objet possédant des références vers l'entité. Comparez cela avec le modèle Java habituel où un objet est supprimé par le garbage collector dès qu'il n'est plus référencé. Les entités doivent être explicitement enregistrées et supprimées (sauf dans les cas où sauvegardes et suppressions sont cascadées d'une entité parent vers ses enfants). C'est différent du modèle ODMG de persistance par atteignabilité - et correspond mieux à la façon dont les objets sont habituellement utilisés dans des grands systèmes. Les entités permettent les références circulaires et partagées. Elles peuvent aussi être versionnées.
L'état persistant d'une entité consiste en des références vers d'autres entités et instances de types valeurs. Ces valeurs sont des types primitifs, des collections (et non le contenu d'une collection), des composants de certains objets immuables. Contrairement aux entités, les valeurs (et en particulier les collections et composants) sont persistées et supprimées par atteignabiliité. Comme les valeurs (et types primitifs) sont persistées et supprimées avec l'entité qui les contient, ils ne peuvent pas posséder leurs propres versions. Les valeurs n'ont pas d'identité indépendantes, ainsi elles ne peuvent pas être partagées par deux entités ou collections.
Jusqu'à présent nous avons utilisé le terme "classe persistante" pour parler d'entités. Nous allons continuer à faire ainsi. Cependant, au sens strict, toutes les classes définies par un utilisateur possédant un état persistant ne sont pas des entités. Un composant est une classe définie par un utilisateur avec la sémantique d'une valeur. Une propriété Java de type java.lang.String
a aussi les caractéristiques d'une valeur. Selon cette définition, nous sommes en mesure de déclarer que tous les types (classes) fournis par JDK possèdent la sémantique d'une valeur dans Java, alors que les types définis par un utilisateur pourront être mappés avec des sémantiques entités ou valeur type. Cette décision est prise par le développeur d'application. Un bon conseil pour une classe entité dans un modèle de domaine sont des références partagées à une instance unique de cette classe, alors que la composition ou l'agrégation se traduit en général par une valeur type.
Nous nous pencherons sur ces deux concepts tout au long de la documentation.
Le défi est de mapper les types Javas (et la définition des développeurs des entités et valeurs types) sur les types du SQL ou des bases de données. Le pont entre les deux systèmes est proposé par Hibernate : pour les entités nous utilisons <class>
, <subclass>
et ainsi de suite. Pour les types valeurs nous utilisons <property>
, <component>
, etc., habituellement avec un attribut type
. La valeur de cet attribut est le nom d'un type de mappage Hibernate. Hibernate propose de nombreux mappages prêts à l'utilisation (pour les types de valeurs standards du JDK). Vous pouvez écrire vos propres types de mappages et implémenter aussi vos propres stratégies de conversion comme nous le verrons plus tard.
Tous les types proposés Hibernate à part les collections autorisent les sémantiques null.
Les types de mappage de base peuvent être classés de la façon suivante :
integer, long, short, float, double, character, byte, boolean, yes_no, true_false
Les mappages de type des primitives Java ou leurs classes wrappers (ex : Integer pour int) vers les types de colonne SQL (propriétaires) appropriés. boolean, yes_no
et true_false
sont tous des alternatives pour les types Java boolean
ou java.lang.Boolean
.
string
Mappage de type de java.lang.String
vers VARCHAR
(ou le VARCHAR2
Oracle).
date, time, timestamp
mappages de type pour java.util.Date
et ses sous-classes vers les types SQL DATE
, TIME
et TIMESTAMP
(ou équivalent).
calendar, calendar_date
mappages de type pour java.util.Calendar
vers les types SQL TIMESTAMP
et DATE
(ou équivalent).
big_decimal, big_integer
mappages de type de java.math.BigDecimal
et java.math.BigInteger
vers NUMERIC
(ou le NUMBER
Oracle).
locale, timezone, currency
mappages de type pour java.util.Locale
, java.util.TimeZone
et java.util.Currency
vers VARCHAR
(ou le VARCHAR2
Oracle). Les instances de Locale
et Currency
sont mappées sur leurs codes ISO. Les instances de TimeZone
sont mappées sur leur ID
.
class
Un type de mappage de java.lang.Class
vers VARCHAR
(ou le VARCHAR2
Oracle). Un objet Class
est mappé sur son nom Java complet.
binary
Mappe les tableaux de bytes vers le type binaire SQL approprié.
text
Mappe les longues chaînes de caractères Java vers les types SQL CLOB
ou TEXT
.
serializable
Mappe les types Java sérialisables vers le type SQL binaire approprié. Vous pouvez aussi indiquer le type Hibernate serializable
avec le nom d'une classe Java sérialisable ou une interface qui ne soit pas par défaut un type de base.
clob, blob
Mappages de type pour les classes JDBC java.sql.Clob
et java.sql.Blob
. Ces types peuvent ne pas convenir pour certaines applications car un objet blob ou clob peut ne pas être réutilisable en dehors d'une transaction (de plus l'implémentation par les pilotes comporte des lacunes).
imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary
Mappages de type pour ceux qui sont habituellement considérés comme des types Java modifiables, et pour lesquels Hibernate effectue certaines optimisations convenant seulement aux types Java immuables. L'application les traite comme immuables. Par exemple, vous ne devriez pas appeler Date.setTime()
sur une instance mappée sur un imm_timestamp
. Pour changer la valeur de la propriété, et faire en sorte que cette modification soit persistée, l'application doit assigner un nouvel (non identique) objet à la propriété.
Les identifiants uniques des entités et collections peuvent être de n'importe quel type de base excepté binary
, blob
et clob
(les identifiants composites sont aussi permis, voir plus bas).
Les types de base des valeurs ont des Type
constants correspondants et définis dans org.hibernate.Hibernate
. Par exemple, Hibernate.STRING
représente le type string
.
Il est assez facile pour les développeurs de créer leurs propres types de valeurs. Par exemple, vous aimeriez persister des propriétés du type java.lang.BigInteger
dans des colonnes VARCHAR
. Hibernate ne procure pas de type par défaut à cet effet. Toutefois, les types personnalisés ne se limitent pas à mapper des propriétés (ou élément collection) à une simple colonne de table. Donc, par exemple, vous pourriez avoir une propriété Java getName()
/setName()
de type java.lang.String
persistée dans les colonnes FIRST_NAME
, INITIAL
, SURNAME
.
Pour implémenter votre propre type, vous pouvez soit implémenter org.hibernate.UserType
soit org.hibernate.CompositeUserType
et déclarer des propriétés utilisant des noms de classes complets du type. Consultez org.hibernate.test.DoubleStringType
pour étudier les possibilités.
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property
>
Remarquez l'utilisation des balises <column>
pour mapper une propriété sur des colonnes multiples.
Les interfaces CompositeUserType
, EnhancedUserType
, UserCollectionType
, et UserVersionType
prennent en charge des utilisations plus spécialisées.
Vous pouvez même fournir des paramètres en indiquant UserType
dans le fichier de mappage. À cet effet, votre UserType
doit implémenter l'interface org.hibernate.usertype.ParameterizedType
. Pour spécifier des paramètres dans votre type propre, vous pouvez utiliser l'élément <type>
dans vos fichiers de mappage.
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default"
>0</param>
</type>
</property
>
Le UserType
permet maintenant de récupérer la valeur pour le paramètre nommé default
à partir de l'objet Properties
qui lui est passé.
Si vous utilisez fréquemment un UserType
, il est utile de lui définir un nom plus court. Vous pouvez l'effectuer, en utilisant l'élément <typedef>
. Les typedefs permettent d'assigner un nom à votre type propre et peuvent aussi contenir une liste de valeurs de paramètres par défaut si ce type est paramétré.
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default"
>0</param>
</typedef
>
<property name="priority" type="default_zero"/>
Il est également possible de redéfinir les paramètres par défaut du typedef au cas par cas en utilisant des paramètres type sur le mappage de la propriété.
Alors que Hibernate offre une riche variété de types, et la prise en charge des composants, vous aurez très rarement besoin d'utiliser un type personnalisé, il est néanmoins recommandé d'utiliser des types personnalisés pour les classes (non entités) qui apparaissent fréquemment dans votre application. Par exemple, une classe MonetaryAmount
est un bon candidat pour un CompositeUserType
même si elle pourrait facilement être mappée en tant que composant. Une motivation pour cela est l'abstraction. Avec un type personnalisé, vos documents de mappage sont à l'abri des changements futurs dans votre façon de représenter des valeurs monétaires.
Il est possible de fournir plus d'un mappage par classe persistante. Dans ce cas, vous devez spécifier un nom d'entité pour lever l'ambiguité entre les instances des entités mappées (par défaut, le nom de l'entité est celui de la classe). Hibernate vous permet de spécifier le nom de l'entité lorsque vous utilisez des objets persistants, lorsque vous écrivez des requêtes ou quand vous mappez des associations vers les entités nommées.
<class name="Contract" table="Contracts" entity-name="CurrentContract"> ... <set name="history" inverse="true" order-by="effectiveEndDate desc"> <key column="currentContractId"/> <one-to-many entity-name="HistoricalContract"/> </set> </class> <class name="Contract" table="ContractHistory" entity-name="HistoricalContract"> ... <many-to-one name="currentContract" column="currentContractId" entity-name="CurrentContract"/> </class >
Remarquez comment les associations sont désormais spécifiées en utilisant entity-name
au lieu de class
.
Vous pouvez forcer Hibernate à mettre un identifiant entre quotes dans le SQL généré en mettant le nom de la table ou de la colonne entre backticks dans le document de mappage. Hibernate utilisera les bons styles de quotes pour le SQL Dialect
(habituellement des doubles quotes, mais des parenthèses pour SQL Server et des backticks pour MySQL).
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class
>
XML ne convient pas à tout le monde, il y a donc des moyens alternatifs pour définir des métadonnées de mappage O/R dans Hibernate.
De nombreux utilisateurs de Hibernate préfèrent embarquer les informations de mappages directement au sein du code source en utilisant lesbalises XDoclet @hibernate.tags
. Nous ne couvrons pas cette approche dans ce document puisque cela est considéré comme faisant partie de XDoclet. Cependant, nous présentons l'exemple suivant de la classe Cat
avec des mappages XDoclet :
package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
/*
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
/**
* @hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother() {
return mother;
}
void setMother(Cat mother) {
this.mother = mother;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}
Voyez le site web de Hibernate pour plus d'exemples sur XDoclet et Hibernate.
Le JDK 5.0 introduit des annotations proches de celles de XDoclet au niveau java, qui sont type-safe et vérifiées à la compilation. Ce mécanisme est plus puissant que XDoclet et mieux supporté par les outils et les IDE. IntelliJ IDEA, par exemple, supporte l'auto-complétion et le surlignement syntaxique des annotations JDK 5.0. La nouvelle révision des spécifications des EJB (JSR-220) utilise les annotations JDK 5.0 comme mécanisme primaire pour les metadonnées des beans entités. Hibernate3 implémente l'EntityManager
de la JSR-220 (API de persistance), le support du mappage de métadonnées est disponible via le paquetage Hibernate Annotations, en tant que module séparé à télécharger. EJB3 (JSR-220) et les métadonnées Hibernate3 sont supportés.
Ceci est un exemple d'une classe POJO annotée comme un EJB entité :
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
@Id;
Long id;
String firstName;
String lastName;
Date birthday;
@Transient
Integer age;
@Embedded
private Address homeAddress;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="CUSTOMER_ID")
Set<Order
> orders;
// Getter/setter and business methods
}
Notez que le support des annotations JDK 5.0 (et de la JSR-220) est encore en cours et n'est pas terminé. Référez vous au module Hibernate Annotation pour plus d'informations.
Les propriétés générées sont des propriétés dont les valeurs sont générées par la base de données. Typiquement, les applications Hibernate avaient besoin d'invoquer refresh
sur les instances qui contenaient des propriétés pour lesquelles la base de données générait des valeurs. Marquer les propriétés comme générées, permet à l'application de déléguer cette responsabilité à Hibernate. Principalement, à chaque fois que Hibernate réalise un SQL INSERT ou UPDATE en base de données pour une entité marquée comme telle, cela provoque immédiatement un select pour récupérer les valeurs générées.
Properties marked as generated must additionally be non-insertable and non-updateable. Only versions, timestamps, and simple properties, can be marked as generated.
never
(par défaut) - indique que la valeur donnée de la propriété n'est pas générée dans la base de données.
insert
: the given property value is generated on insert, but is not regenerated on subsequent updates. Properties like created-date fall into this category. Even though version and timestamp properties can be marked as generated, this option is not available.
always
- indique que la valeur de la propriété est générée à l'insertion comme aux mise à jour.
Hibernate allows you to customize the SQL it uses to read and write the values of columns mapped to simple properties. For example, if your database provides a set of data encryption functions, you can invoke them for individual columns like this:
<!-- XML : generated by JHighlight v1.0 (http://jhighlight.dev.java.net) --> <span class="xml_tag_symbols"><</span><span class="xml_tag_name">property</span><span class="xml_plain"> </span><span class="xml_attribute_name">name</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"creditCardNumber"</span><span class="xml_tag_symbols">></span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_tag_symbols"><</span><span class="xml_tag_name">column</span><span class="xml_plain"> </span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">name</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"credit_card_num"</span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">read</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"decrypt(credit_card_num)"</span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">write</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"encrypt(?)"</span><span class="xml_tag_symbols">/></span><span class="xml_plain"></span><br /> <span class="xml_tag_symbols"></</span><span class="xml_tag_name">property</span><span class="xml_plain"></span><br /> <span class="xml_tag_symbols">></span><span class="xml_plain"></span><br />
Hibernate applies the custom expressions automatically whenever the property is referenced in a query. This functionality is similar to a derived-property formula
with two differences:
The property is backed by one or more columns that are exported as part of automatic schema generation.
The property is read-write, not read-only.
The write
expression, if specified, must contain exactly one '?' placeholder for the value.
Permettent les ordres CREATE et DROP d'objets arbitraire de la base de données, en conjonction avec les outils Hibernate d'évolutions de schéma, pour permettre de définir complètement un schéma utilisateur au sein des fichiers de mappage Hibernate. Bien que conçu spécifiquement pour créer et supprimer des objets tels que les triggers et les procédures stockées, en réalité toute commande pouvant être exécutée via une méthode de java.sql.Statement.execute()
(ALTERs, INSERTS, etc) est valable à cet endroit. Il y a principalement deux modes pour définir les objets auxiliaires de base de données :
Le premier mode est de lister explicitement les commandes CREATE et DROP dans le fichier de mappage :
<hibernate-mapping>
...
<database-object>
<create
>CREATE TRIGGER my_trigger ...</create>
<drop
>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping
>
Le second mode est de fournir une classe personnalisée qui sait comment construire les commandes CREATE et DROP. Cette classe personnalisée doit implémenter l'interface org.hibernate.mappage.AuxiliaryDatabaseObject
.
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping
>
De plus, ces objets de base de données peuvent être optionnellement traités selon l'utilisation de dialectes particuliers.
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9iDialect"/>
<dialect-scope name="org.hibernate.dialect.Oracle10gDialect"/>
</database-object>
</hibernate-mapping
>
Copyright © 2004 Red Hat, Inc.