Améliorer les performances Stratégies de chargement Une stratégie de chargement est une stratégie qu'Hibernate va utiliser pour récupérer des objets associés si l'application à besoin de naviguer à travers une association. Les stratégies de chargement peuvent être déclarées dans les méta-données de l'outil de mapping objet relationnel ou surchargées par une requête de type HQL ou Criteria particulière. Hibernate3 définit les stratégies de chargement suivantes : Chargement par jointure - Hibernate récupère l'instance associée ou la collection dans un même SELECT, en utilisant un OUTER JOIN. Chargement par select - Un second SELECT est utilisé pour récupérer l'instance associée ou la collection. A moins que vous ne désactiviez explicitement le chargement tardif en spécifiant lazy="false", ce second select ne sera exécuté que lorsque vous accéderez réellement à l'association. Chargement par sous-select - Un second SELECT est utilisé pour récupérer les associations pour toutes les entités récupérées dans une requête ou un chargement préalable. A moins que vous ne désactiviez explicitement le chargement tardif en spécifiant lazy="false", ce second select ne sera exécuté que lorsque vous accéderez réellement à l'association. Chargement par lot - Il s'agit d'une stratégie d'optimisation pour le chargement par select - Hibernate récupère un lot d'instances ou de collections en un seul SELECT en spécifiant une liste de clé primaire ou de clé étrangère. Hibernate fait également la distinction entre : Chargement immédiat - Une association, une collection ou un attribut est chargé immédiatement lorsque l'objet auquel appartient cet élément est chargé. Chargement tardif d'une collection - Une collection est chargée lorque l'application invoque une méthode sur cette collection (il s'agit du mode de chargement par défaut pour les collections). Chargement "super tardif" d'une collection - les éléments de la collection sont récupérés individuellement depuis la base de données lorsque nécessaire. Hibernate essaie de ne pas charger toute la collection en mémoire sauf si cela est absolument nécessaire (bien adapté aux très grandes collections). Chargement par proxy - une association vers un seul objet est chargée lorsqu'une méthode autre que le getter sur l'identifiant est appelée sur l'objet associé. Chargement "sans proxy" - une association vers un seul objet est chargée lorsque l'on accède à cet objet. Par rapport au chargement par proxy, cette approche est moins tardif (l'association est quand même chargée même si on n'accède qu'à l'identifiant) mais plus transparente car il n'y a pas de proxy visible dans l'application. Cette approche requiert une instrumentation du bytecode à la compilation et est rarement nécessaire. Chargement tardif des attributs - Un attribut ou un objet associé seul est chargé lorsque l'on y accède. Cette approche requiert une instrumentation du bytecode à la compilation et est rarement nécessaire. Nous avons ici deux notions orthogonales : quand l'association est chargée et comment (quelle requête SQL est utilisée). Il ne faut pas confondre les deux. Le mode de chargement est utilisé pour améliorer les performances. On peut utiliser le mode tardif pour définir un contrat sur quelles données sont toujours accessibles sur une instance détachée d'une classe particulière. Travailler avec des associations chargées tardivement Par défaut, Hibernate3 utilise le chargement tardif par select pour les collections et le chargement tardif par proxy pour les associations vers un seul objet. Ces valeurs par défaut sont valables pour la plupart des associations dans la plupart des applications. Note : si vous définissez hibernate.default_batch_fetch_size, Hibernate va utiliser l'optimisation du chargement par lot pour le chargement tardif (cette optimisation peut aussi être activée à un niveau de granularité plus fin). Cependant, le chargement tardif pose un problème qu'il faut connaitre. L'accès à une association définie comme "tardive", hors du contexte d'une session hibernate ouverte, va conduire à une exception. Par exemple : Etant donné que la collection des permissions n'a pas été initialisée avant que la Session soit fermée, la collection n'est pas capable de se charger. Hibernate ne supporte pas le chargement tardif pour des objets détachés. La solution à ce problème est de déplacer le code qui lit la collection avant le "commit" de la transaction. Une autre alternative est d'utiliser une collection ou une association non "tardive" en spécifiant lazy="false" dans le mapping de l'association. Cependant il est prévu que le chargement tardif soit utilisé pour quasiment toutes les collections ou associations. Si vous définissez trop d'associtions non "tardives" dans votre modèle objet, Hibernate va finir par devoir charger toute la base de données en mémoire à chaque transaction ! D'un autre côté, on veut souvent choisir un chargement par jointure (qui est par défaut non tardif) à la place du chargement par select dans une transaction particulière. Nous allons maintenant voir comment adapter les stratégies de chargement. Dans Hibernate3 les mécanismes pour choisir une stratégie de chargement sont identiques que l'on ait une association vers un objet simple ou vers une collection. Personnalisation des stratégies de chargement Le chargement par select (mode par défaut) est très vulnérable au problème du N+1 selects, du coup vous pouvez avoir envie d'activer le chargement par jointure dans les fichiers de mapping : ]]> La stratégie de chargement définie à l'aide du mot fetch dans les fichiers de mapping affecte : La récupération via get() ou load() La récupération implicite lorsque l'on navigue à travers une association Les requêtes de type Criteria Les requêtes HQL si l'on utilise le chargement par subselect Quelle que soit la stratégie de chargement que vous utilisez, la partie du graphe d'objets qui est définie comme non "tardive" sera chargée en mémoire. Cela peut mener à l'exécution de plusieurs selects successifs pour une seule requête HQL. On n'utilise pas souvent les documents de mapping pour adapter le chargement. Au lieu de cela, on conserve le comportement par défaut et on le surcharge pour une transaction particulière en utilisant left join fetch dans les requêtes HQL. Cela indique à hibernate à Hibernate de charger l'association de manière agressive lors du premier select en utilisant une jointure externe. Dans l'API Criteria vous pouvez utiliser la méthode setFetchMode(FetchMode.JOIN) Si vous ne vous sentez pas prêt à modifier la stratégie de chargement utilisé par get() ou load(), vous pouvez juste utiliser une requête de type Criteria comme par exemple : (Il s'agit de l'équivalent pour Hibernate de ce que d'autres outils de mapping appellent un "fetch plan" ou "plan de chargement") Une autre manière complètement différente d'éviter le problème des N+1 selects est d'utiliser le cache de second niveau. Proxys pour des associations vers un seul objet Le chargement tardif des collections est implémenté par Hibernate en utilisant ses propres implémentations pour des collections persistantes. Si l'on veut un chargement tardif pour des associations vers un seul objet métier il faut utiliser un autre mécanisme. L'entité qui est pointée par l'association doit être masquée derrière un proxy. Hibernate implémente l'initialisation tardive des proxys sur des objets persistents via une mise à jour à chaud du bytecode (à l'aide de l'excellente librairie CGLIB). Par défaut, Hibernate génère des proxys (au démarrage) pour toutes les classes persistantes et les utilise pour activer le chargement tardif des associations many-to-one et one-to-one. Le fichier de mapping peut déclarer une interface qui sera utilisée par le proxy d'interfaçage pour cette classe à l'aide de l'attribut proxy. Par défaut Hibernate utilises une sous classe de la classe persistante. Il faut que les classes pour lesquelles on ajoute un proxy implémentent un constructeur par défaut de visibilité au moins package. Ce constructeur est recommandé pour toutes les classes persistantes ! Il y a quelques précautions à prendre lorsque l'on étend cette approche à des classes polymorphiques, exemple : ...... ..... ]]> Tout d'abord, les instances de Cat ne pourront jamais être "castées" en DomesticCat, même si l'instance sous jacente est une instance de DomesticCat : Deuxièmement, il est possible de casser la notion d'== des proxy. Cette situation n'est pas si mauvaise qu'il n'y parait. Même si nous avons deux références à deux objets proxys différents, l'instance de base sera quand même le même objet : Troisièmement, vous ne pourrez pas utiliser un proxy CGLIB pour une classe final ou pour une classe contenant la moindre méthode final. Enfin, si votre objet persistant obtient une ressource à l'instanciation (par example dans les initialiseurs ou dans le contructeur par défaut), alors ces ressources seront aussi obtenues par le proxy. La classe proxy est vraiment une sous classe de la classe persistante. Ces problèmes sont tous dus aux limitations fondamentales du modèle d'héritage unique de Java. Si vous souhaitez éviter ces problèmes, vos classes persistantes doivent chacune implémenter une interface qui déclare ses méthodes métier. Vous devriez alors spécifier ces interfaces dans le fichier de mapping : ...... ..... ]]> CatImpl implémente l'interface Cat et DomesticCatImpl implémente l'interface DomesticCat. Ainsi, des proxys pour les instances de Cat et DomesticCat pourraient être retournées par load() ou iterate() (Notez que list() ne retourne généralement pas de proxy). Les relations sont aussi initialisées tardivement. Ceci signifie que vous devez déclarer chaque propriété comme étant de type Cat, et non CatImpl. Certaines opérations ne nécessitent pas l'initialisation du proxy equals(), si la classe persistante ne surcharge pas equals() hashCode(), si la classe persistante ne surcharge pas hashCode() Le getter de l'identifiant Hibernate détectera les classes qui surchargent equals() ou hashCode(). Eh choisissant lazy="no-proxy" au lieu de lazy="proxy" qui est la valeur par défaut, il est possible d'éviter les problèmes liés au transtypage. Il faudra alors une instrumentation du bytecode à la compilation et toutes les opérations résulterons immédiatement en une initialisation du proxy. Initialisation des collections et des proxys Une exception de type LazyInitializationException sera renvoyée par hibernate si une collection ou un proxy non initialisé est accédé en dehors de la portée de la Session, e.g. lorsque l'entité à laquelle appartient la collection ou qui a une référence vers le proxy est dans l'état "détachée". Parfois, nous devons nous assurer qu'un proxy ou une collection est initialisée avant de fermer la Session. Bien sûr, nous pouvons toujours forcer l'initialisation en appelant par exemple cat.getSex() ou cat.getKittens().size(). Mais ceci n'est pas très lisible pour les personnes parcourant le code et n'est pas très générique. Les méthodes statiques Hibernate.initialize() et Hibernate.isInitialized() fournissent à l'application un moyen de travailler avec des proxys ou des collections initialisés. Hibernate.initialize(cat) forcera l'initialisation d'un proxy de cat, si tant est que sa Session est ouverte. Hibernate.initialize( cat.getKittens() ) a le même effet sur la collection kittens. Une autre option est de conserver la Session ouverte jusqu'à ce que toutes les collections et tous les proxys aient été chargés. Dans certaines architectures applicatives, particulièrement celles ou le code d'accès aux données via hiberante et le code qui utilise ces données sont dans des couches applicatives différentes ou des processus physiques différents, il peut devenir problématique de garantir que la Session est ouverte lorsqu'une collection est initialisée. Il y a deux moyens de traiter ce problème : Dans une application web, un filtre de servlet peut être utilisé pour fermer la Session uniquement lorsque la requête a été entièrement traitée, lorsque le rendu de la vue est fini (il s'agit du pattern Open Session in View). Bien sûr, cela demande plus d'attention à la bonne gestion des exceptions de l'application. Il est d'une importance vitale que la Session soit fermée et la transaction terminée avant que l'on rende la main à l'utilisateur même si une exception survient durant le traitement de la vue. Voir le wiki Hibernate pour des exemples sur le pattern "Open Session in View". Dans une application avec une couche métier séparée, la couche contenant la logique métier doit "préparer" toutes les collections qui seront nécessaires à la couche web avant de retourner les données. Cela signifie que la couche métier doit charger toutes les données et retourner toutes les données déjà initialisées à la couche de présentation/web pour un cas d'utilisation donné. En général l'application appelle la méthode Hibernate.initialize() pour chaque collection nécessaire dans la couche web (cet appel doit être fait avant la fermeture de la session) ou bien récupère les collections de manière agressive à l'aide d'une requête HQL avec une clause FETCH ou à l'aide du mode FetchMode.JOIN pour une requête de type Criteria. Cela est en général plus facile si vous utilisez le pattern Command plutôt que Session Facade. Vous pouvez également attacher à une Session un objet chargé au préalable à l'aide des méthodes merge() ou lock() avant d'accéder aux collections (ou aux proxys) non initialisés. Non, Hibernate ne fait pas, et ne doit pas faire, cela automatiquement car cela pourrait introduire une sémantique transactionnelle ad hoc. Parfois, vous ne voulez pas initialiser une grande collection mais vous avez quand même besoin d'informations sur elle (comme sa taille) ou un sous ensemble de ses données Vous pouvez utiliser un filtre de collection pour récupérer sa taille sans l'initialiser : La méthode createFilter() est également utilisée pour récupérer de manière efficace des sous ensembles d'une collection sans avoir besoin de l'initialiser dans son ensemble. Utiliser le chargement par lot Pour améliorer les performances, Hibernate peut utiliser le chargement par lot ce qui veut dire qu'Hibernate peut charger plusieurs proxys (ou collections) non initialisés en une seule requête lorsque l'on accède à l'un de ces proxys. Le chargement par lot est une optimisation intimement liée à la stratégie de chargement tardif par select. Il y a deux moyens d'activer le chargement par lot : au niveau de la classe et au niveau de la collection. Le chargement par lot pour les classes/entités est plus simple à comprendre. Imaginez que vous ayez la situation suivante à l'exécution : vous avez 25 instances de Cat chargées dans une Session, chaque Cat a une référence à son owner, une Person. La classe Person est mappée avec un proxy, lazy="true". Si vous itérez sur tous les cats et appelez getOwner() sur chacun d'eux, Hibernate exécutera par défaut 25 SELECT, pour charger les owners (initialiser le proxy). Vous pouvez paramétrer ce comportement en spécifiant une batch-size (taille du lot) dans le mapping de Person : ...]]> Hibernate exécutera désormais trois requêtes, en chargeant respectivement 10, 10, et 5 entités. Vous pouvez aussi activer le chargement par lot pour les collections. Par exemple, si chaque Person a une collection chargée tardivement de Cats, et que 10 personnes sont actuellement chargées dans la Session, itérer sur toutes les persons générera 10 SELECTs, un pour chaque appel de getCats(). Si vous activez le chargement par lot pour la collection cats dans le mapping de Person, Hibernate pourra précharger les collections : ... ]]> Avec une taille de lot (batch-size) de 8, Hibernate chargera respectivement 3, 3, 3, et 1 collections en quatre SELECTs. Encore une fois, la valeur de l'attribut dépend du nombre de collections non initialisées dans une Session particulière. Le chargement par lot de collections est particulièrement utile si vous avez des arborescenses récursives d'éléments (typiquement, le schéma facture de matériels). (Bien qu'un sous ensemble ou un chemin matérialisé est sans doute une meilleure option pour des arbres principalement en lecture.) Utilisation du chargement par sous select Si une collection ou un proxy vers un objet doit être chargé, Hibernate va tous les charger en ré-exécutant la requête orignial dans un sous select. Cela fonctionne de la même manière que le chargement par lot sans la possibilité de fragmenter le chargement. Utiliser le chargement tardif des propriétés Hibernate3 supporte le chargement tardif de propriétés individuelles. La technique d'optimisation est également connue sous le nom de fetch groups (groupes de chargement). Il faut noter qu'il s'agit principalement d'une fonctionnalité marketing car en pratique l'optimisation de la lecture d'un enregistrement est beaucoup plus importante que l'optimisation de la lecture d'une colonne. Cependant, la restriction du chargement à certaines colonnes peut être pratique dans des cas extrèmes, lorsque des tables "legacy" possèdent des centaines de colonnes et que le modèle de données ne peut pas être amélioré. Pour activer le chargement tardif d'une propriété, il faut mettre l'attribut lazy sur une propriété particulière du mapping : ]]> Le chargement tardif des propriétés requiert une instrumentation du bytecode lors de la compilation ! Si les classes persistantes ne sont pas instrumentées, Hibernate ignorera de manière silencieuse le mode tardif et retombera dans le mode de chargement immédiat. Pour l'instrumentation du bytecode vous pouvez utiliser la tâche Ant suivante : ]]> Une autre façon (meilleure ?) pour éviter de lire plus de colonnes que nécessaire au moins pour des transactions en lecture seule est d'utiliser les fonctionnalités de projection des requêtes HQL ou Criteria. Cela évite de devoir instrumenter le bytecode à la compilation et est certainement une solution préférable. Vous pouvez forcer le mode de chargement agressif des propriétés en utilisant fetch all properties dans les requêts HQL. Le cache de second niveau Une Session Hibernate est un cache de niveau transactionnel des données persistantes. Il est possible de configurer un cache de cluster ou de JVM (de niveau SessionFactory pour être exact) défini classe par classe et collection par collection. Vous pouvez même utiliser votr choix de cache en implémentant le pourvoyeur (provider) associé. Faites attention, les caches ne sont jamais avertis des modifications faites dans la base de données par d'autres applications (ils peuvent cependant être configurés pour régulièrement expirer les données en cache). Par défaut, Hibernate utilise EHCache comme cache de niveau JVM (le support de JCS est désormais déprécié et sera enlevé des futures versions d'Hibernate). Vous pouvez choisir une autre implémentation en spécifiant le nom de la classe qui implémente org.hibernate.cache.CacheProvider en utilisant la propriété hibernate.cache.provider_class. Fournisseur de cache Cache Classe pourvoyeuse Type Support en Cluster Cache de requêtes supporté Hashtable (ne pas utiliser en production) org.hibernate.cache.HashtableCacheProvider mémoire oui EHCache org.hibernate.cache.EhCacheProvider mémoire, disque oui OSCache org.hibernate.cache.OSCacheProvider mémoire, disque oui SwarmCache org.hibernate.cache.SwarmCacheProvider en cluster (multicast ip) oui (invalidation de cluster) JBoss TreeCache org.hibernate.cache.TreeCacheProvider en cluster (multicast ip), transactionnel oui (replication) oui (horloge sync. nécessaire)
Mapping de Cache L'élément <cache> d'une classe ou d'une collection à la forme suivante : ]]> usage (requis) spécifie la stratégie de cache : transactionel, lecture-écriture, lecture-écriture non stricte ou lecture seule region (optionnel, par défaut il s'agit du nom de la classe ou du nom de role de la collection) spécifie le nom de la région du cache de second niveau include (optionnel, par défaut all) non-lazy spécifie que les propriétés des entités mappées avec lazy="true" ne doivent pas être mises en cache lorsque le chargement tardif des attributs est activé. Alternativement (voir préférentiellement), vous pouvez spécifier les éléments <class-cache> et <collection-cache> dans hibernate.cfg.xml. L'attribut usage spécifie une stratégie de concurrence d'accès au cache. Strategie : lecture seule Si votre application a besoin de lire mais ne modifie jamais les instances d'une classe, un cache read-only peut être utilisé. C'est la stratégie la plus simple et la plus performante. Elle est même parfaitement sûre dans un cluster. .... ]]> Stratégie : lecture/écriture Si l'application a besoin de mettre à jour des données, un cache read-write peut être approprié. Cette stratégie ne devrait jamais être utilisée si votre application nécessite un niveau d'isolation transactionnelle sérialisable. Si le cache est utilisé dans un environnement JTA, vous devez spécifier hibernate.transaction.manager_lookup_class, fournissant une stratégie pour obtenir le TransactionManager JTA. Dans d'autres environnements, vous devriez vous assurer que la transation est terminée à l'appel de Session.close() ou Session.disconnect(). Si vous souhaitez utiliser cette stratégie dans un cluster, vous devriez vous assurer que l'implémentation de cache utilisée supporte le vérrouillage. Ce que ne font pas les pourvoyeurs caches fournis. .... .... ]]> Stratégie : lecture/écriture non stricte Si l'application besoin de mettre à jour les données de manière occasionnelle (qu'il est très peu probable que deux transactions essaient de mettre à jour le même élément simultanément) et qu'une isolation transactionnelle stricte n'est pas nécessaire, un cache nonstrict-read-write peut être approprié. Si le cache est utilisé dans un environnement JTA, vous devez spécifier hibernate.transaction.manager_lookup_class. Dans d'autres environnements, vous devriez vous assurer que la transation est terminée à l'appel de Session.close() ou Session.disconnect() Stratégie : transactionelle La stratégie de cache transactional supporte un cache complètement transactionnel comme, par exemple, JBoss TreeCache. Un tel cache ne peut être utilisé que dans un environnement JTA et vous devez spécifier hibernate.transaction.manager_lookup_class. Aucun des caches livrés ne supporte toutes les stratégies de concurrence. Le tableau suivant montre quels caches sont compatibles avec quelles stratégies de concurrence. Stratégie de concurrence du cache Cache read-only (lecture seule) nonstrict-read-write (lecture-écriture non stricte) read-write (lecture-ériture) transactional (transactionnel) Hashtable (ne pas utilser en production) oui oui oui EHCache oui oui oui OSCache oui oui oui SwarmCache oui oui JBoss TreeCache oui oui
Gérer les caches A chaque fois que vous passez un objet à la méthode save(), update() ou saveOrUpdate() et à chaque fois que vous récupérez un objet avec load(), get(), list(), iterate() or scroll(), cet objet est ajouté au cache interne de la Session. Lorsqu'il y a un appel à la méthode flush(), l'état de cet objet va être synchronisé avec la base de données. Si vous ne voulez pas que cette synchronisation ait lieu ou si vous traitez un grand nombre d'objets et que vous avez besoin de gérer la mémoire de manière efficace, vous pouvez utiliser la méthode evict() pour supprimer l'objet et ses collections dépendantes du cache de la session La Session dispose aussi de la méthode contains() pour déterminer si une instance appartient au cache de la session. Pour retirer tous les objets du cache session, appelez Session.clear() Pour le cache de second niveau, il existe des méthodes définies dans SessionFactory pour retirer des instances du cache, la classe entière, une instance de collection ou le rôle entier d'une collection. Le CacheMode contrôle comme une session particulière interragit avec le cache de second niveau CacheMode.NORMAL - lit et écrit les items dans le cache de second niveau CacheMode.GET - lit les items dans le cache de second niveau mais ne les écrit pas sauf dans le cache d'une mise à jour d'une donnée CacheMode.PUT - écrit les items dans le cache de second niveau mais ne les lit pas dans le cache de second niveau CacheMode.REFRESH - écrit les items dans le cache de second niveau mais ne les lit pas dans le cache de second niveau, outrepasse l'effet dehibernate.cache.use_minimal_puts, en forçant un rafraîchissement du cache de second niveau pour chaque item lu dans la base Pour parcourir le contenu du cache de second niveau ou la région du cache dédiée au requêtes, vous pouvez utiliser l'API Statistics API: Vous devez pour cela activer les statistiques et optionnellement forcer Hibernate à conserver les entrées dans le cache sous un format plus compréhensible pour l'utilisateur : Le cache de requêtes Les résultats d'une requête peuvent aussi être placés en cache. Ceci n'est utile que pour les requêtes qui sont exécutées avec les mêmes paramètres. Pour utiliser le cache de requêtes, vous devez d'abord l'activer : Ce paramètre amène la création de deux nouvelles régions dans le cache, une qui va conserver le résultat des requêtes mises en cache (org.hibernate.cache.StandardQueryCache) et l'autre qui va conserver l'horodatage des mises à jour les plus récentes effectuées sur les tables requêtables (org.hibernate.cache.UpdateTimestampsCache). Il faut noter que le cache de requête ne conserve pas l'état des entités, il met en cache uniquement les valeurs de l'identifiant et les valeurs de types de base (?). Le cache de requête doit toujours être utilisé avec le cache de second niveau pour être efficace. La plupart des requêtes ne retirent pas de bénéfice pas du cache, donc par défaut les requêtes ne sont pas mises en cache. Pour activer le cache, appelez Query.setCacheable(true). Cet appel permet de vérifier si les résultats sont en cache ou non, voire d'ajouter ces résultats si la requête est exécutée. Si vous avez besoin de contrôler finement les délais d'expiration du cache, vous pouvez spécifier une région de cache nommée pour une requête particulière en appelant Query.setCacheRegion(). Si une requête doit forcer le rafraîchissement de sa région de cache, vous devez appeler Query.setCacheMode(CacheMode.REFRESH). C'est particulièrement utile lorsque les données peuvent avoir été mises à jour par un processus séparé (e.g. elles n'ont pas été modifiées par Hibernate). Cela permet à l'application de rafraîchir de manière sélective les résultats d'une requête particulière. Il s'agit d'une alternative plus efficace à l'éviction d'une région du cache à l'aide de la méthode SessionFactory.evictQueries(). Comprendre les performances des Collections Nous avons déjà passé du temps à discuter des collections. Dans cette section, nous allons traiter du comportement des collections à l'exécution. Classification 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. Les Sets ont une clé primaire composée de <key> et des colonnes représentant l'élément. Elle est donc moins efficace pour certains types de collections d'éléments, en particulier les éléments composites, les textes volumineux ou les champs binaires ; la base de données peut ne pas être capable d'indexer aussi efficacement une clé primaire aussi complexe. Cependant, pour les associations un-vers-plusieurs ou plusieurs-vers-plusieurs, spécialement lorsque l'on utilise des entités ayant des identifiants techniques, il est probable que cela soit aussi efficace (note : si vous voulez que SchemaExport créé effectivement la clé primaire d'un <set> pour vous, vous devez déclarer toutes les colonnes avec 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). Les lists, les maps, les idbags et les sets sont les collections les plus efficaces pour la mise à jour 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à. Les Bags et les lists sont les plus efficaces pour les collections inverse 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. Suppression en un coup 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". Moniteur de performance L'optimisation n'est pas d'un grand intérêt sans le suivi et l'accès aux données de performance. Hibernate fournit toute une panoplie de rapport sur ses opérations internes. Les statistiques dans Hibernate sont fournies par SessionFactory. Suivi d'une SessionFactory Vous pouvez accéder au métriques d'une SessionFactory de deux manières. La première option est d'appeler sessionFactory.getStatistics() et de lire ou d'afficher les Statistics vous même. Hibernate peut également utiliser JMX pour publier les métriques si vous activez le MBean StatisticsService. Vous pouvez activer un seul MBean pour toutes vos SessionFactory ou un par factory. Voici un code qui montre un exemple de configuration minimaliste : TODO: Cela n'a pas de sens : dans le premier cs on récupère et on utilise le MBean directement. Dans le second, on doit fournir le nom JNDI sous lequel est retenu la fabrique de session avant de l'utiliser. Pour cela il faut utiliser hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") Vous pouvez (dés)activer le suivi pour une SessionFactory au moment de la configuration en mettant hibernate.generate_statistics à false à chaud avec sf.getStatistics().setStatisticsEnabled(true) ou hibernateStatsBean.setStatisticsEnabled(true) Les statistiques peuvent être remises à zéro de manière programmatique à l'aide de la méthode clear() Un résumé peut être envoyé à un logger (niveau info) à l'aide de la méthode logSummary() Métriques Hibernate fournit un certain nombre de métriques, qui vont des informations très basiques aux informations très spécialisées qui ne sont appropriées que dans certains scenarii. Tous les compteurs accessibles sont décrits dans l'API de l'interface Statistics dans trois catégories : Les métriques relatives à l'usage général de la Session comme le nombre de sessions ouvertes, le nombre de connexions JDBC récupérées, etc... Les métriques relatives aux entités, collections, requêtes et caches dans leur ensemble (métriques globales), Les métriques détaillées relatives à une entité, une collection, une requête ou une région de cache particulière. Par exemple, vous pouvez vérifier l'accès au cache ainsi que le taux d'éléments manquants et de mise à jour des entités, collections et requêtes et le temps moyen que met une requête. Il faut faire attention au fait que le nombre de millisecondes est sujet à approximation en Java. Hibernate est lié à la précision de la machine virtuelle, sur certaines plateformes, cela n'offre qu'une précision de l'ordre de 10 secondes. Des accesseurs simples sont utilisés pour accéder aux métriques globales (e.g. celles qui ne sont pas liées à une entité, collection ou région de cache particulière). Vous pouvez accéder aux métriques d'une entité, collection, région de cache particulière à l'aide de son nom et à l'aide de sa représentation HQL ou SQL pour une requête. Référez vous à la javadoc des APIS Statistics, EntityStatistics, CollectionStatistics, SecondLevelCacheStatistics, and QueryStatistics pour plus d'informations. Le code ci-dessous montre un exemple simple : Pour travailler sur toutes les entités, collections, requêtes et régions de cache, vous pouvez récupérer la liste des noms des entités, collections, requêtes et régions de cache avec les méthodes : getQueries(), getEntityNames(), getCollectionRoleNames(), et getSecondLevelCacheRegionNames().