“Ma questo progetto, esattamente, cos’è?”
Il progetto della mia azienda attuale è quello che potremmo chiamare un ‘frankenstack’. MyBatis e JPA convivono nello stesso progetto. Il problema è che i ‘creatori’, cioè i membri iniziali che avevano scritto questo codice, se n’erano già andati tutti dall’azienda.
Noi rimasti, cioè il team attuale, eravamo confusi. Non esisteva un solo documento che spiegasse con quale criterio fossero state mescolate queste tecnologie. E così la situazione è diventata sempre più caotica.
Alla fine, dentro lo stesso servizio, c’era chi scavava nell’XML per realizzare funzionalità simili e chi invece progettava entità. Il tutto era diventato una strana forma di ‘anarchia tecnica’. Il progetto si stava trasformando in un mostro sempre più difficile da mantenere, finché non è stata convocata una riunione.
“Uniformiamo tutto su un unico stack tecnologico.”

“Ma JPA non è troppo lento per usarlo?”
L’atmosfera della riunione era tesa. In particolare, i colleghi abituati a MyBatis si opponevano con forza a una standardizzazione su JPA. Il loro argomento principale era la velocità.
“Non vi ricordate la pagina delle statistiche che avevamo fatto con JPA e che impiegava 20 secondi solo per caricarsi? Se scriviamo l’SQL direttamente, il risultato arriva in un secondo. Affidarsi a una scatola nera che non controlliamo davvero è rischioso.”
Era vero che alcune logiche impiegavano decine di secondi solo per caricare i dati. Ma ero convinto che non fosse un problema della tecnologia, bensì della nostra preparazione. Ho aperto il portatile lì per lì e ho mostrato il famigerato ‘codice da 20 secondi’.
Il codice era pessimo. Recuperava una List<Entity>, ma ogni volta che il loop toccava un oggetto associato colpiva il database ancora una volta, uno per uno. Era un classico problema N+1.
“Questo non significa che JPA sia lento. Significa che siamo stati noi a far lavorare JPA in modo inefficiente. Guardate.”
Ho applicato subito un Fetch Join e ridotto la query a un solo colpo. Dopo il deploy, il tempo di caricamento è passato da 20 secondi a 1 secondo. È stato il momento in cui ho visto cambiare l’espressione dei colleghi.

Una scoperta inattesa: non un pasticcio, ma un ibrido
Una volta chiarito il malinteso su JPA, bastava unificare tutto sotto JPA? Guardando più da vicino, si capiva che anche questa non era la risposta giusta.
Provare a gestire con JPA query statistiche complesse o download Excel da centinaia di migliaia di righe significava pagare un costo troppo alto in mapping degli oggetti, e scrivere query dinamiche era complicato. MyBatis, invece, in questi casi era molto più intuitivo e veloce. Mi sono chiesto: “Come risolvono questo problema le altre aziende?” Frustrato, ho passato la notte a cercare su Google e a leggere blog tecnici.
Con mia sorpresa, molte aziende tech non insistevano affatto sull’uso esclusivo di JPA. Alcune usavano QueryDSL per le query complesse ad alte prestazioni, e altre, proprio come noi, affiancavano anche MyBatis.
A quel punto ho avuto l’illuminazione. “Allora chi c’era prima non ha mescolato queste tecnologie a caso.”
Conoscevano già i punti di forza e di debolezza di ogni strumento e avevano separato intenzionalmente le responsabilità per tecnologia. Il vero problema è che se ne sono andati senza documentare queste decisioni, e noi che abbiamo ereditato il sistema ci siamo ritrovati a litigare sul perché non fosse stato unificato.
Oltre i limiti di JPA: QueryDSL e Projection
A questo punto è sorta un’altra domanda: potevamo eliminare MyBatis e risolvere il problema usando solo tecnologie dell’ecosistema JPA? Qui entrano in gioco QueryDSL e Projection.
// 1. Interfaccia che definisce solo i dati necessari (nessun DTO necessario)
public interface DailyStat {
String getDate();
Long getTotalSales();
}
// 2. Interroga tramite QueryDSL o un JPA Repository
// Dal DB vengono SELECTionate solo quelle due colonne, quindi e veloce quanto MyBatis.
List<DailyStat> stats = repository.findDailySales();
Una volta capiti questi strumenti, è venuto meno gran parte del motivo per tornare nell’inferno XML di MyBatis. Anche dentro l’ecosistema JPA si possono scrivere query molto performanti.
Consiglio pratico: smettete di litigare e fatele convivere
Alla fine, il nostro team ha scelto una ‘convivenza con principi chiari’ invece di una ‘standardizzazione assoluta’. (E nel lungo periodo puntavamo comunque a passare a QueryDSL.)
La cosa interessante è che questa strategia di sopravvivenza che abbiamo scelto, ‘JPA per le scritture, uno strumento dedicato per le letture’, dal punto di vista architetturale è una forma molto basilare del pattern CQRS(Command and Query Responsibility Segregation).
Non serve neppure spezzare il database in modo eclatante. Già il semplice principio di separare a livello di codice le responsabilità di command e query rende il progetto molto più pulito.
In conclusione: la colpa non è della tecnologia
Spesso discutiamo dicendo: “JPA è il migliore”, oppure “No, MyBatis in produzione funziona molto meglio”. Ma ciò che ho imparato da questa esperienza è che non esistono tecnologie cattive. Esistono solo situazioni in cui una tecnologia viene usata per il compito sbagliato.
JPA è come una bacchetta magica, ma se pronunci male l’incantesimo esplode. MyBatis è come un martello robusto, ma può farti vedere chiodi dappertutto.
Se nel tuo team state vivendo tensioni per via del codice legacy o di preferenze tecnologiche diverse, fermatevi un attimo. Forse dentro quel caos c’è il ragionamento profondo dei developer che vi hanno preceduto. Scoprirlo e trasformarlo in regole chiare: è questa la strada per diventare un team di sviluppo davvero forte.