Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 21. Améliorer les performances

## 21.1. Stratégies de chargement

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 :

- 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 à l'entité ou à la collection. À moins que vous ne désactiviez explicitement le chargement différé 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 différé 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és primaires ou de clés étrangères.

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 différé d'une collection - Une collection est chargée lorsque l'application invoque une méthode sur cette collection (il s'agit du mode de chargement par défaut pour les collections).

- Chargement "super différé" d'une collection - On accède aux éléments de la collection depuis la base de données lorsque c'est 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 à la variable d'instance. Par rapport au chargement par proxy, cette approche est moins différée (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 différé des attributs - Un attribut ou un objet associé seul est chargé lorsque l'on accède à la variable d'instance. 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 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.

### 21.1.1. Travailler avec des associations chargées en différé

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 :

    s = 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.

### 21.1.2. 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, 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 :

- La récupération via `get()` ou `load()`

- La récupération implicite lorsque l'on navigue à travers une association

- Les requêtes par `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, 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.

### 21.1.3. Proxies pour des associations vers un seul objet

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

## Remarque

`list()` ne retourne pas les proxies normalement.

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 :

- `equals()`, si la classe persistante ne surcharge pas `equals()`

- `hashCode()`, si la classe persistante ne surcharge pas `hashCode()`

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

### 21.1.4. Initialisation des collections et des proxies

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 :

- 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 modèle Vue de la session ouverte). 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 modèle "Open Session in View".

- Dans une application avec une couche métier multiniveaux séparée, la couche contenant la logique métier doit "préparer" toutes les collections qui seront nécessaires à la couche web multiniveaux 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 modèle 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 proxies) 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 :

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

### 21.1.5. Utiliser le chargement par lot

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 `Cat`s, 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.)

### 21.1.6. Utilisation du chargement par sous select

Si une collection en différé ou un proxy vers un objet doit être chargée, Hibernate va tous les charger en ré-exécutant la requête originale dans un sous select. Cela fonctionne de la même manière que le chargement par lot sans la possibilité de fragmenter le chargement.

### 21.1.7. Fetch profiles

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:

Exemple 21.1. Specifying a fetch profile using `@FetchProfile`

    @Entity

    @FetchProfile(name = "customer-with-orders", fetchOverrides = {

       @FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)

    })

    public class Customer {

       @Id

       @GeneratedValue

       private long id;

       private String name;

       private long customerNumber;

       @OneToMany

       private Set<Order> orders;

       // standard getter/setter

       ...

    }

Exemple 21.2. Specifying a fetch profile using `<fetch-profile>` outside `<class>` node

    <hibernate-mapping>

        <class name="Customer">

            ...

            <set name="orders" inverse="true">

                <key column="cust_id"/>

                <one-to-many class="Order"/>

            </set>

        </class>

        <class name="Order">

            ...

        </class>

        <fetch-profile name="customer-with-orders">

            <fetch entity="Customer" association="orders" style="join"/>

        </fetch-profile>

    </hibernate-mapping>

Exemple 21.3. Specifying a fetch profile using `<fetch-profile>` inside `<class>` node

    <hibernate-mapping>

        <class name="Customer">

            ...

            <set name="orders" inverse="true">

                <key column="cust_id"/>

                <one-to-many class="Order"/>

            </set>

            <fetch-profile name="customer-with-orders">

                <fetch association="orders" style="join"/>

            </fetch-profile>

        </class>

        <class name="Order">

            ...

        </class>

    </hibernate-mapping>

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:

Exemple 21.4. Activating a fetch profile for a given `Session`

    Session session = ...;

    session.enableFetchProfile( "customer-with-orders" );  // name matches from mapping

    Customer customer = (Customer) session.get( Customer.class, customerId );

## 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.

### 21.1.8. Utiliser le chargement en différé des propriétés

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.

## 21.2. Le cache de second niveau

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.

Tableau 21.1. Fournisseurs de cache

Cache | Classe pourvoyeuse | Type | Cluster sécurisé | Cache de requêtes supporté
--- | --- | --- | --- | ---
Table de hachage (ne pas utiliser en production) | `org.hibernate.cache.HashtableCacheProvider` | mémoire |  | yes
EHCache | `org.hibernate.cache.EhCacheProvider` | memory, disk, transactional, clustered | yes | yes
OSCache | `org.hibernate.cache.OSCacheProvider` | mémoire, disque |  | yes
SwarmCache | `org.hibernate.cache.SwarmCacheProvider` | en cluster (multicast ip) | oui (invalidation de cluster) |
JBoss Cache 1.x | `org.hibernate.cache.TreeCacheProvider` | en cluster (multicast ip), transactionnel | oui (réplication) | oui (horloge sync. nécessaire)
JBoss Cache 2 | `org.hibernate.cache.jbc.JBossCacheRegionFactory` | en cluster (multicast ip), transactionnel | oui (replication ou invalidation) | oui (horloge sync. nécessaire)

### 21.2.1. Mappages de Cache

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:

- `ENABLE_SELECTIVE` (Default and recommended value): entities are not cached unless explicitly marked as cacheable.

- `DISABLE_SELECTIVE`: entities are cached unless explicitly marked as not cacheable.

- `ALL`: all entities are always cached even if marked as non cacheable.

- `NONE`: no entity are cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.

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:

- `read-only`

- `read-write`

- `nonstrict-read-write`

- `transactional`

## Note

It is recommended to define the cache concurrency strategy per entity rather than using a global one. Use the `@org.hibernate.annotations.Cache` annotation for that.

Exemple 21.5. Definition of cache concurrency strategy via `@Cache`

    @Entity

    @Cacheable

    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

    public class Forest { ... }

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.6. Caching collections using annotations

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)

    @JoinColumn(name="CUST_ID")

    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

    public SortedSet<Ticket> getTickets() {

        return tickets;

    }

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.

Exemple 21.7. `@Cache` annotation with attributes

    @Cache(
        CacheConcurrencyStrategy usage();
        String region() default "";
        String include() default "all";
    )

| usage: the given cache concurrency strategy (NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)
| region (optional): the cache region (default to the fqcn of the class or the fq role name of the collection)
| `include` (optional): all to include all properties, non-lazy to only include non lazy properties (default all).

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.

Exemple 21.8. The Hibernate `<cache>` mapping element

    <cache
        usage="transactional|read-write|nonstrict-read-write|read-only"
        region="RegionName"
        include="all|non-lazy"
    />

| `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 rôle 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 en différé des attributs est activé.

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

### 21.2.2. Stratégie : 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.

### 21.2.3. 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 la propriété `hibernate.transaction.manager_lookup_class`, fournissant une stratégie pour obtenir le JTA `TransactionManager`. 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 verrouillage, ce que ne font pas les pourvoyeurs caches fournis.

### 21.2.4. Stratégie : lecture/écriture non stricte

Si l'application a besoin de mettre à jour les données de manière occasionnelle (il est très peu probable que deux transactions essaient de mettre à jour le même élément simultanément) et si 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()`.

### 21.2.5. 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`.

### 21.2.6. Support de stratégie de concurrence du fournisseur-cache

## Important

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.

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.

Tableau 21.2. Support de stratégie de concurrence du cache

Cache | read-only | nonstrict-read-write | read-write | transactional
--- | --- | --- | --- | ---
Table de hachage (ne pas utiliser en production) | yes | yes | yes |
EHCache | yes | yes | yes | yes
OSCache | yes | yes | yes |
SwarmCache | yes | yes |  |
JBoss Cache 1.x | yes |  |  | yes
JBoss Cache 2 | yes |  |  | yes

## 21.3. 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()` 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.

Exemple 21.9. Explcitly evicting a cached instance from the first level cache using `Session.evict()`

    ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set

    while ( cats.next() ) {

        Cat cat = (Cat) cats.get(0);

        doSomethingWithACat(cat);

        sess.evict(cat);

    }

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.

Exemple 21.10. Second-level cache eviction via `SessionFactoty.evict()` and `SessionFacyory.evictCollection()`

    sessionFactory.evict(Cat.class, catId); //evict a particular Cat

    sessionFactory.evict(Cat.class);  //evict all Cats

    sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens

    sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections

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` :

Exemple 21.11. Browsing the second-level cache entries via the `Statistics` API

    Map cacheEntries = sessionFactory.getStatistics()

            .getSecondLevelCacheStatistics(regionName)

            .getEntries();

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 :

Exemple 21.12. Enabling Hibernate statistics

    hibernate.generate_statistics true
    hibernate.cache.use_structured_entries true

## 21.4. Le cache de requêtes

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

### 21.4.1. Enabling query caching

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:

- `org.hibernate.cache.StandardQueryCache`, holding the cached query results

- `org.hibernate.cache.UpdateTimestampsCache`, holding timestamps of the most recent updates to queryable tables. These are used to validate the results as they are served from the query cache.

## Important

If you configure your underlying cache implementation to use expiry or timeouts is very important that the cache timeout of the underlying cache region for the UpdateTimestampsCache be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the UpdateTimestampsCache region not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.

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.

## Note

The query cache does not cache the state of the actual entities in the cache; it caches only identifier values and results of value type. For this reaso, the query cache should always be used in conjunction with the second-level cache for those entities expected to be cached as part of a query result cache (just as with collection caching).

### 21.4.2. Query cache regions

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

    List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")

            .setEntity("blogger", blogger)

            .setMaxResults(15)

            .setCacheable(true)

            .setCacheRegion("frontpages")

            .list();

If you want to force the query cache to refresh one of its regions (disregard any cached results it finds there) you can use `org.hibernate.Query.setCacheMode(CacheMode.REFRESH)`. In conjunction with the region you have defined for the given query, Hibernate will selectively force the results cached in that particular region to be refreshed. This is particularly useful in cases where underlying data may have been updated via a separate process and is a far more efficient alternative to bulk eviction of the region via `org.hibernate.SessionFactory.evictQueries()`.

## 21.5. Comprendre les performances des collections

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.

### 21.5.1. Taxinomie

Hibernate définit trois types de collections de base :

- les collections de valeurs

- Association un-à-plusieurs

- les associations plusieurs-à-plusieurs

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 :

- collections indexées

- ensembles (sets)

- sacs (bags)

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

### 21.5.2. Les lists, les maps, les idbags et les ensembles 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 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à.

### 21.5.3. Les sacs et les listes sont les plus efficaces pour les collections inverses

Avant que vous n'oubliez les sacs pour toujours, il y a un cas précis où les sacs (et les listes) sont bien plus performants que les ensembles. Pour une collection marquée comme `inverse="true"` (le choix le plus courant pour un relation un-à-plusieurs bidirectionnelle), nous pouvons ajouter des éléments à un sac ou une liste sans avoir besoin de l'initialiser (charger) les éléments du sac! Ceci parce que `Collection.add()` ou `Collection.addAll()` doit toujours retourner vrai pour un sac 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();

### 21.5.4. 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 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.

- effacer dix-huit lignes une à une puis en insérer trois

- effacer la totalité de la collection (en un SQL `DELETE`) puis insérer les cinq éléments restant (un à un)

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"`.

## 21.6. 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`.

### 21.6.1. Suivi d'une 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` :

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

### 21.6.2. Métriques

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 :

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

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

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 20. Mappage XML
- Niveau supérieur
- Sommaire
- SuivantChapitre 22. Guide de la boîte à outils