Hibernate.orgCommunity Documentation

Chapitre 21. Améliorer les performances

21.1. Stratégies de chargement
21.1.1. Travailler avec des associations chargées en différé
21.1.2. Personnalisation des stratégies de chargement
21.1.3. Proxies pour des associations vers un seul objet
21.1.4. Initialisation des collections et des proxies
21.1.5. Utiliser le chargement par lot
21.1.6. Utilisation du chargement par sous select
21.1.7. Fetch profiles
21.1.8. Utiliser le chargement en différé des propriétés
21.2. Le cache de second niveau
21.2.1. Mappages de Cache
21.2.2. Stratégie : lecture seule
21.2.3. Stratégie : lecture/écriture
21.2.4. Stratégie : lecture/écriture non stricte
21.2.5. Stratégie : transactionelle
21.2.6. Support de stratégie de concurrence du fournisseur-cache
21.3. Gérer les caches
21.4. Le cache de requêtes
21.4.1. Enabling query caching
21.4.2. Query cache regions
21.5. Comprendre les performances des collections
21.5.1. Taxinomie
21.5.2. Les lists, les maps, les idbags et les ensembles sont les collections les plus efficaces pour la mise à jour
21.5.3. Les sacs et les listes sont les plus efficaces pour les collections inverses
21.5.4. Suppression en un coup
21.6. Moniteur de performance
21.6.1. Suivi d'une SessionFactory
21.6.2. Métriques

Une stratégie de chargement est une stratégie que Hibernate va utiliser pour récupérer des objets associés si l'application a 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 mappage 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 :

Hibernate fait également la distinction entre :

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 les confondre. Le mode de chargement fetch est utilisé pour améliorer les performances. On peut utiliser le mode lazy pour définir un contrat sur quelles données sont toujours accessibles sur toute instance détachée d'une classe particulière.

Par défaut, Hibernate3 utilise le chargement différé par select pour les collections et le chargement différé 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.

Si vous définissez hibernate.default_batch_fetch_size, Hibernate va utiliser l'optimisation du chargement par lot pour le chargement différé (cette optimisation peut aussi être activée à un niveau de granularité plus fin).

L'accès à une association définie comme "différé", hors du contexte d'une session Hibernate ouverte, entraîne une exception. Par exemple :

= sessions.openSession();

Transaction tx = s.beginTransaction();
            
User u = (User) s.createQuery("from User u where u.name=:userName")
    .setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts");  // Error!

Étant donné que la collection des permissions n'a pas été initialisée avant de fermer la Session, la collection n'est pas capable de charger son état. Hibernate ne supporte pas le chargement différé pour des objets détachés. La solution à ce problème est de déplacer le code qui lit à partir de la collection avant le "commit" de la transaction.

Une autre alternative est d'utiliser une collection ou une association non "différée" en spécifiant lazy="false" dans le mappage de l'association. Cependant il est prévu que le chargement différé soit utilisé pour quasiment toutes les collections ou associations. Si vous définissez trop d'associations non "différées" dans votre modèle objet, Hibernate va finir par devoir charger toute la base de données en mémoire à chaque transaction.

Par ailleurs, on veut souvent choisir un chargement par jointure (qui est par défaut non différé) à 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.

Le chargement par select (mode par défaut) est très vulnérable au problème du N+1 selects, ainsi vous souhaiterez peut-être activer le chargement par jointure dans les fichiers de mappage :


<set name="permissions"
            fetch="join">
    <key column="userId"/>
    <one-to-many class="Permission"/>
</set

<many-to-one name="mother" class="Cat" fetch="join"/>

La stratégie de chargement définie à l'aide du mot fetch dans les fichiers de mappage affecte :

Quelle que soit la stratégie de chargement que vous utilisez, la partie du graphe d'objets, non-différée, 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 mappage pour adapter le chargement. En revanche, 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 de charger l'association de manière agressive lors du premier select en utilisant une jointure externe. Dans la requête API Criteria vous utiliserez la méthode setFetchMode(FetchMode.JOIN).

S'il vous arrive de vouloir changer la stratégie de chargement utilisée par utilisée par get() ou load(), vous pouvez juste utiliser une requête de type Criteria comme par exemple :

User user = (User) session.createCriteria(User.class)

                .setFetchMode("permissions", FetchMode.JOIN)
                .add( Restrictions.idEq(userId) )
                .uniqueResult();

Il s'agit de l'équivalent pour Hibernate de ce que d'autres outils de mappage 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.

Le chargement différé des collections est implémenté par Hibernate qui utilise ses propres implémentations pour des collections persistantes. Si l'on veut un chargement différé pour des associations vers un seul objet, 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 différée des proxies sur des objets persistants via une mise à jour à chaud du bytecode (à l'aide de l'excellente librairie CGLIB).

Par défaut, Hibernate génère des proxies (au démarrage) pour toutes les classes persistantes et les utilise pour activer le chargement différé des associations many-to-one et one-to-one.

Le fichier de mappage peut déclarer une interface à utiliser comme interface de proxy pour cette classe à l'aide de l'attribut proxy. Par défaut Hibernate utilise 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 avec au minimum une visibilité de paquetage. 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, par exemple :


<class name="Cat" proxy="Cat">
    ......
    <subclass name="DomesticCat">
        .....
    </subclass>
</class>

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 :

Cat cat = (Cat) session.load(Cat.class, id);  // instantiate a proxy (does not hit the db)

if ( cat.isDomesticCat() ) {                  // hit the db to initialize the proxy
    DomesticCat dc = (DomesticCat) cat;       // Error!
    ....
}

Deuxièmement, il est possible de casser la notion de == des proxies.

Cat cat = (Cat) session.load(Cat.class, id);            // instantiate a Cat proxy

DomesticCat dc = 
        (DomesticCat) session.load(DomesticCat.class, id);  // acquire new DomesticCat proxy!
System.out.println(cat==dc);                            // false

Cette situation n'est pas si mauvaise qu'il n'y parait. Même si nous avons deux références à deux objets proxies différents, l'instance sous-jacente sera quand même le même objet :

cat.setWeight(11.0);  // hit the db to initialize the proxy

System.out.println( dc.getWeight() );  // 11.0

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 quelconque ressource à l'instanciation (par exemple dans les initialiseurs ou dans le constructeur par défaut), alors ces ressources seront aussi obtenues par le proxy. La classe proxy est en réalité 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 mappage : CatImpl implémente l'interface Cat et DomesticCatImpl implémente l'interface DomesticCat. Par exemple :


<class name="CatImpl" proxy="Cat">
    ......
    <subclass name="DomesticCatImpl" proxy="DomesticCat">
        .....
    </subclass>
</class>

Tout d'abord, les instances de Cat et de DomesticCat peuvent être retournées par load() ou par iterate().

Cat cat = (Cat) session.load(CatImpl.class, catid);

Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();
Cat fritz = (Cat) iter.next();

Les relations sont aussi initialisées en différé. 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 :

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ésulteront immédiatement en une initialisation du proxy.

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, par ex. lorsque l'entité à laquelle appartient la collection ou qui a une référence vers le proxy, est dans l'état "détaché".

Parfois, nous devons nous assurer qu'un proxy ou une collection est initialisé 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 approprié pour le code générique.

Les méthodes statiques Hibernate.initialize() et Hibernate.isInitialized() fournissent à l'application un moyen de travailler avec des proxies 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 proxies nécessaires 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 sera alors difficile de garantir que la Session est ouverte lorsqu'une collection est initialisée. Il y a deux moyens de maîtriser ce problème :

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 :

( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()

La méthode createFilter() est également utilisée pour récupérer efficacement des sous-ensembles d'une collection sans avoir besoin de l'initialiser dans son ensemble :

s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();

Pour améliorer les performances, Hibernate peut utiliser le chargement par lot ce qui veut dire que Hibernate peut charger plusieurs proxies (ou collections) non initialisés en une seule requête lorsque l'on accède à l'un de ces proxies. Le chargement par lot est une optimisation intimement liée à la stratégie de chargement en différé 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 mappage de Person :


<class name="Person" batch-size="10">...</class>

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 en différé des Cats, et que 10 personnes sont actuellement chargées dans la Session, itérer sur toutes les persons générera 10 SELECT s, un pour chaque appel de getCats(). Si vous activez le chargement par lot pour la collection cats dans le mappage de Person, Hibernate pourra précharger les collections :


<class name="Person">
    <set name="cats" batch-size="3">
        ...
    </set>
</class>

Avec une taille de lot batch-size de 8, Hibernate chargera respectivement des collections 3, 3, 3, et 1 en quatre SELECT s. 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 une arborescence imbriquée d'éléments, c'est-à-dire le le schéma facture de matériels typique. (Bien qu'un sous ensemble ou un chemin matérialisé soit probablement une meilleure option pour des arbres principalement en lecture.)

Another way to affect the fetching strategy for loading associated objects is through something called a fetch profile, which is a named configuration associated with the org.hibernate.SessionFactory but enabled, by name, on the org.hibernate.Session. Once enabled on a org.hibernate.Session, the fetch profile will be in affect for that org.hibernate.Session until it is explicitly disabled.

So what does that mean? Well lets explain that by way of an example which show the different available approaches to configure a fetch profile:




Now normally when you get a reference to a particular customer, that customer's set of orders will be lazy meaning we will not yet have loaded those orders from the database. Normally this is a good thing. Now lets say that you have a certain use case where it is more efficient to load the customer and their orders together. One way certainly is to use "dynamic fetching" strategies via an HQL or criteria queries. But another option is to use a fetch profile to achieve that. The following code will load both the customer andtheir orders:


Note

@FetchProfile definitions are global and it does not matter on which class you place them. You can place the @FetchProfile annotation either onto a class or package (package-info.java). In order to define multiple fetch profiles for the same class or package @FetchProfiles can be used.

Currently only join style fetch profiles are supported, but they plan is to support additional styles. See HHH-3414 for details.

Hibernate3 supporte le chargement en différé 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 en différé d'une propriété, il faut mettre l'attribut lazy sur une propriété particulière du mappage :


<class name="Document">
       <id name="id">
        <generator class="native"/>
    </id>
    <property name="name" not-null="true" length="50"/>
    <property name="summary" not-null="true" length="200" lazy="true"/>
    <property name="text" not-null="true" length="2000" lazy="true"/>
</class>

Le chargement en différé 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 en différé et retombera dans le mode de chargement immédiat.

Pour l'instrumentation du bytecode vous pouvez utiliser la tâche Ant suivante :


<target name="instrument" depends="compile">
    <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
        <classpath path="${jar.path}"/>
        <classpath path="${classes.dir}"/>
        <classpath refid="lib.class.path"/>
    </taskdef>

    <instrument verbose="true">
        <fileset dir="${testclasses.dir}/org/hibernate/auction/model">
            <include name="*.class"/>
        </fileset>
    </instrument>
</target>

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êtes HQL.

Une Session Hibernate est un cache de niveau transactionnel de données persistantes. Il est possible de configurer un cache de cluster ou de JVM (de niveau SessionFactory) défini classe par classe et collection par collection. Vous pouvez même utiliser votre choix de cache en implémentant le fournisseur 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).

You have the option to tell Hibernate which caching implementation to use by specifying the name of a class that implements org.hibernate.cache.CacheProvider using the property hibernate.cache.provider_class. Hibernate is bundled with a number of built-in integrations with the open-source cache providers that are listed in Tableau 21.1, « Fournisseurs de cache ». You can also implement your own and plug it in as outlined above. Note that versions prior to Hibernate 3.2 use EhCache as the default cache provider.


As we have done in previous chapters we are looking at the two different possibiltites to configure caching. First configuration via annotations and then via Hibernate mapping files.

By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode element in your persistence.xml file or by using the javax.persistence.sharedCache.mode property in your configuration. The following values are possible:

The cache concurrency strategy used by default can be set globaly via the hibernate.cache.default_cache_concurrency_strategy configuration property. The values for this property are:


Hibernate also let's you cache the content of a collection or the identifiers if the collection contains other entities. Use the @Cache annotation on the collection property.


Exemple 21.7, « @Cache annotation with attributes »shows the @org.hibernate.annotations.Cache annotations with its attributes. It allows you to define the caching strategy and region of a given second level cache.


Let's now take a look at Hibernate mapping files. There the <cache> element of a class or collection mapping is used to configure the second level cache. Looking at Exemple 21.8, « The Hibernate <cache> mapping element » the parallels to anotations is obvious.


Alternatively to <cache>, you can use <class-cache> and <collection-cache> elements in hibernate.cfg.xml.

Let's now have a closer look at the different usage strategies

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() ou 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 premier niveau de la session.


La Session fournit également une 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 du cache d'une instance, de la classe entière, d'une instance de collection ou du rôle entier d'une collection.


Le CacheMode contrôle la manière dont une session particulière interagit avec le cache de second niveau :

  • CacheMode.NORMAL - lit et écrit les articles dans le cache de second niveau

  • CacheMode.GET - lit les articles du cache de second niveau mais ne les écrit pas sauf dans le cas d'une mise à jour des données

  • CacheMode.PUT - écrit les articles dans le cache de second niveau mais ne les lit pas dans le cache de second niveau

  • CacheMode.REFRESH - écrit les articles dans le cache de second niveau mais ne les lit pas dans le cache de second niveau, outrepasse l'effet de hibernate.cache.use_minimal_puts, en forçant un rafraîchissement du cache de second niveau pour chaque article lu dans la base de données.

Pour parcourir le contenu du cache de second niveau ou la région du cache dédiée aux requêtes, vous pouvez utiliser l'API Statistics :


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 :


Query result sets can also be cached. This is only useful for queries that are run frequently with the same parameters.

Caching of query results introduces some overhead in terms of your applications normal transactional processing. For example, if you cache results of a query against Person Hibernate will need to keep track of when those results should be invalidated because changes have been committed against Person. That, coupled with the fact that most applications simply gain no benefit from caching query results, leads Hibernate to disable caching of query results by default. To use query caching, you will first need to enable the query cache:

hibernate.cache.use_query_cache true

This setting creates two new cache regions:

As mentioned above, most queries do not benefit from caching or their results. So by default, individual queries are not cached even after enabling query caching. To enable results caching for a particular query, call org.hibernate.Query.setCacheable(true). This call allows the query to look for existing cache results or add its results to the cache when it is executed.

Dans les sections précédentes, nous avons couvert les collections et leurs applications. Dans cette section, nous allons explorer de nouveaux problèmes en rapport aux collections en cours d'exécution.

Hibernate définit trois types de collections de base :

Cette classification distingue les différentes relations entre les tables et les clés étrangères mais ne n'en dit pas suffisamment 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 lignes des collections. Cela nous amène aux classifications suivantes :

Toutes les collections indexées (maps, lists, arrays) ont une clé primaire constituée des colonnes clés (<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 une ligne particulière peut être localisée efficacement lorsque Hibernate essaie de la mettre à jour ou de la supprimer.

Les ensembles ont une clé primaire composée de <key> et des colonnes représentant l'élément. Elle est donc moins efficace pour certains types d'éléments de collection, 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-à-plusieurs ou plusieurs-à-plusieurs, en particulier lorsqu'on utilise des entités ayant des identifiants techniques, il est probable que cela soit aussi efficace (note : si vous voulez que SchemaExport crée effectivement la clé primaire d'un <set> pour vous, vous devez déclarer toutes les colonnes avec not-null="true").

Le mappage à l'aide de <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 sacs. Dans la mesure où un sac permet la duplication des valeurs d'é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 entre les lignes dupliquées. Hibernate résout ce problème en supprimant complètement (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-à-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 les lignes individuelles de la collection).

La discussion précédente montre clairement que les collections indexées et (la plupart du temps) les ensembles, permettent de réaliser le plus efficacement les opérations d'ajout, de suppression ou mise à jour d'éléments.

Les collections indexées ont un avantage sur les ensembles, dans le cadre des associations plusieurs-à-plusieurs ou de collections de valeurs. À cause de la structure inhérente d'un Set, Hibernate n'effectue jamais de ligne UPDATE quand un élément est modifié. Les modifications apportées à un Set se font via un INSERT et DELETE (de chaque ligne). Une fois de plus, ce cas ne s'applique pas aux associations un-à-plusieurs.

Après s'être rappelé que les tableaux ne peuvent pas être chargés en différé, nous pouvons conclure que les lists, les maps et les idbags sont les types de collections (non inversées) les plus performants, avec les ensembles pas loin derrière. Les ensembles sont le type de collection le plus courant dans les applications Hibernate. Cela vient du fait que la sémantique des ensembles est la plus naturelle dans le modèle relationnel.

Cependant, dans des modèles objet bien conçus avec Hibernate, on constate que la plupart des collections sont en fait des associations un-à-plusieurs avec inverse="true". Pour ces associations, les mises à jour sont gérées au niveau de l'association "plusieurs-à-un" et les considérations de performance de mise à jour des collections ne s'appliquent tout simplement pas dans ces cas-là.

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 appelez list.clear(), par exemple). Dans ce cas, Hibernate fera un simple DELETE et le travail est fait !

Supposons que nous ajoutions un élément unique 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 sac). Cela est préférable.

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.

Hibernate n'est pas assez intelligent pour savoir que, dans ce cas, la seconde option est plus rapide (Il vaut mieux que 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 (c'est-à-dire la deuxième stratégie) à tout moment en libérant (c'est-à-dire en déréférençant) la collection initiale et en retournant une collection nouvellement instanciée avec tous les éléments restants.

Bien sûr, la suppression en un coup ne s'applique pas pour les collections qui sont mappées avec inverse="true".

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.

Vous pouvez accéder aux 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 fabrique. Voici un code qui montre un exemple de configuration minimaliste :

// MBean service registration for a specific SessionFactory

Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory's

Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server

Vous pouvez (dés)activer le suivi pour une SessionFactory :

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().

Hibernate fournit plusieurs métriques, qui vont des informations très basiques aux informations très spécialisées qui ne sont appropriées que dans certains scénarios. Tous les compteurs accessibles sont décrits dans l'API de l'interface Statistics dans trois catégories :

Par exemple, vous pouvez vérifier les hit, miss du 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 (c'est-à-dire, 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, et QueryStatistics pour plus d'informations. Le code ci-dessous montre un exemple simple :

Statistics stats = HibernateUtil.sessionFactory.getStatistics();


double queryCacheHitCount  = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
  queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
  stats.getEntityStatistics( Cat.class.getName() );
long changes =
        entityStats.getInsertCount()
        + entityStats.getUpdateCount()
        + entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times"  );

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 suivantes : getQueries(), getEntityNames(), getCollectionRoleNames(), et getSecondLevelCacheRegionNames().