« Mais ce projet, au juste, c’est quoi ? »
Le projet de mon entreprise actuelle est ce qu’on pourrait appeler un « frankenstack ». MyBatis et JPA cohabitent dans le même projet. Le problème, c’est que les « créateurs », c’est-à-dire les membres initiaux qui avaient écrit ce code, avaient déjà tous quitté l’entreprise.
Nous, l’équipe actuelle, étions complètement perdus. Il n’y avait pas le moindre document expliquant selon quels critères ces technologies avaient été mélangées. La situation n’a donc fait qu’empirer.
Au final, au sein d’un même service, certains fouillaient dans du XML pour implémenter des fonctionnalités similaires pendant que d’autres concevaient des entités. On était tombés dans une étrange forme d’« anarchie technique ». Le projet devenait un monstre de plus en plus difficile à maintenir, jusqu’à ce qu’une réunion soit finalement organisée.
« Unifions notre stack technique une bonne fois pour toutes. »

« JPA, ce n’est pas trop lent pour qu’on l’utilise ? »
L’atmosphère de la réunion était tendue. Les collègues habitués à MyBatis, en particulier, s’opposaient fermement à une standardisation sur JPA. Leur argument principal était la vitesse.
« Vous ne vous souvenez pas de cette page de statistiques codée avec JPA qui mettait 20 secondes à charger ? Si on écrit le SQL à la main, on a le résultat en une seconde. S’appuyer sur une boîte noire qu’on ne maîtrise pas vraiment, c’est dangereux. »
Il était vrai que certaines logiques mettaient plusieurs dizaines de secondes juste pour charger les données. Mais j’étais convaincu que ce n’était pas un problème de technologie, mais de maîtrise. J’ai ouvert mon ordinateur sur-le-champ et affiché le fameux « code à 20 secondes ».
Le code était catastrophique. Il récupérait un List<Entity>, puis à chaque passage dans la boucle, il frappait la base de données pour charger les objets associés un par un. C’était un problème N+1 typique.
« Ce n’est pas JPA qui est lent. C’est nous qui lui avons fait faire le travail de manière inefficace. Regardez. »
J’ai appliqué un Fetch Join sur-le-champ et réduit la requête à un seul aller-retour. Après déploiement, le temps de chargement est passé de 20 secondes à 1 seconde. C’est à ce moment-là que j’ai vu le regard de mes collègues changer.

Découverte inattendue : pas un patchwork, mais un hybride
Une fois les malentendus sur JPA dissipés, fallait-il simplement tout unifier autour de JPA ? En regardant de plus près, on a vite compris que ce n’était pas non plus la bonne réponse.
Pour des requêtes statistiques complexes ou des exports Excel de plusieurs centaines de milliers de lignes, passer par JPA coûtait trop cher en transformation d’objets, et les requêtes dynamiques étaient difficiles à écrire. MyBatis, à l’inverse, était nettement plus intuitif et plus rapide pour ce genre de besoin. Je me suis alors demandé : « Comment les autres entreprises résolvent-elles ce problème ? » Frustré, j’ai passé la nuit sur Google à écumer des blogs techniques.
À ma surprise, beaucoup d’entreprises tech ne s’en tenaient pas uniquement à JPA. Certaines utilisaient QueryDSL pour optimiser les requêtes complexes, et d’autres, comme nous, faisaient cohabiter MyBatis avec le reste.
Là, j’ai eu le déclic. « Donc, ceux qui sont partis n’avaient pas mélangé tout ça n’importe comment. »
Ils connaissaient déjà les avantages et les limites de chaque outil, et avaient volontairement séparé les responsabilités par technologie. Le vrai problème, c’est qu’ils sont partis sans documenter ces choix. Nous, qui avons hérité du système, nous nous sommes donc retrouvés à nous disputer sur le fait qu’il n’avait pas été unifié.
Au-delà des limites de JPA : QueryDSL et Projection
Une autre question s’est alors posée : pouvait-on retirer MyBatis et résoudre ce problème uniquement avec les technologies de l’écosystème JPA ? C’est là qu’entrent en jeu QueryDSL et Projection.
// 1. Interface qui ne declare que les champs necessaires (pas besoin de DTO)
public interface DailyStat {
String getDate();
Long getTotalSales();
}
// 2. Recuperer via QueryDSL ou un JPA Repository
// Seules ces deux colonnes sont SELECTionnees depuis la BDD, cest donc aussi rapide que MyBatis.
List<DailyStat> stats = repository.findDailySales();
Une fois ces outils compris, il y avait beaucoup moins de raisons de retourner dans l’enfer XML de MyBatis. On pouvait tout à fait écrire des requêtes très performantes au sein même de l’écosystème JPA.
Conseil terrain : ne vous battez pas, faites-les coexister
Au final, notre équipe a choisi une « coexistence avec des principes clairs » plutôt qu’une « unification absolue ». (Et sur le long terme, nous visions tout de même une transition vers QueryDSL.)
Ce qui est intéressant, c’est que cette stratégie de survie, « JPA pour l’écriture, un outil spécialisé pour la lecture », n’est au fond, d’un point de vue architecture, qu’une forme très basique du pattern CQRS(Command and Query Responsibility Segregation).
Il n’est même pas nécessaire de découper la base de données de manière spectaculaire. Le simple fait de séparer, dans le code, les responsabilités entre commandes et lectures rend déjà un projet bien plus propre.
Conclusion : la technologie n’est pas coupable
On passe souvent du temps à débattre : « JPA est le meilleur », ou au contraire « Non, MyBatis est bien plus efficace en production ». Mais ce que j’ai appris avec cette expérience, c’est qu’il n’existe pas de mauvaise technologie. Il n’existe que des situations où l’on utilise une technologie pour le mauvais besoin.
JPA, c’est comme une baguette magique : si vous lancez le mauvais sort, tout explose. MyBatis, c’est comme un marteau solide : il peut vous donner l’impression que tout ressemble à un clou.
Si votre équipe traverse en ce moment des tensions à cause du legacy code ou de préférences techniques différentes, arrêtez-vous un instant. Peut-être qu’au milieu de ce chaos se cache une réflexion profonde des développeurs qui vous ont précédés. La retrouver et la transformer en règles claires, c’est cela qui fait une équipe d’ingénierie vraiment solide.