3 Problèmes de performances communs à Hibernate et comment les trouver dans votre fichier journal

1. Introduction

Vous avez probablement lu certaines des plaintes concernant les mauvaises performances d’Hibernate ou vous avez peut-être eu du mal avec certaines d’entre elles. J’utilise Hibernate depuis plus de 15 ans maintenant et j’ai rencontré plus que suffisamment de ces problèmes.

Au fil des ans, j’ai appris que ces problèmes peuvent être évités et qu’ils peuvent être retrouvés en grande partie dans votre fichier journal. Dans ce post, je veux vous montrer comment vous pouvez en trouver et en réparer trois.

2. Trouvez et corrigez les problèmes de performances

2.1. Consigner les instructions SQL en production

Le premier problème de performance est extrêmement facile à détecter et souvent ignoré.

C’est la journalisation des instructions SQL dans un environnement de production.

L’écriture de certaines instructions de journal n’a pas l’air de déplaire, et de nombreuses applications font exactement cela. Mais il est extrêmement inefficace, en particulier via System.out.println comme le fait Hibernate si vous définissez le paramètre show sql dans votre configuration Hibernate sur true__:

Hibernate: select
    order0__.id as id1__2__,
    order0__.orderNumber as orderNum2__2__,
    order0__.version as version3__2__
  from purchaseOrder order0__
Hibernate: select
    items0__.order__id as order__id4__0__0__,
    items0__.id as id1__0__0__,
    items0__.id as id1__0__1__,
    items0__.order__id as order__id4__0__1__,
    items0__.product__id as product__5__0__1__,
    items0__.quantity as quantity2__0__1__,
    items0__.version as version3__0__1__
  from OrderItem items0__
  where items0__.order__id=?
Hibernate: select
    items0__.order__id as order__id4__0__0__,
    items0__.id as id1__0__0__,
    items0__.id as id1__0__1__,
    items0__.order__id as order__id4__0__1__,
    items0__.product__id as product__5__0__1__,
    items0__.quantity as quantity2__0__1__,
    items0__.version as version3__0__1__
  from OrderItem items0__
  where items0__.order__id=?
Hibernate: select
    items0__.order__id as order__id4__0__0__,
    items0__.id as id1__0__0__,
    items0__.id as id1__0__1__,
    items0__.order__id as order__id4__0__1__,
    items0__.product__id as product__5__0__1__,
    items0__.quantity as quantity2__0__1__,
    items0__.version as version3__0__1__
  from OrderItem items0__
  where items0__.order__id=?

Dans l’un de mes projets, j’ai amélioré les performances de 20% en quelques minutes en définissant show sql sur false__. C’est le genre de réussite que vous aimez rapporter lors de la prochaine réunion au stand-up

Il est assez évident de savoir comment résoudre ce problème de performances. Ouvrez simplement votre configuration (votre fichier persistence.xml, par exemple) et définissez le paramètre show sql sur false__. De toute façon, vous n’avez pas besoin de ces informations pour la production.

Mais vous pourriez en avoir besoin pendant le développement. Si vous ne le faites pas, vous utilisez 2 configurations Hibernate différentes (ce qui n’est pas le cas), vous avez également désactivé l’instruction SQL. Pour ce faire, la solution consiste à utiliser 2 log configurations différentes pour le développement et la production, optimisées pour les besoins spécifiques de l’environnement d’exécution.

  • Configuration de développement **

La configuration de développement doit fournir autant d’informations utiles que possible afin que vous puissiez voir comment Hibernate interagit avec la base de données. Vous devez donc au moins consigner les instructions SQL générées dans votre configuration de développement. Vous pouvez le faire en activant le message DEBUG pour la catégorie org.hibernate.SQL . Si vous souhaitez également voir les valeurs de vos paramètres de liaison, vous devez définir le niveau de journalisation de org.hibernate.type.descriptor.sql sur TRACE :

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p[%c]- %m%n
log4j.rootLogger=info, stdout

# basic log level for all messages
log4j.logger.org.hibernate=info

# SQL statements and parameters
log4j.logger.org.hibernate.SQL=debug
log4j.logger.org.hibernate.type.descriptor.sql=trace

L’extrait de code suivant montre quelques exemples de messages de journal qu’Hibernate écrit avec cette configuration de journal. Comme vous pouvez le constater, vous obtenez des informations détaillées sur la requête SQL exécutée et sur toutes les valeurs de paramètre définies et extraites:

23:03:22,246 DEBUG SQL:92 - select
    order0__.id as id1__2__,
    order0__.orderNumber as orderNum2__2__,
    order0__.version as version3__2__
  from purchaseOrder order0__
  where order0__.id=1
23:03:22,254 TRACE BasicExtractor:61 - extracted value ([id1__2__]:[BIGINT]) -[1]23:03:22,261 TRACE BasicExtractor:61 - extracted value ([orderNum2__2__]:[VARCHAR]) -[order1]23:03:22,263 TRACE BasicExtractor:61 - extracted value ([version3__2__]:[INTEGER]) -[0]----

Hibernate vous fournit beaucoup plus d'informations internes sur une __Session__ si vous activez les statistiques d'Hibernate. Vous pouvez le faire en définissant la propriété système __hibernate.generate__statistics__ sur true.

Cependant, activez uniquement les statistiques sur votre environnement de développement ou de test. La collecte de toutes ces informations ralentit votre application et vous pouvez créer vous-même vos problèmes de performances si vous l'activez en production.

Vous pouvez voir des exemples de statistiques dans l'extrait de code suivant:

[source,text,gutter:,true]

23:04:12,123 INFO StatisticalLoggingSessionEventListener:258 - Session Metrics { 23793 nanoseconds spent acquiring 1 JDBC connections; 0 nanoseconds spent releasing 0 JDBC connections; 394686 nanoseconds spent preparing 4 JDBC statements; 2528603 nanoseconds spent executing 4 JDBC statements; 0 nanoseconds spent executing 0 JDBC batches; 0 nanoseconds spent performing 0 L2C puts; 0 nanoseconds spent performing 0 L2C hits; 0 nanoseconds spent performing 0 L2C misses; 9700599 nanoseconds spent executing 1 flushes (flushing a total of 9 entities and 3 collections); 42921 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections) }

J'utilise régulièrement ces statistiques dans mon travail quotidien pour http://www.ibly-on-java.org/hibernate-performance-tuning-part-1/[indexter les problèmes de performance avant qu'ils ne se produisent en production]et je pourrais écrire plusieurs posts. à peu près ça. Alors, concentrons-nous sur les plus importants.

Les lignes 2 à 5 indiquent le nombre de connexions et d’instructions JDBC utilisées par Hibernate au cours de cette session et le temps qu’il a consacré à cette session. Vous devriez toujours regarder ces valeurs et les comparer à vos attentes.

S'il y a beaucoup plus d'énoncés que prévu, vous avez probablement le http://www.iblyts-on-java.org/hibernate-performance-tuning-part-2/[ѕ problème de performance le plus courant, le n sélectionnez un problème. Vous pouvez le trouver dans presque toutes les applications et cela pourrait créer d'énormes problèmes de performances sur une base de données plus grande. J'explique cette question plus en détail dans la section suivante.

Les lignes 7 à 9 montrent comment Hibernate a interagi avec le cache de second niveau.

Il s’agit de l’un des trois caches disponibles à l’adresse http://www.edly-java.org/hibernate-performance-tuning-part-3[Hibernate]et stocke les entités de manière indépendante de la session. Si vous utilisez le 2ème niveau de votre application, vous devez toujours surveiller ces statistiques pour voir si Hibernate obtient les entités à partir de là.

**  Configuration de production **

La configuration de production doit être optimisée pour la performance et éviter tout message non requis de manière urgente. En général, cela signifie que vous ne devez enregistrer que les messages d'erreur. Si vous utilisez Log4j, vous pouvez y parvenir avec la configuration suivante:

Si vous utilisez Log4j, vous pouvez y parvenir avec la configuration suivante:

[source,text,gutter:,true]

log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p[%c]- %m%n log4j.rootLogger=info, stdout