Nous avons déjà passé du temps à discuter des collections. Dans cette section, nous allons traiter du comportement des collections à l'exécution.
Hibernate définit trois types de collections :
les collections de valeurs
les associations un-vers-plusieurs
les associations plusieurs-vers-plusieurs
Cette classification distingue les différentes relations entre les tables et les clés étrangères mais ne nous apprend rien de ce que nous devons savoir sur le modèle relationnel. Pour comprendre parfaitement la structure relationnelle et les caractéristiques des performances, nous devons considérer la structure de la clé primaire qui est utilisée par Hibernate pour mettre à jour ou supprimer les éléments des collections. Celà nous amène aux classifications suivantes :
collections indexées
sets
bags
Toutes les collections indexées (maps, lists, arrays) ont une clé primaire constituée des colonnes clé (<key>
) et <index>
. Avec ce type de clé primaire, la mise à jour de collection est en général très performante - la clé primaire peut être indexées
efficacement et un élément particulier peut être localisé efficacement lorsqu'Hibernate essaie de le mettre à jour ou de le
supprimer.
Sets have a primary key consisting of <key>
and element columns. This may be less efficient for some types of collection element, particularly composite elements or
large text or binary fields; the database may not be able to index a complex primary key as efficiently. On the other hand,
for one to many or many to many associations, particularly in the case of synthetic identifiers, it is likely to be just as
efficient. (Side-note: if you want SchemaExport
to actually create the primary key of a <set>
for you, you must declare all columns as not-null="true"
.)
Le mapping à l'aide d'<idbag>
définit une clé de substitution ce qui leur permet d'être très efficaces lors de la mise à jour. En fait il s'agit du meilleur
cas de mise à jour d'une collection
Le pire cas intervient pour les Bags. Dans la mesure où un bag permet la duplications des éléments et n'a pas de colonne d'index,
aucune clé primaire ne peut être définie. Hibernate n'a aucun moyen de distinguer des enregistrements dupliqués. Hibernate
résout ce problème en supprimant complètement les enregistrements (via un simple DELETE
), puis en recréant la collection chaque fois qu'elle change. Ce qui peut être très inefficace.
Notez que pour une relation un-vers-plusieurs, la "clé primaire" peut ne pas être la clé primaire de la table en base de données - mais même dans ce cas, la classification ci-dessus reste utile (Elle explique comment Hibernate "localise" chaque enregistrement de la collection).
La discussion précédente montre clairement que les collections indexées et (la plupart du temps) les sets, permettent de réaliser le plus efficacement les opérations d'ajout, de suppression ou de modification d'éléments.
Il existe un autre avantage qu'ont les collections indexées sur les Sets dans le cadre d'une association plusieurs vers plusieurs
ou d'une collection de valeurs. A cause de la structure inhérente d'un Set
, Hibernate n'effectue jamais d'UPDATE
quand un enregistrement est modifié. Les modifications apportées à un Set
se font via un INSERT
et DELETE
(de chaque enregistrement). Une fois de plus, ce cas ne s'applique pas aux associations un vers plusieurs.
Après s'être rappelé que les tableaux ne peuvent pas être chargés tardivement, nous pouvons conclure que les lists, les maps et les idbags sont les types de collections (non inversées) les plus performants, avec les sets pas loin derrières. Les sets son le type de collection le plus courant dans les applications Hibernate. Cela est du au fait que la sémantique des "set" est la plus naturelle dans le modèle relationnel.
Cependant, dans des modèles objet bien conçus avec Hibernate, on voit souvent que la plupart des collections sont en fait
des associations "un-vers-plusieurs" avec inverse="true"
. Pour ces associations, les mises à jour sont gérées au niveau de l'association "plusieurs-vers-un" et les considérations
de performance de mise à jour des collections ne s'appliquent tout simplement pas dans ces cas là.
Avant que vous n'oubliez les bags pour toujours, il y a un cas précis où les bags (et les lists) sont bien plus performants
que les sets. Pour une collection marquée comme inverse="true"
(le choix le plus courant pour un relation un vers plusieurs bidirectionnelle), nous pouvons ajouter des éléments à un bag
ou une list sans avoir besoin de l'initialiser (fetch) les éléments du sac! Ceci parce que Collection.add()
ou Collection.addAll()
doit toujours retourner vrai pour un bag ou une List
(contrairement au Set
). Cela peut rendre le code suivant beaucoup plus rapide.
Parent p = (Parent) sess.load(Parent.class, id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); //no need to fetch the collection! sess.flush();
Parfois, effacer les éléments d'une collection un par un peut être extrêmement inefficace. Hibernate n'est pas totalement
stupide, il sait qu'il ne faut pas le faire dans le cas d'une collection complètement vidée (lorsque vous appellez list.clear()
, par exemple). Dans ce cas, Hibernate fera un simple DELETE
et le travail est fait !
Supposons que nous ajoutions un élément dans une collection de taille vingt et que nous enlevions ensuite deux éléments. Hibernate
effectuera un INSERT
puis deux DELETE
(à moins que la collection ne soit un bag). Ce qui est souhaitable.
Cependant, supposons que nous enlevions dix huit éléments, laissant ainsi deux éléments, puis que nous ajoutions trois nouveaux éléments. Il y a deux moyens de procéder.
effacer dix huit enregistrements un à un puis en insérer trois
effacer la totalité de la collection (en un DELETE
SQL) puis insérer les cinq éléments restant un à un
Hibernate n'est pas assez intelligent pour savoir que, dans ce cas, la seconde méthode est plus rapide (Il plutôt heureux qu'Hibernate ne soit pas trop intelligent ; un tel comportement pourrait rendre l'utilisation de triggers de bases de données plutôt aléatoire, etc...).
Heureusement, vous pouvez forcer ce comportement lorsque vous le souhaitez, en liberant (c'est-à-dire en déréférençant) la collection initiale et en retournant une collection nouvellement instanciée avec les éléments restants. Ceci peut être très pratique et très puissant de temps en temps.
Bien sûr, la suppression en un coup ne s'applique pas pour les collections qui sont mappées avec inverse="true"
.