jeu, 15/03/2007 - 09:13 | Yoran   Articles Java

Hibernate vs JDBC - Performances comparées

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

attention cet article est très loin d'être à jour et les données présentées ici sont juste là pour mémoire. Raison pour laquelle les commentaires sont fermées. Si cependant vous avez besoin d'informations, n'hésitez pas à passer par le formulaire de contacts.

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