Sviluppare applicazioni con Spring Boot è veloce, ma garantire che il codice sia sempre performante, sicuro e allineato alle best practice non è altrettanto semplice. Ecco perché ho creato SpringSentinel, il plugin Maven per l’analisi statica che funge da vero e proprio “guardiano” per i tuoi progetti Java.
In questo articolo esploreremo come SpringSentinel può automatizzare l’audit del tuo codice.
Analizzare manualmente ogni riga di codice alla ricerca di query N+1 o vulnerabilità di sicurezza è un compito titanico. SpringSentinel automatizza questo processo focalizzandosi su tre pilastri fondamentali:
Performance & Database: Rileva l’uso improprio di FetchType.EAGER e potenziali colli di bottiglia nelle transazioni.
Sicurezza (Security): Scansiona il progetto alla ricerca di segreti hardcoded e configurazioni CORS insicure.
Governance REST: Verifica che i tuoi endpoint siano versionati e seguano la convenzione kebab-case.
Per vedere SpringSentinel in azione, scoprire come leggere i report HTML e come configurarlo in meno di 2 minuti, guarda il video completo qui sotto:
Video di risposta ad un post Linkedin. In breve, per me, la programmazione come era prima è quasi morta. Il futuro a breve termine sarà coordinare agenti, ottimizzazione token, creatore di app molto più potenti (paragonabili a piccole software house). Programmazione reale ridotta a poca roba.
Nello sviluppo di applicazioni enterprise con Spring Data JPA e Hibernate, esiste un confine sottile tra la comodità dell’astrazione e il disastro prestazionale. Questo confine è spesso marcato dall’N+1 Select, un problema che non genera errori di compilazione, non rompe i test unitari, ma può portare al collasso di un database in produzione sotto carichi reali.
Hibernate nasce per mappare oggetti su tabelle. Per efficienza, le relazioni (specialmente le collezioni come @OneToMany) sono impostate di default su FetchType.LAZY. Ciò significa che i dati correlati non vengono caricati finché non vi si accede esplicitamente.
Esempio
Immaginiamo un’entità Order che ha una lista di prodotti acquistati ItemOrder. Quindi nel codice di Order avremo annotazione @OneToMany e in ItemOrder @ManyToOne, con fetch type lazy di default, per non appesantire il sistema.
Se proviamo a recuperare gli ordini e accedere ai loro item, generiamo l’N+1:
// Query 1: Recupera tutti gli ordini (es. 10 ordini)
List<Order> orders = entityManager.createQuery(“SELECT o FROM Order o”, Order.class).getResultList();
for (Order order : orders) {
// Query +N: Ogni volta che chiamiamo size() o iteriamo, Hibernate lancia una nuova query
A livello SQL, quello che accade dietro le quinte è disastroso:
Query 1:SELECT * FROM Orders; (Restituisce 10 righe)
Query 2:SELECT * FROM ItemOrder WHERE OrderId = 1;
Query 3:SELECT * FROM ItemOrder WHERE OrderId = 2;
… e così via fino alla decima query.
In totale abbiamo eseguito 1 + 10 = 11 query. Se avessi 1.000 ordini, l’applicazione eseguirebbe 1.001 query! Se avessi N ordini avrei N+1 query ( da qui il nome del problema).
Ogni query al database comporta un “round-trip” (viaggio di andata e ritorno) sulla rete. Anche se la query singola è veloce, sommare centinaia di ritardi di rete crea una latenza enorme, saturando le connessioni disponibili e rallentando l’intera applicazione.
Soluzione 1: JOIN FETCH
La soluzione più comune e pulita in JPA è l’utilizzo della clausola JOIN FETCH. Questa istruzione dice a Hibernate di eseguire una JOIN SQL e di popolare immediatamente la collezione degli item.
String jpql = “SELECT o FROM Order o JOIN FETCH o.items”; List<Order> orders = entityManager.createQuery(jpql, Order.class).getResultList();
// Ora questo ciclo NON genera altre query, i dati sono già in memoria for (Order order : orders) { System.out.println(“Item caricati: ” + order.getItems().size()); }
In genere, viene creata un query apposita in JPQL nel repository.
Soluzione 2: Entity Graph (JPA 2.1+)
Se non vuoi scrivere query JPQL personalizzate, puoi usare gli Entity Graphs. Questo approccio permette di definire “cosa” caricare a seconda del caso d’uso, mantenendo la query principale semplice.
EntityGraph<Order> graph = entityManager.createEntityGraph(Order.class); graph.addSubgraph(“items”); // Specifichiamo di voler caricare anche la lista items
List<Order> orders = entityManager.createQuery(“SELECT o FROM Order o”, Order.class) .setHint(“javax.persistence.loadgraph”, graph) .getResultList();
Anche qui spesso, viene creata una query specifica con @EntityGraph nel repository