Amoureux de la nature, clickez-ici (ou scannez le QR code) obtenir une version PDF.
Scannez ce QR code pour
lire cet article sur votre mobile.
Ce n'est pas un scoop même si cela mérite souvent d'être rappelé, la base de données est un élément crucial pour à peu prés toute application professionnelle. Cruciale car c'est elle qui héberge le système d’information et qui, à ce titre, a beaucoup plus de valeur que tous les applicatifs, aussi complexes soient-ils, qui gravitent autour. Cruciale aussi en terme de performance car une base mal choisie, mal paramétrée ou mal utilisée, détruira systématiquement les plus louables efforts d'optimisation. Un point à garder en tête lorsqu'en Java (ou ailleurs) arrive le moment d'écrire ou de lire dans une base et que se pose la Grande Question : persistance ou pas persistance...
Cadre de l’évaluation
Hibernate
Je suis parti sur l’exemple fournit dans le tutoriel d’Hibernate. Dans cet exemple nous avons une très simple table Event et l’objet qui lui est associé avec seulement trois champs. Pas de relation, pas d’exotisme. J’ai juste rajouté une méthode pour sauvegarder un objet modifié et encadré les fonctions histoire de répéter 1000 fois l’opération. La session Hibernate est créée à l'extérieur de la boucle et n’est pas prise en compte dans le calcul du temps passé :
session = HibernateUtil.getSessionFactory().openSession();
// je regarde l’heure
for (int i = 0; i < parameter(); i++) {
session.beginTransaction();
session.save(new Event("My Event"));
session.getTransaction().commit();
}
// je regarde le temps passé ici
J’ai choisi délibérément de faire une transaction par itération car c’est ainsi que cela se passe dans la vraie vie. Je ferais la même chose avec JDBC en faisant un commit à chaque tour.
Je prends tout ce luxe de précaution d’introduction pour éviter toute envolée lyriquo-trollesque. En gros, si vous pensez que ce code n’est pas optimisé, il faut aller engueuler les gens qui ont écrit le tutorial. Moi je n’y suis pour rien, j’ai fais juste comme on m’a dit de faire.
Pour terminer sur le cadre, j'inclus ici le fichier de paramétrage (en version compactée) utilisé pour ce test. Au passage, merci à Stéphane pour son aide sur les caches. Si vous avez des remarques sur un mauvaise usage rendant Hibernate sous-optimum, merci de me le faire savoir. Le but est d'avoir une comparaison le plus juste possible.
<?xml version='1.0' encoding='utf-8'?>
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://mon_serveur/ma_base</property>
<property name="connection.username">mon_user</property>
<property name="connection.password">mon_password</property>
<!-- <property name="connection.pool_size">1</property> -->
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="c3p0.acquire_increment">1</property>
<property name="c3p0.idle_test_period">100</property>
<property name="c3p0.max_size">100</property>
<property name="c3p0.max_statements">20</property>
<property name="c3p0.min_size">10</property>
<property name="c3p0.timeout">100</property>
<property name="show_sql">false</property>
<property name="hbm2ddl.auto">create</property>
<mapping resource="net/karmaLab/benchmark/hibernate/Event.hbm.xml" />
</session-factory>
</hibernate-configuration>
JDBC
Pour la version "à la main", je travaille sur la même base qu'hibernate, avec le mêmes pilote JDBC. L’équivalent de la méthode donnée plus haut est donc :
Class.forName("org.postgresql.Driver");
connection = DriverManager.getConnection("jdbc:postgresql://mon_serveur/tests", "mon_user", "mon_pass");
// Je regarde l’heure ici
PreparedStatement statement = connection
.prepareStatement("insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)");
Event event = new Event("My Event");
for (int i = 0; i < parameter(); i++) {
statement.setDate(1, new Date(event.getDate().getTime()));
statement.setString(2, event.getTitle());
statement.setLong(3, id++);
statement.execute();
connection.commit();
}
// je regarde le temps passé ici
Alors oui, le code JDBC est bien moins joli que le code Hibernate, mais voyons ce que cela donne côté performances.
Résultats
Les tests suivants ont été effectués avec le cache des PreparedStatement activé (c3p0.max_statements=20). Chaque test a été réitéré 10 fois de suite et seul le meilleur temps est gardé. Enfin le cache disque est vidé à chaque lancé et la base de données PostgreSQL est posée sur une machine distante sur un réseau local Gigabit.
Inserts

Selects

Updates

Conclusion
Bon, j'ai comme l'impression que tout cela se passe de commentaire ou presque : Hibernate est littéralement écrasé.
Bon, c'est pas un scoop non plus, mais cela a le mérite de tordre un peu le coup à la légende urbaine laissant croire jusqu'à un gain de performance avec Hibernate. Je veux bien accepter l'idée que dans des cas plus complexes de tables liées les unes aux autres Hibernate puisse obtenir des performances comparativement moins désastreuses mais pour des choses simples, le résultat est là.
Une couche de persistance, quelle qu'elle soit, a un coût. Et plus la volumétrie augmente, plus ce coup devient prépondérant. Un coût qui ne s'évalue pas seulement comme le pensent certains à la qualité et au nombre des requêtes émises, mais aussi aux ressources utilisées pour les fabriquer, et c'est bien là que le bât blesse.
Maintenant utiliser ou pas Hibernate ou équivalent (comme le système des EJB 2.
est une question de choix d'architecture et de besoin. L'idée n'est pas ici de jeter le bébé avec l'eau du bain mais bien de prendre conscience que chaque "facilité" a une contrepartie et que dans certains cas cette dernière peut être létale à un projet.

Bonjour,
Pour que la comparaison soit juste, il me semble que le cache de prepared statement du pool de connexions utilisée par hibernate devrait être activé.
Vous ne précisez pas cette information, votre évaluation avait elle été faite avec ce cache activé ? Cela peut fondamentalement changer les performances et c'est le mode normal d'utilisation d'hibernate en production, sinon hibernate passe son temps à ouvrir et fermer des prepared statement.
Bonne remarque merci. Je vais activer cela et si cela change quelque chose je rendrais à césar ce qui lui revoient
Pour information et comme je le disais dans l'article, j'ai utilisé pour hibernate, strictement rien de plus que ce qui se trouve dans leur tutorial, fichiers de paramétrage compris.
Bon, j'ai activé le cache (org.hibernate.cache.EhCacheProvide) comme indiqué dans la doc, c'est effectivement mieux, mais cela reste bien moins efficace qu'un appel JDBC simple. J'ai utilisé le mauvais provider de cache ? Un paramètre en plus ?
Bonjour,
Effectivement, ce n'est pas une surprise de voire Hibernate moins performant que du JDBC simple.
Mais Hibernate apporte un gain important en terme de développement sur des moyens/gros projets (je dirais qu'à partir d'une vingtaine de table c'est un plus).
En effet, le mapping se fait simplement (plus ou moins suivant les associations entre les objets). Il est même allégé par le système d'annotations remplaçant le XML.
En utilisant Hibernate, on pense tout objet même les requêtes en utilisant les criterias. Les associations entre les objets (et les tables) se fait sans aucun effort de la part du développeur.
Le plus gros travail avec Hibernate est sa configuration (simplifié si on le couple avec Spring). Après c'est un jeu d'enfant.
Pense aussi à la gestion de transaction et d'accès concurrent, qu'il faut implémenter à la main en JDBC.
Bref, sur des projets dont le développement est couteux et destinés à durer, ils faut mieux choisir Hibernate dès le début pour gagner du temps par la suite.
Je rajouterai une dernière chose. Les performances d'une application ne sont pas le premier facteur à prendre en compte. Il faut mieux faire une application maintenable et qui donne des résultats correctes qu'une application rapide qui se trompe une fois sur deux et dont ne peut pas relire le code parqu'il y en a partout.
C'est un simple constat que j'ai fait en me basant sur mon expérience professionnel. Souvent Hibernate simplifie bien les choses.
Cordialement,
Gronono
@Gronono
Tout d'abord, pour JDBC, une grande simplification du processus peut être opéré conjointement par la génération automatique de code et l'utilisation de Spring JDBC Template pour les cas "non standards". La génération de code est une solution souvent, et à tord, écartée qui peut permettre d'extraire d'un schéma de base la DAO "basique" qu'Hibernate fabrique dynamiquement. Fonctionellement cela abouti au même résultat. Ensuite pour l'épineux problème des selects, Spring JDBCTemplate permet de garder un bon rapport performance/maintenabilité.
De manière générale, la facilité de maintenance d'un système de développement est souvent liée à la capacité de ceux qui en ont la charge à mettre en place des solutions efficace de rationalisation. A titre d'exemple, certains projets où j'ai eu à jouer les pompiers, avec leurs 200 fichiers .hbm, étaient bien loin d'être d'être optimum
A se stade, il aurait mieux fallu écrire les requêtes à la main.
Ceci dit, chaque approche apporte son lot d'avantages et d'inconvénients. J'ai aussi un longue expérience professionnelle qui me fait dire qu'en ce domaine il n'est jamais de règle absolue. Si j'ai à construire l'architecture d'un système de gestion dont le système d'information est remplis au grès d'étapes de saisie humaines (typique d'une application de gestion), la perte de performance induit par hibernate va clairement être compensé par la maintenabilité. En revanche lorsqu'il s'agit par exemple de stocker en base des données remontant en flux (données financières, issues de systèmes embarqués, etc), hibernate devient un problème et le gain de maintenabilité est complètement éclipsé par des performances désastreuses. De même s'il s'agit d'une application amené à faire des requêtes en simple lecture sur une base massive.
Tout cela pour dire que même si je suis un peu ironique dans mon introduction, ce benchmark n'a pour autre but que de faire prendre conscience que le choix hibernate a un coût et que ce coût ne peut être négligé qu'à partir du moment où l'on a clairement évalué et même testé cette solution sur les véritables volumétries que l'application aura à prendre en charge.
Donc pour reprendre (sans ironie) ta phrase Bref, sur des projets dont le développement est coûteux et destinés à durer, ils faut mieux choisir Hibernate dès le début pour gagner du temps par la suite., je reformulerais en disant que pour choisir, il faut déjà être sur de l'endroit où l'on met les pieds, et ensuite seulement, Hibernate est un choix qui présente des avantages, tout comme JDBC.
En somme, tout choix d'architecture est raisonnable, s'il est raisonné, et basé sur les véritables informations, pas les mythes de performance relayés par plus les enthousiastes et/ou les plus intéressés
Car si ce n'est pas une surprise pour toi, un tel décalage de performance en a été une pour moi, surtout après avoir été de nombreuse fois "contré" sur certains choix d'architecture par un "Hibernate est aussi rapide, si ce n'est plus rapide, que du natif". Un rapport de 3 à 7 selon l'opération, sur un million d'enregistrement, c'est loin d'être une paille...
Dernier point avant d'arrêter mes jacasseries, le fait de tout penser objet comme tu l'évoques est aussi un facteur de perte de performances. Car le monde objet et le monde relationnels sont suffisamment éloignés pour que le fait d'éloigner cette contrainte des tâches quotidiennes d'un développeur, lui fasse produire des architecture intrinsèquement sous-optimales par rapport à ce que peu produire une base de donnée "moderne".
J'ai mis à jour le test en suivant les conseils de stéphane (c3p0.max_statements). Ceci dit, je n'ai pas ajouté C3p0 sur la version JDBC, ce qui "handicape" ce dernier.
J'ai aussi ajouté le vidage du cache disque entre chaque tests. Les résultats ne change pas des masses mais c'est sans aucun doute plus objectif.
Sauf si je me trompe et pour être + proche de la vrai vie, je pense que le test suivant serait + judicieux:
boucle n fois {
j'instancie l'évènement
je le sauve
}
(dans le cas Hibernate tu instancies Event N fois, dans le cas Jdbc tu instancies Event 1 seule fois)
(dans le cas JDBC tu utilises la même instance de preparedStatement, mais ceci n'a peut-être pas d'impact)
NB: Code non vérifié
JDBC
public void test() {
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.getConnection("jdbc:postgresql://mon_serveur/tests", "mon_user", "mon_pass");
Event event = null;
// je regarde l’heure
for (int i = 0; i < parameter(); i++) {
event = new Event("My Event");
saveEvent(connection, event);
}
// je regarde le temps passé ici
}
public void saveEvent(Connection connection, Event event) {
PreparedStatement statement = connection
.prepareStatement("insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)");
statement.setDate(1, new Date(event.getDate().getTime()));
statement.setString(2, event.getTitle());
statement.setLong(3, id++);
statement.execute();
connection.commit();
}
Hibernate
public void test() {
Session session = HibernateUtil.getSessionFactory().openSession();
Event event = null;
// je regarde l’heure
for (int i = 0; i < parameter(); i++) {
event = new Event("My Event");
saveEvent(session, event);
}
// je regarde le temps passé ici
}
public void saveEvent(Session session, Event event) {
session.beginTransaction();
session.save(event);
session.getTransaction().commit();
}
Je doute que ca inverse la tendance, mais bon ca me semble plus juste
@ccn je pense pas non plus que ça l'inversera mais c'est intéressant, je changerais cela pour la prochaine baterie de tests (généralement avec un nouveau JDK
Oups, j'ai oublié le statement.close() et le try finaly qui va avec.
Coté hibernate????? no sé
@ccn pas grave, cela ne va pas impacter de beaucoup les perfs de toute façon
Pour hibernate il n'y a pas de "close" me semble-t-il donc autant ne pas le mettre en JDBC pou équilibrer les chances.
si il n'y a pas de close en hibernate, c'est sans doute qu'il est géré par hibernate himself.
A mon avis pour faire des tests il faut être le + prêt possible de la réalité, donc creer "2 vrais" méthodes save de dao (une jdbc, et un hibernate) et les coder comme dans une "vrai" application.
Bonne chance
Bonjour,
je travaille en utilisant le connecteur JDBC mais je m'intéresse à hibernate également.
Toutefois, ce qui m'intrigue, ce sont les tableaux de résultats. Pour moi, lorsque je le lis, je vois qu'hibernate répond plus vite que jdbc.
Alors quelles sont les unités , et comment doit-on le lire ?
Merci d'avance pour votre réponse.
Cordialement.
Donner des temps n'aurait pas été intéressant, ce sont donc des indices. J'ai mis hibernate en valeur de référence (100%). Un rapport 250/100 implique donc que JDBC serait 2.5 fois plus rapide qu'hibernate pour cette opération.
Y a-t-il un BEABA pour débutants pour l'utilisation du JDBC ?
Norman
Généralement je donne cette introduction pour JDBC, c'est assez clair et bien documenté :
http://www.dil.univ-mrs.fr/~massat/ens/jee/jdbc.html
Bonjour,
Votre test est assez basique, ce cas devrait être optimiser avec ou sans Hibernate. Cependant, il est vrai que l'utilisation d'un framework comporte un coût en terme de performances, mais, un gain en terme de temps de développement, de sécurité pour une performance moyenne sur une complexité moyenne pour des développeurs moyens, une facilité de lecture pour un autre intervenant que le développeur donc de maintenance, etc ...
Il est possible d'utiliser Hibernate pour tout le dév, optimiser ensuite cette utilisation, puis, sur les problèmes de performances rencontrés à l'utilisation, remplacer ponctuellement Hibernate par du JDBC si nécessaire...
Hibernate normalise le dev, rien n'empêche une dé-normalisation par la suite...
Bone journée.
Ce test est délibérément basique pour faire prendre conscience justement de ce surcoût. Après, comme vous le soulignez, l'utilisation d'un framework est un choix qui se justifie, mais en connaissance de cause. J'ai eu des cas ou hibernate m'a été imposé sur de l'injection massive de données (acquisition de valeurs dans le monde industriel) et là cela n'a aucun sens, le surcout étant énorme. Maintenant pour une application "de gestion", et si l'on garde la main sur le code SQL pour les états (listes), Hibernate rempli parfaitement son rôle.
Ceci étant dit, cet article est maintenant ancien et hibnernate a dut depuis cette époque, un peu évoluer, j'imagine.
Ce test est délibérément basique pour faire prendre conscience justement de ce surcoût. Après, comme vous le soulignez, l'utilisation d'un framework est un choix qui se justifie, mais en connaissance de cause. J'ai eu des cas ou hibernate m'a été imposé sur de l'injection massive de données (acquisition de valeurs dans le monde industriel) et là cela n'a aucun sens, le surcout étant énorme. Maintenant pour une application "de gestion", et si l'on garde la main sur le code SQL pour les états (listes), Hibernate rempli parfaitement son rôle.
Ceci étant dit, cet article est maintenant ancien et hibnernate a dut depuis cette époque, un peu évoluer, j'imagine.
Hibernate gère aussi les JDBC batch. Je pensais aux "tableaux" Oracle, mais je ne sais pas si Hibernate les gèrent, et, si ils existent en PostgreSQL, et, c'est, comme vous le souligner déjà ancien et peut être périmé.
Mais, nous sommes aussi souvent tributaire d'une version ancienne de JAVA, d'un framework, etc...
Je vais avoir l'occasion dans les semaines qui viennent de ré-actualiser avec les dernières versions mes connaissances, je vous tiendrais au courant.
Mais bon, il faut voir le bon côté des choses, il y a du travail pour encore longtemps.
Un article récent parlait de la fin des développeurs avec la fin de la loi Moore, je n'ai pas attendu la parution d'un autre article pour penser avec lui, au contraire, l'optimisation redeviendra nécessaire.
Et puis, rien n'empêche, de faire évoluer, ajouter une fonctionnalité à Hibernate ... et, ça, c'est une valeur d'avenir, là est, entre autres, la véritable valeur ajoutée de ce type d'informatique...
Mais bon, c'est un autre débat.
Publier un nouveau commentaire