Untitled
SPRING, HIBERNATE
Cos'è l'Optimistic Lock in JPA/Hibernate e quando si usa?
Serve per gestire la concorrenza ottimistica. Si implementa con @Version. Hibernate verifica il campo versione al momento dell'update: se è cambiato rispetto al valore originale, genera OptimisticLockException.
Cos’è la MultipleBagFetchException e come la risolvi?
È un'eccezione generata da Hibernate quando provi a eseguire contemporaneamente fetch join di più liste. Puoi risolverla facendo fetch separati, usando Set al posto di List, oppure EntityGraph.
L’annotazione @Transactional funziona sui metodi privati? Perché?
No. Spring crea proxy solo sui metodi pubblici o protetti. I metodi privati non vengono intercettati perché Spring AOP è basato sui proxy generati dinamicamente.
A cosa servono @DynamicInsert e @DynamicUpdate in Hibernate?
Permettono a Hibernate di generare query SQL che contengono solo i campi effettivamente modificati o popolati, evitando aggiornamenti inutili di tutte le colonne.
Come implementeresti Logging distribuito nei microservizi? Che ruolo hanno traceId, spanId e Zipkin?
Usando strumenti come Sleuth e Zipkin, che propagano traceId e spanId nei log per correlare richieste distribuite su più microservizi e analizzare eventuali problemi.
Come gestiresti metriche e tracing (uso CPU, memoria) nei microservizi?
Con Micrometer e sistemi come Prometheus/Grafana, integrati con Zipkin per tracing. Permette monitoraggio continuo e alerting automatico.
Quale è il ciclo di vita di un bean?
-
Istanziazione
Constructor Injection: avviene proprio in questa fase, ovvero durante la creazione del bean. Tutte le dipendenze dichiarate nel costruttore sono già state istanziate e vengono iniettate immediatamente quando il bean viene creato. -
Iniezione delle dipendenze (@Autowired, Field Injection)
Dopo che l'oggetto è stato istanziato (e quindi dopo la constructor injection), Spring esegue la field injection (se utilizzata), valorizzando tutti i campi annotati con @Autowired. Questo avviene subito dopo la creazione dell'oggetto e prima che il bean venga usato.
-
Callback di inizializzazione (@PostConstruct)
Dopo che tutte le dipendenze sono state iniettate (tramite costruttore o @Autowired sui campi/metodi), Spring chiama il metodo annotato con @PostConstruct. Qui puoi eseguire ulteriori inizializzazioni che richiedono tutte le dipendenze già pronte.
-
Uso del bean
Il bean è ora pienamente operativo e può essere utilizzato dalla tua applicazione.
-
Callback di distruzione (@PreDestroy)
Prima che il bean venga distrutto, viene invocato il metodo annotato con @PreDestroy, dove puoi eseguire eventuali attività di cleanup.
Cos'è un EntityListener in JPA e quando lo useresti?
Sono classi o metodi annotati per intercettare eventi del ciclo di vita delle entità (es: @PrePersist, @PostLoad). Utile per auditing, logging, operazioni automatiche.
Qual è la differenza tra Event Listener e Entity Listener in Spring?
Event Listener è un meccanismo generico per intercettare eventi Spring a livello applicativo. Entity Listener è specifico per eventi del ciclo di vita delle entity JPA.
Cos'è un Aspect in Spring e quando si usa?
Un Aspect è una logica cross-cutting applicata con AOP. Serve per gestire logging centralizzato, transazioni, sicurezza, o caching senza sporcare il codice di business.
Come documenteresti una REST API usando OpenAPI (Swagger)?
Aggiungendo annotazioni (@Operation, @ApiResponse) o generando automaticamente documentazione da controller con Springdoc o SpringFox, ottenendo interfaccia esplorabile e documentata.
Cos'è un NaturalId in Hibernate?
È un identificativo alternativo della entity, naturale e stabile (es. codice fiscale, email). Utilizzabile con annotazione @NaturalId.
Che cos’è Hibernate Envers e a cosa serve?
È un modulo Hibernate per auditing automatico di entità. Tiene traccia delle modifiche storiche su entità con versionamento automatico dei dati.
Cos'è un Intersection Type in Java?
Un tipo derivato dall'intersezione di più interfacce, utile per esprimere che un parametro o variabile implementa contemporaneamente più interfacce, ad esempio <T extends InterfaceA & InterfaceB>.
Se un oggetto è dichiarato final, posso modificare i suoi campi interni?
Sì, l’oggetto final non può cambiare il riferimento stesso, ma puoi modificare i campi interni se non sono final o immutabili.
Quali tipi di transazioni e livelli di isolamento conosci in Spring/JPA?
Tipi transazione: REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, NEVER, MANDATORY, NESTED. Livelli isolamento: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE.
Se voglio escludere un bean Spring se non ho una certa classe nel classpath, quale annotazione uso?
@ConditionalOnClass
Che differenza c’è tra fetch EAGER e LAZY in Hibernate?
EAGER carica immediatamente l’entità collegata. LAZY posticipa il caricamento finché non si accede esplicitamente.
Quali librerie di mapping DTO-Entity conosci?
MapStruct, ModelMapper, Dozer, JMapper.
Puoi spiegare brevemente cosa sono le proprietà ACID nelle transazioni?
Atomicità (tutto o niente), Coerenza (stato valido), Isolamento (transazioni indipendenti), Durabilità (persistenza permanente).
Cos’è la cache di secondo livello Hibernate e quando la useresti?
Una cache condivisa tra sessioni per evitare continue query al DB. Utile per entità lette spesso e aggiornate raramente.
Qual è la differenza tra save() e persist() in Hibernate?
save() restituisce un identificatore e forza insert immediato. persist() è void e effettua insert al flush o commit.
Cosa sono le query derivate in Spring Data JPA?
Metodi che generano automaticamente query SQL dal nome, come findByNameAndAge.
Cos’è il problema N+1 query e come lo risolvi in Hibernate?
Avviene caricando entità relazionate con LAZY senza fetch join, causando molte query. Si risolve con JOIN FETCH, EntityGraph o batch-size.
Qual è la differenza tra Authentication e Authorization in Spring Security?
Authentication verifica chi è l'utente, Authorization verifica cosa può fare l’utente autenticato.
Qual è la differenza tra JPQL e Criteria API?
JPQL è string-based simile a SQL. Criteria API è tipizzata, più verbosa, ma ideale per query dinamiche sicure.
Cosa cambia tra Spring MVC e Spring WebFlux?
MVC è imperativo e blocking, basato su servlet (thread-per-request). WebFlux è reattivo, non-blocking, basato su Mono e Flux.
La transazione in self invocation rollbacka?
Devono passare per un proxy quindi in questo modo non funziona.
Esempio non funzionante Spring
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class EmployeeService { private final EmployeeRepository employeeRepository; @EventListener(ApplicationReadyEvent.class) public void test() { testSelfInvocation(); } @Transactional public void testSelfInvocation() { Employee emp1 = new Employee("Henry", "Ford", 22); employeeRepository.save(emp1); // ⚠️ this save is not rolled back BigDecimal.ONE.divide(BigDecimal.ZERO); // ❌ throws ArithmeticException Employee emp2 = new Employee("John", "Wick", 33); employeeRepository.save(emp2); } }
Soluzione Spring (Spostare il metodo @Transactional in un altro bean e iniettarlo)
@Service public class EmployeeService { @Autowired private EmployeeTransactionalService transactionalService; @EventListener(ApplicationReadyEvent.class) public void test() { transactionalService.testSelfInvocation(); // ✅ proxy Spring attivo } } @Service public class EmployeeTransactionalService { @Autowired private EmployeeRepository employeeRepository; @Transactional public void testSelfInvocation() { Employee emp1 = new Employee("Henry", "Ford", 22); employeeRepository.save(emp1); BigDecimal.ONE.divide(BigDecimal.ZERO); // ❌ rollback completo Employee emp2 = new Employee("John", "Wick", 33); employeeRepository.save(emp2); } }
Soluzione Spring (Recuperare il proxy Spring del proprio bean tramite ApplicationContext)
@Service public class EmployeeService { @Autowired private ApplicationContext context; @EventListener(ApplicationReadyEvent.class) public void test() { context.getBean(EmployeeService.class).testSelfInvocation(); // ✅ passa per il proxy } @Transactional public void testSelfInvocation() { Employee emp1 = new Employee("Henry", "Ford", 22); employeeRepository.save(emp1); BigDecimal.ONE.divide(BigDecimal.ZERO); // ❌ rollback Employee emp2 = new Employee("John", "Wick", 33); employeeRepository.save(emp2); } }
QUARKUS
La transazione in self invocation rollbacka?
Problema CDI, le chiamate interne alla stessa classe (this.metodo()) non passano per l'interceptor, quindi il problema della self-invocation resta.
Esempio non funzionante Quarkus
import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; import jakarta.inject.Inject; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; import java.math.BigDecimal; @ApplicationScoped public class EmployeeService { @Inject EntityManager em; @PostConstruct void onStartup() { test(); // Self-invocation, transactional NON attiva } public void test() { testSelfInvocation(); // <-- Non passa per l'interceptor } @Transactional public void testSelfInvocation() { Employee emp1 = new Employee("Henry", "Ford", 22); em.persist(emp1); // ⚠️ non sarà rollbackato BigDecimal.ONE.divide(BigDecimal.ZERO); // ❌ ArithmeticException Employee emp2 = new Employee("John", "Wick", 33); em.persist(emp2); } }
Soluzione Quarkus (spostare il metodo Transactional in altro bean e inject)
@Inject EmployeeTransactionalService transactionalService; public void test() { transactionalService.testSelfInvocation(); }
Soluzione Quarkus (Recuperare il proxy CDI tramite @Inject Instance<> o CDI.current())
@Inject Instance<EmployeeService> self; public void test() { self.get().testSelfInvocation(); // <-- PASSA dall'interceptor! }
PATTERN
🧱 Pattern Creazionali
Singleton
Assicura che una classe abbia una sola istanza e fornisce un punto di accesso globale.
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } }
Factory Method
Fornisce un’interfaccia per creare oggetti, lasciando alle sottoclassi la responsabilità dell’istanziazione.
interface Prodotto { void stampa(); } class ProdottoA implements Prodotto { public void stampa() { System.out.println("Prodotto A"); } } class Fabbrica { public static Prodotto creaProdotto() { return new ProdottoA(); } }
Builder
Separa la costruzione di un oggetto complesso dalla sua rappresentazione.
class Pizza { private String impasto; private boolean mozzarella; public static class Builder { private String impasto; private boolean mozzarella; public Builder impasto(String impasto) { this.impasto = impasto; return this; } public Builder mozzarella(boolean mozzarella) { this.mozzarella = mozzarella; return this; } public Pizza build() { Pizza p = new Pizza(); p.impasto = this.impasto; p.mozzarella = this.mozzarella; return p; } } }
🧩 Pattern Strutturali
Adapter
Permette a classi con interfacce incompatibili di lavorare insieme.
class LegacyPrinter { void printLegacy(String s) { System.out.println("Legacy: " + s); } } interface Printer { void print(String s); } class PrinterAdapter implements Printer { private LegacyPrinter legacy; public PrinterAdapter(LegacyPrinter legacy) { this.legacy = legacy; } public void print(String s) { legacy.printLegacy(s); } }
Facade
Fornisce un’interfaccia semplificata a un sottosistema complesso.
class CPU { void start() { System.out.println("CPU avviata"); } } class Memoria { void carica() { System.out.println("Memoria caricata"); } } class ComputerFacade { private CPU cpu = new CPU(); private Memoria memoria = new Memoria(); public void avviaComputer() { cpu.start(); memoria.carica(); } }
⚙️ Pattern Comportamentali
Strategy
Permette di scegliere dinamicamente un algoritmo tra più opzioni.
interface MetodoPagamento { void paga(double importo); } class CartaCredito implements MetodoPagamento { public void paga(double importo) { System.out.println("Pagamento con carta: " + importo); } } class Acquisto { private MetodoPagamento metodo; public Acquisto(MetodoPagamento metodo) { this.metodo = metodo; } public void eseguiPagamento(double importo) { metodo.paga(importo); } }
Observer
Notifica automaticamente più oggetti quando uno stato cambia.
interface Osservatore { void aggiorna(String stato); } class ConcreteOsservatore implements Osservatore { public void aggiorna(String stato) { System.out.println("Aggiornato: " + stato); } } class Soggetto { private List<Osservatore> osservatori = new ArrayList<>(); public void aggiungi(Osservatore o) { osservatori.add(o); } public void notifica(String stato) { for (Osservatore o : osservatori) { o.aggiorna(stato); } } }
Che cos’è il Circuit Breaker pattern?
Pattern per gestire fallimenti temporanei nelle chiamate tra microservizi, evitando sovraccarichi e aumentando resilienza.
Quali sono i principi SOLID?
I principi SOLID sono 5 linee guida fondamentali per progettare software manutenibile, scalabile e robusto, soprattutto in programmazione orientata agli oggetti.
🔹 S – Single Responsibility Principle (Responsabilità Singola)
Ogni classe dovrebbe avere una sola responsabilità e quindi un solo motivo per cambiare.
✅ Una classe che fa solo una cosa è più facile da capire, testare e modificare.
❌ Se una classe gestisce più cose (es. logica di business e accesso ai dati), è più fragile e difficile da manutenere.
🔹 O – Open/Closed Principle (Aperto/Chiuso)
Una classe dovrebbe essere aperta all'estensione ma chiusa alla modifica.
✅ Puoi estendere il comportamento di una classe esistente (ad es. tramite ereditarietà o strategia), senza modificarne il codice sorgente.
❌ Se modifichi ogni volta il codice esistente per aggiungere funzionalità, rischi di introdurre bug.
🔹 L – Liskov Substitution Principle (Sostituibilità di Liskov)
Una sottoclasse deve poter essere usata al posto della sua superclasse senza rompere il comportamento atteso.
✅ Se una funzione usa un oggetto di tipo Base
, dovrebbe funzionare anche con un oggetto di tipo Derived
.
❌ Se la sottoclasse modifica il comportamento in modo non compatibile (es. lancia eccezioni o ignora metodi), viola questo principio.
🔹 I – Interface Segregation Principle (Separazione delle Interfacce)
È meglio avere interfacce specifiche e piccole, piuttosto che un’unica interfaccia generale e pesante.
✅ Gli oggetti dovrebbero implementare solo i metodi che usano realmente.
❌ Se un’interfaccia ha troppi metodi inutili per una classe, questa sarà costretta a implementarli comunque (spesso con metodi vuoti o non sensati).
🔹 D – Dependency Inversion Principle (Inversione delle Dipendenze)
Le classi dovrebbero dipendere da astrazioni, non da classi concrete.
✅ Le dipendenze principali dovrebbero essere interfacce o classi astratte, non implementazioni dirette.
❌ Se una classe crea direttamente le sue dipendenze (new
), è difficile da testare e modificare.
Esempio pratico: usa iniezione delle dipendenze (Dependency Injection) per fornire alla classe ciò di cui ha bisogno.
JAVA
Posso rimuovere da una lista che sto ciclando un oggetto?
No non posso, se proprio devo posso usare un iterator o un removeif da java 8 (concurrent modification exception)
for (String str : myArrayList) { if (someCondition) { myArrayList.remove(str); } }
Come funziona MDC (Mapped Diagnostic Context) e a cosa serve?
Permette di aggiungere contesti specifici ai log, utili per analizzare richieste distribuite (es. aggiunta di traceId o correlationId).
Quanti metodi astratti ha un'interfaccia funzionale?
Un solo metodo astratto. Può avere metodi default e statici, ma sempre solo un abstract method.
Cosa sono covarianza e contravarianza nei generics Java?
Covarianza (extends): permette un tipo più specifico. Contravarianza (super): permette un tipo più generico.
Qual è la differenza tra Predicate, Function e Consumer in Java?
- Predicate<T>: ritorna un boolean, usato per condizioni (filtri).
- Function<T, R>: trasforma un tipo in un altro.
- Consumer<T>: consuma un valore, ma non restituisce nulla.
Predicate<String> isShort = s -> s.length() < 5; System.out.println(isShort.test("ciao")); // true Function<String, Integer> length = s -> s.length(); System.out.println(length.apply("ciao")); // 4 Consumer<String> printer = s -> System.out.println("Ciao " + s); printer.accept("Lorenzo"); // Ciao Lorenzo
Qual è la differenza tra map() e flatMap()?
- map(): applica una funzione e mantiene la struttura.
- flatMap(): "appiattisce" una struttura annidata.
List<String> words = List.of("a", "bb", "ccc"); List<Integer> lengths = words.stream() .map(String::length) // List<Integer> .collect(Collectors.toList()); // [1, 2, 3] List<List<String>> list = List.of(List.of("a", "b"), List.of("c")); List<String> flat = list.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); // [a, b, c]
A cosa serve Optional in Java?
Evita null, fornisce API fluente per gestire assenza/presenza di un valore.
Optional<String> maybeName = Optional.of("Lorenzo"); maybeName.ifPresent(System.out::println); // Lorenzo String name = maybeName.orElse("Default"); // name = "Lorenzo"
Differenza tra forEach() e peek() su uno stream
- forEach(): operazione terminale, esegue un’azione e consuma lo stream.
- peek(): operazione intermedia, utile per debug/logging.
Stream.of("a", "b", "c") .peek(e -> System.out.println("Peek: " + e)) .map(String::toUpperCase) .forEach(System.out::println); // Peek: a // A // Peek: b // B // Peek: c // C
Se usi solo peek() senza una terminal operation come collect() o forEach(), non viene eseguito nulla.
Qual è il tipo di lista che mantiene l’ordine degli elementi?
ArrayList, LinkedList (mantengono l’ordine di inserimento).
List<String> lista = new ArrayList<>(); lista.add("uno"); lista.add("due"); System.out.println(lista); // [uno, due]
Qual è il tipo di set che mantiene l’ordine degli elementi?
LinkedHashSet mantiene l’ordine di inserimento, TreeSet l’ordine naturale.
Set<String> set1 = new LinkedHashSet<>(List.of("b", "a", "c")); System.out.println(set1); // [b, a, c] Set<String> set2 = new TreeSet<>(List.of("b", "a", "c")); System.out.println(set2); // [a, b, c]
SQL
Che cos’è la normalizzazione del database e cosa comportano le prime tre forme normali?
È un processo per ridurre ridondanza e migliorare l’integrità dei dati.
- 1NF: Valori atomici e righe uniche.
- 2NF: Attributi non-chiave dipendono interamente dalla chiave primaria.
- 3NF: Attributi non-chiave non hanno dipendenze transitive.
Normalizzazione del Database – 1NF, 2NF, 3NF
1NF – Prima Forma Normale
Una tabella è in 1NF se:
- Ogni colonna contiene valori atomici (non liste o array).
- Ogni riga è univoca (con chiave primaria).
❌ Esempio NON in 1NF
ID_Ordine | Articoli |
---|---|
1 | Penna, Quaderno |
2 | Matita, Gomma |
La colonna "Articoli" contiene più valori → violazione 1NF.
✅ Esempio in 1NF (normalizzato)
ID_Ordine | Articolo |
---|---|
1 | Penna |
1 | Quaderno |
2 | Matita |
2 | Gomma |
2NF – Seconda Forma Normale
Una tabella è in 2NF se:
- È già in 1NF.
- Ogni colonna non chiave dipende dall’intera chiave primaria, non solo da una parte.
❌ Esempio NON in 2NF
Chiave primaria: (ID_Ordine, Articolo)
ID_Ordine | Articolo | Cliente |
---|---|---|
1 | Penna | Mario Rossi |
1 | Quaderno | Mario Rossi |
2 | Matita | Luca Bianchi |
2 | Gomma | Luca Bianchi |
"Cliente" dipende solo da
ID_Ordine
, non da tutta la chiave primaria → violazione 2NF.
✅ Esempio in 2NF
Tabella Ordini
ID_Ordine | Cliente |
---|---|
1 | Mario Rossi |
2 | Luca Bianchi |
Tabella DettagliOrdine
ID_Ordine | Articolo |
---|---|
1 | Penna |
1 | Quaderno |
2 | Matita |
2 | Gomma |
3NF – Terza Forma Normale
Una tabella è in 3NF se:
- È già in 2NF.
- Nessuna colonna non chiave dipende da un’altra colonna non chiave (no dipendenze transitive).
❌ Esempio NON in 3NF
ID_Ordine | Cliente | Indirizzo Cliente |
---|---|---|
1 | Mario Rossi | Via Roma, Roma |
2 | Luca Bianchi | Via Milano, Milano |
"Indirizzo Cliente" dipende da "Cliente" e non da
ID_Ordine
→ violazione 3NF.
✅ Esempio in 3NF
Tabella Ordini
ID_Ordine | Cliente |
---|---|
1 | Mario Rossi |
2 | Luca Bianchi |
Tabella Clienti
Cliente | Indirizzo |
---|---|
Mario Rossi | Via Roma, Roma |
Luca Bianchi | Via Milano, Milano |