Tag: java, jpa, hibernate, orm, persistence, database
Livello: intermedio
TL;DR: Reference rapida su JPA e Hibernate per colloqui: ciclo di vita delle entita', gestione sessioni, query JPQL/Criteria, relazioni, caching e ottimizzazione N+1.
JPA definisce lo standard di persistenza in Java tramite annotazioni e API.
Hibernate e' l'implementazione ORM concreta con funzionalita' proprietarie avanzate.
EntityManager gestisce il ciclo di vita delle entita' e le query.
Stati dell'entita': Transient, Persistent (Managed), Detached, Removed.
Persistence Context agisce da cache di primo livello transazionale.
Dirty Checking rileva modifiche su entita' Managed ed emette update al flush.
Strategie di caricamento: FetchType.LAZY (proxy) e FetchType.EAGER (immediato).
Caching: Primo livello per Session/EntityManager, Secondo livello globale condiviso.
Problema N+1: query principale piu' N query per collezioni caricate in modo pigro.
Strategie ereditarieta': Single Table, Joined Table, Table Per Class.
Q: Come risolvi il problema N+1 delle query in JPA?R: Utilizzo JOIN FETCH nelle query JPQL, applico @EntityGraph per definire i fetch plan a livello di query, oppure configuro batch fetching con @BatchSize.
Q: Qual e' la differenza tra EntityManager.find() e EntityManager.getReference()?R: find() esegue subito la SELECT tornando l'entita', mentre getReference() restituisce un proxy lazy caricato solo al primo accesso ai campi, lanciando eccezione se non esiste.
Q: Quando si verifica la LazyInitializationException e come si previene?R: Si verifica accedendo a una collezione lazy a Sessione/EntityManager gia' chiuso; la prevengo caricando i dati necessari nella transazione con JOIN FETCH o EntityGraph.
Q: Qual e' la differenza tra merge() e persist()?R: persist() rende managed una nuova entita' transient; merge() copia lo stato di un'entita' detached su un'istanza managed gestita dal Persistence Context.
Mancata sincronizzazione dei due lati di una relazione @OneToMany bidirezionale.
Uso improprio di FetchType.EAGER che genera query a cascata incontrollate.
Dichiarare annotazioni sia su getter che su field nella stessa gerarchia.
Implementare equals() e hashCode() usando solo ID generati prima del flush.
Esecuzione di DML diretti in bulk via JPQL che scavalcano il Persistence Context.
Modifiche a entita' Detached senza richiamare esplicitamente merge().
// Entita' con relazione bidirezionale e join fetch
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
@BatchSize(size = 25)
private List<OrderItem> items = new ArrayList<>();
}
Java
// Query JPQL con JOIN FETCH per evitare N+1
TypedQuery<Order> query = entityManager.createQuery(
"SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id", Order.class
);
query.setParameter("id", orderId);
Order order = query.getSingleResult();
Java
// Definizione ed uso di NamedEntityGraph
@NamedEntityGraph(
name = "Order.items",
attributeNodes = @NamedAttributeNode("items")
)
@Entity
public class Order { /* ... */ }
// Utilizzo del graph
EntityGraph<?> graph = entityManager.getEntityGraph("Order.items");
Map<String, Object> hints = Map.of("jakarta.persistence.fetchgraph", graph);
Order order = entityManager.find(Order.class, orderId, hints);
[ ] Configurazione cache di secondo livello (L2) con Ehcache/Infinispan
[ ] Differenza comportamentale tra fetchgraph e loadgraph
[ ] Strategie di locking ottimistico (@Version) e pessimistico (LockModeType)
[ ] Ciclo di vita dei callback JPA (@PrePersist, @PostUpdate)