MyBatis vs JPA: er bestaat geen slechte technologie.

“Wat is dit project in hemelsnaam?”

Het project bij mijn huidige werkgever is wat je gerust een ‘frankenstack’ kunt noemen. MyBatis en JPA bestaan naast elkaar in hetzelfde project. Het probleem was alleen dat de ‘scheppers’, de oorspronkelijke teamleden die deze code hadden geschreven, allemaal al uit het bedrijf vertrokken waren.

Wij die achterbleven, het huidige team dus, waren behoorlijk in verwarring. Er was geen enkel document dat uitlegde volgens welke criteria die technologieën door elkaar waren gebruikt. Daardoor werd de situatie alleen maar chaotischer.

Uiteindelijk was het zo ver gekomen dat binnen één service sommige mensen in XML zaten te graven om vergelijkbare functionaliteit te bouwen, terwijl anderen entities ontwierpen. Het was veranderd in een bizarre vorm van ‘technische anarchie’. Het project groeide uit tot een monster dat steeds moeilijker te onderhouden was, en uiteindelijk werd er een vergadering belegd.

“Laten we dit op één technologiestack standaardiseren.”

Technologieën zonder duidelijke regels door elkaar gebruiken is geen vrijheid, maar gewoon verwarring.

“JPA is toch veel te traag om te gebruiken?”

De sfeer in de vergadering was gespannen. Vooral collega’s die gewend waren aan MyBatis waren fel tegen een standaardisatie op JPA. Hun belangrijkste argument was snelheid.

“Herinneren jullie je die statistiekenpagina nog die we met JPA hadden gebouwd en die 20 seconden nodig had om alleen al te laden? Als we de SQL zelf schrijven, staat het er in één seconde. Vertrouwen op een black box die we niet volledig beheersen is gewoon riskant.”

Het klopte dat sommige logica tientallen seconden nodig had om alleen de data in te laden. Maar ik was ervan overtuigd dat dat geen probleem van de technologie was, maar van onze beheersing ervan. Ik klapte mijn laptop open en haalde die beruchte ‘20-seconden-code’ erbij.

De code was ronduit slecht. Er werd een List<Entity> opgehaald, maar telkens wanneer de lus een gerelateerd object aanraakte, werd de database opnieuw bevraagd, één voor één. Een schoolvoorbeeld van het N+1-probleem.

“Dit betekent niet dat JPA traag is. Het betekent dat wij JPA inefficiënt laten werken. Kijk maar.”

Ter plekke paste ik een Fetch Join toe en bracht ik de query terug naar één enkele hit. Na de deploy zakte de laadtijd van 20 seconden naar 1 seconde. Dat was het moment waarop ik de blik van mijn collega’s zag veranderen.

Geef niet meteen het gereedschap de schuld. Lees eerst de handleiding.

Een onverwachte ontdekking: geen rommeltje, maar een hybride

Nu het misverstand rond JPA was opgehelderd, moesten we dan gewoon alles op JPA standaardiseren? Toen we beter keken, bleek ook dat niet het volledige antwoord.

Complexe statistische queries of Excel-downloads met honderdduizenden regels via JPA afhandelen bleek onhandig: de kosten van objectmapping waren te hoog en dynamische queries schrijven was omslachtig. MyBatis was voor dat soort werk juist veel intuïtiever en sneller. Ik begon me af te vragen: “Hoe lossen andere bedrijven dit eigenlijk op?” Gefrustreerd zat ik tot diep in de nacht op Google en in technische blogs te zoeken.

Tot mijn verrassing hielden veel techbedrijven helemaal niet vast aan alleen JPA. Sommigen gebruikten QueryDSL voor complexe leesprestaties, en anderen gebruikten, net als wij, MyBatis er gewoon naast.

Toen viel het kwartje. “Dus de mensen die weg zijn gegaan hebben dit helemaal niet gedachteloos door elkaar gegooid.”

Zij kenden de voor- en nadelen van elk gereedschap al en hadden bewust verantwoordelijkheden per technologie gescheiden. Het echte probleem was dat ze vertrokken zonder dat vast te leggen, waardoor wij, die het systeem erfden, ruzie maakten over waarom het nooit was gestandaardiseerd.

Verder dan de grenzen van JPA: QueryDSL en Projection

Toen kwam de volgende vraag op: konden we MyBatis weghalen en dit probleem oplossen met alleen technologie uit het JPA-ecosysteem? Daar komen QueryDSL en Projection in beeld.

// 1. Interface die alleen de benodigde data definieert (geen DTO nodig)
public interface DailyStat {
    String getDate();
    Long getTotalSales();
}

// 2. Via QueryDSL of een JPA Repository opvragen
// Alleen die twee kolommen worden uit de DB geSELECTd, dus net zo snel als MyBatis.
List<DailyStat> stats = repository.findDailySales();

Zodra ik die technieken eenmaal begreep, was er veel minder reden om terug te keren naar de XML-hel van MyBatis. Ook binnen het JPA-ecosysteem kun je prima zeer snelle queries schrijven.

Praktisch advies: stop met vechten en laat ze samenwerken

Uiteindelijk koos ons team niet voor ‘volledige standaardisatie’, maar voor ‘samenleven met duidelijke principes’. (En op de lange termijn wilden we wel richting QueryDSL bewegen.)

Het interessante is dat deze overlevingsstrategie die wij kozen, ‘JPA voor writes, een gespecialiseerde tool voor reads’, vanuit architectuurperspectief eigenlijk een heel basale vorm is van het CQRS(Command and Query Responsibility Segregation)-patroon.

Je hoeft daarvoor niet meteen de database groots op te splitsen. Alleen al het idee om op codeniveau verantwoordelijkheden voor commands en queries te scheiden, maakt een project een stuk schoner.

Tot slot: technologie is niet schuldig

We discussiëren vaak met uitspraken als “JPA is het beste” of “Nee, MyBatis werkt in de praktijk veel beter”. Maar wat ik van deze ervaring heb geleerd, is dat er geen slechte technologie bestaat. Er zijn alleen situaties waarin een technologie voor het verkeerde doel wordt ingezet.

JPA is als een toverstaf, maar als je de spreuk verkeerd uitspreekt, ontploft hij. MyBatis is als een stevige hamer, maar kan je alles als een spijker laten zien.

Als jouw team nu worstelt met legacy code of met verschillende technologische voorkeuren, sta dan even stil. Misschien zit in die chaos wel degelijk diep nadenken van eerdere ontwikkelaars verborgen. Dat herkennen en omzetten in duidelijke regels, dat is hoe een ontwikkelteam echt sterk wordt.

Plaats een reactie