Hibernate.orgCommunity Documentation
Hibernate est une solution de mappage objet/relationnel complète qui ne masque pas seulement au développeur les détails du système de gestion de base de données sous-jacent, mais offre aussi la gestion d'état des objets. C'est, contrairement à la gestion de statements
SQL dans les couches de persistance habituelles JDBC/SQL, une vue orientée objet très naturelle de la persistance dans les applications Java.
En d'autres termes, les développeurs d'applications Hibernate devraient toujours réfléchir à l'état de leurs objets, et pas nécessairement à l'exécution des expressions SQL. Cette part est prise en charge par Hibernate et importante seulement aux développeurs d'applications lors du réglage de la performance de leur système.
Hibernate définit et prend en charge les états d'objets suivants :
Éphémère (transient) - un objet est éphémère s'il a juste été instancié en utilisant l'opérateur new
. Il n'a aucune représentation persistante dans la base de données et aucune valeur d'identifiant n'a été assignée. Les instances éphémères seront détruites par le ramasse-miettes si l'application n'en conserve aucune référence. Utilisez la Session
d'Hibernate pour rendre un objet persistant (et laisser Hibernate s'occuper des expressions SQL qui ont besoin d'être exécutées pour cette transistion).
Persistant - une instance persistante a une représentation dans la base de données et une valeur d'identifiant. Elle pourrait avoir juste été sauvegardée ou chargée, pourtant, elle est par définition dans la portée d'une Session
. Hibernate détectera tout changement effectué sur un objet dans l'état persistant et synchronisera l'état avec la base de données lors de la fin de l'unité de travail. Les développeurs n'exécutent pas d'expressions UPDATE
ou DELETE
manuelles lorsqu'un objet devrait être rendu éphémère.
Détaché - une instance détachée est un objet qui a été persistant, mais dont la Session
a été fermée. La référence à l'objet est encore valide, bien sûr, et l'instance détachée pourrait même être modifiée dans cet état. Une instance détachée peut être rattachée à une nouvelle Session
ultérieurement, la rendant (et toutes les modifications avec) de nouveau persistante. Cette fonctionnalité rend possible un modèle de programmation pour de longues unités de travail qui requièrent un temps de réflexion de l'utilisateur. Nous les appelons des conversations, c'est-à-dire une unité de travail du point de vue de l'utilisateur.
Nous allons maintenant approfondir le sujet des états et des transitions d'état (et des méthodes Hibernate qui déclenchent une transition).
Newly instantiated instances of a persistent class are considered transient by Hibernate. We can make a transient instance persistent by associating it with a session:
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
Si Cat
a un identifiant généré, l'identifiant est généré et assigné au cat
lorsque save()
est appelé. Si Cat
a un identifiant assigned
, ou une clef composée, l'identifiant devrait être assigné à l'instance de cat
avant d'appeler save()
. Vous pouvez aussi utiliser persist()
à la place de save()
, avec la sémantique définie plus tôt dans la première ébauche d'EJB3.
persist()
rend une instance éphémère persistante. Toutefois, il ne garantit pas que la valeur d'identificateur soit affectée à l'instance permanente immédiatement, l'affectation peut se produire au moment de flush. Persist()
garantit également qu'il ne s'exécutera pas un énoncé INSERT
s'il est appelée en dehors des limites de transaction. C'est utile pour les longues conversations dans un contexte de session/persistance étendu.
save()
garantit le retour d'un identifiant. Si une instruction INSERT doit être exécutée pour obtenir l'identifiant (par exemple, le générateur "identity", et non pas "sequence"), cet INSERT se produit immédiatement, que vous soyez à l'intérieur ou à l'extérieur d'une transaction. C'est problématique dans une conversation longue dans un contexte de session/persistance étendu.
Alternativement, vous pouvez assigner l'identifiant en utilisant une version surchargée de save()
.
DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );
Si l'objet que vous rendez persistant a des objets associés (par exemple, la collection kittens
dans l'exemple précédent), ces objets peuvent être rendus persistants dans n'importe quel ordre désiré, à moins que vous ayez une contrainte NOT NULL
sur la colonne de la clé étrangère. Il n'y a jamais de risque de violer une contrainte de cl. étrangère. Cependant, vous pourriez violer une contrainte NOT NULL
si vous appeliez save()
sur les objets dans le mauvais ordre.
Habituellement, vous ne vous préoccupez pas de ce détail, puisque vous utiliserez très probablement la fonctionnalité de persistance transitive de Hibernate pour sauvegarder les objets associés automatiquement. Alors, même les violations de contrainte NOT NULL
n'ont plus lieu - Hibernate prendra soin de tout. La persistance transitive est traitée plus loin dans ce chapitre.
Les méthodes load()
de Session
vous donnent un moyen de récupérer une instance persistante si vous connaissez déjà son identifiant. load()
prend un objet de classe et chargera l'état dans une instance nouvellement instanciée de cette classe, dans un état persistant.
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
Alternativement, vous pouvez charger un état dans une instance donnée :
Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();
Notez que load()
lèvera une exception irrécupérable s'il n'y a pas de ligne correspondante dans la base de données. Si la classe est mappée avec un proxy, load()
retourne juste un proxy non initialisé et n'accède en fait pas à la base de données jusqu'à ce que vous invoquiez une méthode du proxy. Ce comportement est très utile si vous souhaitez créer une association vers un objet sans réellement le charger à partir de la base de données. Cela permet aussi à de multiples instances d'être chargées comme un lot si batch-size
est défini pour le mapping de la classe.
Si vous n'êtes pas certain qu'une ligne correspondante existe, vous utiliserez la méthode get()
, laquelle accède à la base de données immédiatement et retourne null s'il n'y a pas de ligne correspondante.
Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;
Vous pouvez même charger un objet en employant un SQL SELECT ... FOR UPDATE
, en utilisant un LockMode
. Voir la documentation de l'API pour plus d'informations.
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
Notez que n'importe quelle instance associée ou collection contenue ne sont pas sélectionnées par FOR UPDATE
, à moins que vous ne décidiez de spécifier lock
ou all
en tant que style de cascade pour l'association.
Il est possible de re-charger un objet et toutes ses collections à tout moment, en utilisant la méthode refresh()
. C'est utile lorsque des "triggers" de base de données sont utilisés pour initialiser certaines propriétés de l'objet.
sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)
How much does Hibernate load from the database and how many SQL SELECT
s will it use? This depends on the fetching strategy. This is explained in Section 20.1, « Stratégies de chargement ».
Si vous ne connaissez par les identifiants des objets que vous recherchez, vous avez besoin d'une requête. Hibernate supporte un langage de requêtes orientées objet, facile à utiliser mais puissant. Pour la création de requêtes par programmation, Hibernate supporte une fonction de requêtage sophistiquée Criteria et Example (QBC et QBE). Vous pouvez aussi exprimer votre requête dans le SQL natif de votre base de données, avec un support optionnel de Hibernate pour la conversion des ensembles de résultats en objets.
Les requêtes HQL et SQL natives sont représentées avec une instance de org.hibernate.Query
. L'interface offre des méthodes pour la liaison des paramètres, la gestion des ensembles de résultats, et pour l'exécution de la requête réelle. Vous obtenez toujours une Query
en utilisant la Session
courante :
List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
Une requête est généralement exécutée en invoquant list()
, le résultat de la requête sera chargée complètement dans une collection en mémoire. Les instances d'entités récupérées par une requête sont dans un état persistant. La méthode uniqueResult()
offre un raccourci si vous savez que votre requête retournera un seul objet. Notez que les requêtes qui utilisent le chargement agressif de collections retournent habituellement des copies des objets racine (mais avec leurs collections initialisées). Vous pouvez simplement filtrer ces copies via un Set
.
Parfois, vous serez en mesure d'obtenir de meilleures performances en exécutant la requête avec la méthode iterate()
. En général, ce sera uniquement le cas si vous attendez que les instances réelles d'entité retournées par la requête, soient déjà chargées dans la session ou le cache de second niveau. Si elles ne sont pas déjà cachées, iterate()
sera plus lent que list()
et pourrait nécessiter plusieurs accès à la base de données pour une simple requête, généralement 1 pour le select initial qui retourne seulement les identifiants, et n selects supplémentaires pour initialiser les instances réelles.
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // fetch the object
// something we couldnt express in the query
if ( qux.calculateComplicatedAlgorithm() ) {
// delete the current instance
iter.remove();
// dont need to process the rest
break;
}
}
Les requêtes d'Hibernate retournent parfois des tuples d'objets, auquel cas chaque tuple est retourné comme un tableau :
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}
Certaines requêtes peuvent spécifier une propriété de classe dans la clause select
. Elles peuvent même appeler des fonctions d'aggrégat SQL. Les propriétés ou les aggrégats sont considérés comme des résultats "scalaires" (et non des entités dans un état persistant).
Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
Des méthodes de Query
sont fournies pour lier des valeurs à des paramètres nommés ou à des paramètres de style JDBC ?
. Contrairement à JDBC, les numéros des paramètres de Hibernate commencent à zéro. Les paramètres nommés sont des identifiants de la forme :nom
dans la chaîne de caractères de la requête. Les avantages des paramètres nommés sont :
les paramètres nommés sont insensibles à l'ordre dans lequel ils apparaissent dans la chaîne de la requête
ils peuvent apparaître plusieurs fois dans la même requête
ils sont auto-documentés
//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();
Si vous avez besoin de spécifier des liens sur votre ensemble de résultats (le nombre maximum de lignes et/ou la première ligne que vous voulez récupérer) vous utiliserez des méthodes de l'interface Query
:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate sait comment traduire cette requête de limite en SQL natif pour votre SGBD.
Si votre connecteur JDBC supporte les ResultSet
s "scrollables", l'interface Query
peut être utilisée pour obtenir un objet ScrollableResults
, qui permettra une navigation flexible dans les résultats de la requête.
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE
> i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()
Notez qu'une connexion ouverte (et un curseur) est requise pour cette fonctionnalité, utilisez setMaxResult()
/setFirstResult()
si vous avez besoin d'une fonctionnalité de pagination hors ligne.
Vous pouvez aussi définir des requêtes nommées dans le document de mapping. Souvenez-vous d'utiliser une section CDATA
si votre requête contient des caractères qui pourraient être interprétés comme des éléments XML.
<query name="ByNameAndMaximumWeight"
><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight
> ?
] ]></query
>
La liaison de paramètres et l'exécution sont effectués par programmation :
Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
Notez que le code réel du programme est indépendant du langage de requête utilisé, vous pouvez aussi définir des requêtes SQL natives dans les méta-données, ou migrer des requêtes existantes vers Hibernate en les plaçant dans les fichiers de mapping.
Notez aussi que la déclaration d'une requête dans un élément <hibernate-mapping>
nécessite un nom globalement unique pour la requête, alors que la déclaration d'une requête dans un élément <class>
est rendue unique de manière automatique par la mise en préfixe du nom entièrement qualifié de la classe, par exemple eg.Cat.ByNameAndMaximumWeight
.
Un filtre de collection est un type spécial de requête qui peut être appliqué à une collection persistante ou à un tableau. La chaîne de requêtes peut se référer à this
, correspondant à l'élément de la collection courant.
Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
La collection retournée est considérée comme un bag, et c'est une copie de la collection donnée. La collection originale n'est pas modifiée. C'est contraire à l'implication du nom "filtre"; mais cohérent avec le comportement attendu.
Observez que les filtres ne nécessitent pas une clause from
(bien qu'ils puissent en avoir une si besoin est). Les filtres ne sont pas limités à retourner des éléments de la collection eux-mêmes.
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();
Même une requête de filtre vide est utile, par exemple pour charger un sous-ensemble d'éléments dans une énorme collection :
Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();
HQL est extrêmement puissant, mais certains développeurs préfèrent construire des requêtes dynamiquement, en utilisant l'API orientée objet, plutôt que de construire des chaînes de requêtes. Hibernate fournit une API intuitive de requête Criteria
pour ces cas :
Criteria crit = session.createCriteria(Cat.class);
crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
The Criteria
and the associated Example
API are discussed in more detail in Chapitre 16, Requêtes par critères.
Vous pouvez exprimer une requête en SQL, en utilisant createSQLQuery()
et laisser Hibernate s'occuper du mappage des résultats vers des objets. Notez que vous pouvez à tout moment, appeler session.connection()
et utiliser directement la Connection
JDBC. Si vous choisissez d'utiliser l'API Hibernate, vous devez mettre les alias SQL entre accolades :
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list();
List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()
SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in Chapitre 17, SQL natif.
Les instances persistantes transactionnelles (c'est-à-dire des objets chargés, sauvegardés, créés ou requêtés par la Session
) peuvent être manipulés par l'application et tout changement vers l'état persistant sera persisté lorsque la Session
est "flushée" (traité plus tard dans ce chapitre). Il n'est pas nécessaire d'appeler une méthode particulière (comme update()
, qui a un but différent) pour rendre vos modifications persistantes. Donc la manière la plus directe de mettre à jour l'état d'un objet est de le charger avec load()
, et puis de le manipuler directement, tant que la Session
est ouverte :
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted
Parfois ce modèle de programmation est inefficace puisqu'il nécessiterait un SQL SELECT
(pour charger l'objet) et un SQL UPDATE
(pour persister son état mis à jour) dans la même session. Ainsi Hibernate offre une autre approche, en utilisant des instances détachées.
Hibernate does not offer its own API for direct execution of UPDATE
or DELETE
statements. Hibernate is a state management service, you do not have to think in statements to use it. JDBC is a perfect API for executing SQL statements, you can get a JDBC Connection
at any time by calling session.connection()
. Furthermore, the notion of mass operations conflicts with object/relational mapping for online transaction processing-oriented applications. Future versions of Hibernate can, however, provide special mass operation functions. See Chapitre 14, Traitement par lot for some possible batch operation tricks.
Beaucoup d'applications ont besoin de récupérer un objet dans une transaction, de l'envoyer à la couche interfacée avec l'utilisateur pour les manipulations, et de sauvegarder les changements dans une nouvelle transaction. Les applications qui utilisent cette approche dans un environnement à haute concurrence utilisent généralement des données versionnées pour assurer l'isolation des "longues" unités de travail.
Hibernate supporte ce modèle en permettant pour le rattachement d'instances détachées en utilisant des méthodes Session.update()
ou Session.merge()
:
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate
Si le Cat
avec l'identifiant catId
avait déjà été chargé par secondSession
lorsque l'application a essayé de le rattacher, une exception aurait été levée.
Utilisez update()
si vous êtes sûr que la session ne contient pas déjà une instance persistante avec le même identifiant, et merge()
si vous voulez fusionner vos modifications n'importe quand sans considérer l'état de la session. En d'autres termes, update()
est généralement la première méthode que vous devez appeler dans une session fraîche, pour vous assurer que le rattachement de vos instances détachées est la première opération qui est exécutée.
The application should individually update()
detached instances that are reachable from the given detached instance only if it wants their state to be updated. This can be automated using transitive persistence. See Section 10.11, « Persistance transitive » for more information.
La méthode lock()
permet aussi à une application de ré-associer un objet avec une nouvelle session. Cependant, l'instance détachée doit être non modifiée.
//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);
Notez que lock()
peut être utilisé avec différents LockMode
s, voir la documentation de l'API et le chapitre sur la gestion des transactions pour plus d'informations. Le rattachement n'est pas le seul cas d'utilisation pour lock()
.
Other models for long units of work are discussed in Section 12.3, « Contrôle de concurrence optimiste ».
Les utilisateurs d'Hibernate ont demandé une méthode dont l'intention générale serait soit de sauvegarder une instance éphémère en générant un nouvel identifiant, soit mettre à jour/rattacher les instances détachées associées à l'identifiant courant. La méthode saveOrUpdate()
implémente cette fonctionnalité.
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);
// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
L'usage et la sémantique de saveOrUpdate()
semble être confuse pour les nouveaux utilisateurs. Premièrement, aussi longtemps que vous n'essayez pas d'utiliser des instances d'une session dans une autre, vous ne devriez pas avoir besoin d'utiliser update()
, saveOrUpdate()
, ou merge()
. Certaines applications n'utiliseront jamais ces méthodes.
Généralement update()
ou saveOrUpdate()
sont utilisées dans le scénario suivant :
l'application charge un objet dans la première session
l'objet est passé à la couche utilisateur
certaines modifications sont effectuées sur l'objet
l'objet est retourné à la couche logique métier
l'application persiste ces modifications en appelant update()
dans une seconde session
saveOrUpdate()
s'utilise dans le cas suivant :
si l'objet est déjà persistant dans cette session, ne rien faire
si un autre objet associé à la session a le même identifiant, lever une exception
si l'objet n'a pas de propriété d'identifiant, appeler save()
si l'identifiant de l'objet a une valeur assignée à un objet nouvellement instancié, appeler save()
si l'objet est versionné (par <version>
ou <timestamp>
), et la valeur de la propriété de version est la même valeur que celle assignée à un objet nouvellement instancié, appeler save()
sinon mettre à jour l'objet avec update()
et merge()
est très différent :
s'il y a une instance persistante avec le même identifiant couramment associé à la session, copier l'état de l'objet donné dans l'instance persistante
s'il n'y a pas d'instance persistante associée à cette session, essayer de le charger à partir de la base de données, ou créer une nouvelle instance persistante
l'instance persistante est retournée
l'instance donnée ne devient pas associée à la session, elle reste détachée
Session.delete()
supprimera l'état d'un objet de la base de données. Bien sûr, votre application pourrait encore conserver une référence vers un objet effacé. Il est préférable de penser à delete()
comme rendant une instance persistante éphémère.
sess.delete(cat);
Vous pouvez effacer des objets dans l'ordre que vous voulez, sans risque de violations de contrainte de clef étrangère. Il est encore possible de violer une contrainte NOT NULL
sur une colonne de clef étrangère en effaçant des objets dans le mauvais ordre, par exemple si vous effacez le parent, mais oubliez d'effacer les enfants.
Il est occasionnellement utile de pouvoir prendre un graphe d'instances persistantes et de les rendre persistantes dans un entrepôt différent, sans regénérer les valeurs des identifiants.
//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//reconcile with a second database
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
Le ReplicationMode
détermine comment replicate()
traitera les conflits avec des lignes existantes dans la base de données.
ReplicationMode.IGNORE
- ignore l'objet s'il y a une ligne existante dans la base de données avec le même identifiant
ReplicationMode.OVERWRITE
- écrase n'importe quelle ligne existante dans la base de données avec le même identifiant
ReplicationMode.EXCEPTION
- lève une exception s'il y a une ligne dans la base de données avec le même identifiant
ReplicationMode.LATEST_VERSION
- écrase la ligne si son numéro de version est plus petit que le numéro de version de l'objet, sinon ignore l'objet
Les cas d'utilisation de cette fonctionnalité incluent la réconciliation de données entrées dans différentes base de données, l'extension des informations de configuration du système durant une mise à jour du produit, retour en arrière sur les changements effectués durant des transactions non-ACID, et plus.
De temps en temps la Session
exécutera les expressions SQL requises pour synchroniser l'état de la connexion JDBC avec l'état des objets retenus en mémoire. Ce processus, flush, survient par défaut aux points suivants :
avant certaines exécutions de requête
lors d'un appel à org.hibernate.Transaction.commit()
lors d'un appel à Session.flush()
Les expressions SQL sont effectuées dans l'ordre suivant :
insertion des entités, dans le même ordre que celui des objets correspondants sauvegardés par l'appel à Session.save()
mise à jour des entités
suppression des collections
suppression, mise à jour et insertion des éléments des collections
insertion des collections
suppression des entités, dans le même ordre que celui des objets correspondants qui ont été supprimés par l'appel de Session.delete()
Une exception est que des objets utilisant la génération native
d'identifiants sont insérés lorsqu'ils sont sauvegardés.
Excepté lorsque vous appelez flush()
explicitement, il n'y a absolument aucune garantie à propos de quand la Session
exécute les appels JDBC, seulement sur l'ordre dans lequel ils sont exécutés. Cependant, Hibernate garantit que Query.list(..)
ne retournera jamais de données périmées, ni des données fausses.
It is possible to change the default behavior so that flush occurs less frequently. The FlushMode
class defines three different modes: only flush at commit time when the Hibernate Transaction
API is used, flush automatically using the explained routine, or never flush unless flush()
is called explicitly. The last mode is useful for long running units of work, where a Session
is kept open and disconnected for a long time (see Section 12.3.2, « Les sessions longues et le versionnage automatique. »).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();
During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in Chapitre 12, Transactions et Accès concurrents.
Il est assez pénible de sauvegarder, supprimer, ou rattacher des objets un par un, surtout si vous traitez un graphe d'objets associés. Un cas courant est une relation parent/enfant. Considérez l'exemple suivant :
Si les enfants de la relation parent/enfant étaient des types de valeur (par exemple, une collection d'adresses ou de chaînes de caractères), leur cycle de vie dépendrait du parent et aucune action ne serait requise pour "cascader" facilement les changements d'état. Si le parent est sauvegardé, les objets enfants de type de valeur sont sauvegardés également, si le parent est supprimé, les enfants sont supprimés, etc. Ceci fonctionne même pour des opérations telles que la suppression d'un enfant de la collection ; Hibernate le détectera et étant donné que les objets de type de valeur ne peuvent pas avoir de références partagées, il supprimera l'enfant de la base de données.
Maintenant considérez le même scénario avec un parent dont les objets enfants sont des entités, et non des types de valeur (par exemple, des catégories et des objets, ou un parent et des chatons). Les entités ont leur propre cycle de vie, supportent les références partagées (donc supprimer une entité de la collection ne signifie pas qu'elle peut être supprimée), et il n'y a par défaut pas de cascade d'état d'une entité vers n'importe quelle entité associée. Hibernate n'implémente pas la persistance par accessibilité par défaut.
Pour chaque opération basique de la session Hibernate - incluant persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()
- il y a un style de cascade correspondant. Respectivement, les styles de cascade s'appellent persist, merge, save-update, delete, lock, refresh, evict, replicate
. Si vous voulez qu'une opération soit cascadée le long d'une association, vous devez l'indiquer dans le document de mappage. Par exemple :
<one-to-one name="person" cascade="persist"/>
Les styles de cascade peuvent être combinés :
<one-to-one name="person" cascade="persist,delete,lock"/>
Vous pouvez même utiliser cascade="all"
pour spécifier que toutes les opérations devraient être cascadées le long de l'association. La valeur par défaut cascade="none"
spécifie qu'aucune opération ne sera cascadée.
Un style de cascade spécial, delete-orphan
, s'applique seulement aux associations un-à-plusieurs, et indique que l'opération delete()
devrait être appliquée à tout enfant supprimé de l'association.
Recommandations :
Cela n'a généralement aucun sens d'activer la cascade sur une association <many-to-one>
ou <many-to-many>
. Les cascades sont souvent utiles pour des associations <one-to-one>
et <one-to-many>
.
Si la durée de vie de l'objet enfant est liée à la durée de vie de l'objet parent, faites-en un objet du cycle de vie en spécifiant cascade="all,delete-orphan"
.
Sinon, vous pourriez ne pas avoir besoin de cascade du tout. Mais si vous pensez que vous travaillerez souvent avec le parent et les enfants ensemble dans la même transaction, et que vous voulez vous éviter quelques frappes, considérez l'utilisation de cascade="persist,merge,save-update"
.
Mapper une association (soit une simple association valuée, soit une collection) avec cascade="all"
marque l'association comme une relation de style parent/enfant où la sauvegarde/mise à jour/suppression du parent entraîne la sauvegarde/mise à jour/suppression de l'enfant ou des enfants.
Par ailleurs, une simple référence à un enfant d'un parent persistant aura pour conséquence la sauvegarde/mise à jour de l'enfant. Cette métaphore est cependant incomplète. Un enfant qui devient non référencé par son parent n'est pas automatiquement supprimé, sauf dans le cas d'une association <one-to-many>
mappée avec cascade="delete-orphan"
. La sémantique précise des opérations de cascade pour une relation parent/enfant est la suivante :
Si un parent est passé à persist()
, tous les enfant sont passés à persist()
Si un parent est passé à merge()
, tous les enfants sont passés à merge()
Si un parent est passé à save()
, update()
ou saveOrUpdate()
, tous les enfants sont passés à saveOrUpdate()
Si un enfant détaché ou éphémère devient référencé par un parent persistant, il est passé à saveOrUpdate()
Si un parent est supprimé, tous les enfants sont passés à delete()
Si un enfant est déréférencé par un parent persistant, rien de spécial n'arrive - l'application devrait explicitement supprimer l'enfant si nécessaire - à moins que cascade="delete-orphan"
soit paramétré, auquel cas l'enfant "orphelin" est supprimé.
Enfin, la cascade des opérations peut être effectuée sur un graphe donné lors de l'appel de l'opération or lors du flush suivant. Toutes les opérations, lorsqu'elles sont cascadées, le sont sur toutes les entités associées accessibles lorsque l'opération est exécutée. Cependant save-upate
et delete-orphan
sont cascadés à toutes les entités associées accessibles lors du flush de la Session
.
Hibernate requiert un modèle de méta-niveau très riche de toutes les entités et types valués. De temps en temps, ce modèle est très utile à l'application elle même. Par exemple, l'application pourrait utiliser les méta-données de Hibernate pour implémenter un algorithme de copie en profondeur "intelligent" qui comprendrait quels objets devraient être copiés (par exemple les types de valeur mutables) et lesquels ne devraient pas l'être (par exemple les types de valeurs immutables et, éventuellement, les entités associées).
Hibernate expose les méta-données via les interfaces ClassMetadata
et CollectionMetadata
et la hiérarchie Type
. Les instances des interfaces de méta-données peuvent être obtenues à partir de la SessionFactory
.
Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// get a Map of all properties which are not collections or associations
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
namedValues.put( propertyNames[i], propertyValues[i] );
}
}
Copyright © 2004 Red Hat, Inc.