JPA Hibernate — Cheat Sheet

Tag: java, jpa, hibernate, orm, persistence, sql

Livello: intermedio

TL;DR: Standard JPA e implementazione Hibernate per la gestione del mapping relazionale, ciclo di vita delle entità e prevenzione del problema N+1.

Concetti chiave

JPA è la specifica standard Java EE/Jakarta per il mapping oggetto-relazionale.

Hibernate è il provider ORM più diffuso che implementa la specifica JPA.

EntityManager gestisce il Persistence Context e il ciclo di vita delle entità.

Stati dell'entità: Transient (nuova), Managed (nel contesto), Detached (scollegata), Removed (eliminata).

Il dirty checking rileva automaticamente i cambi di stato e li sincronizza a fine transazione.

FetchType.LAZY carica le relazioni on-demand; FetchType.EAGER le carica contestualmente.

L'annotazione @Transactional delimita il confine transazionale garantendo atomicità e rollback automatico in caso di errore.

La cache di primo livello è legata alla sessione/EntityManager corrente.

La cache di secondo livello è condivisa a livello di applicazione/SessionFactory.

Domande tipiche e risposte pronte

Q: Come si gestisce o previene il problema delle N+1 query in JPA/Hibernate?R: Utilizzo query JPQL con JOIN FETCH esplicito o definisco un @EntityGraph per caricare le relazioni con una sola istruzione SQL.

Q: Qual è la differenza tra find() e getReference() in EntityManager?R: find() esegue subito la query sul database, mentre getReference() restituisce un proxy lazy senza interrogare il DB finché non si accede alle proprietà.

Q: Perché si verifica la LazyInitializationException?R: Si verifica quando provo ad accedere a una collezione o relazione con fetch lazy al di fuori di una sessione o transazione attiva.

Insidie e errori comuni

Default di fetch non uniforme: le associazioni a singolo oggetto (@ManyToOne, @OneToOne) hanno default EAGER, mentre collezioni (@OneToMany) sono LAZY.

Risolvere LazyInitializationException usando EAGER su tutto il modello provoca gravi cali di prestazioni.

Modifica delle collezioni lazy dopo la chiusura del Persistence Context.

Mancato override coerente di equals() e hashCode() su entità gestite in Set.

Usare cascade = CascadeType.REMOVE su relazioni @ManyToMany eliminando record correlati non desiderati.

Snippet e comandi

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Order> orders = new ArrayList<>();
}

Java

// Esempio risoluzione N+1 con JOIN FETCH
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

Da ripassare

[ ] Configurazione corretta della cache di secondo livello con Redis o Ehcache

[ ] Utilizzo di @BatchSize per ottimizzare le query lazy

[ ] Differenza operativa tra persist() e merge()

[x] Mappatura di base delle associazioni @ManyToOne e @OneToMany

Jarvis Interviews
Jarvis