Esercitazioni di Spring Boot 10: usiamo la cache
Nelle precedenti esercitazioni abbiamo visto come creare la nostra applicazione sui calciatori e come collegarla a MySQL (oppure al database in-memory H2).
Tuttavia ad ogni richiesta di dati sarà interrogato il database anche se gli stessi non hanno subito modifiche. In questo caso possiamo ricorrere ad una cache che ci consente di migliorare le performance.
In sostanza preleveremo i dati da quest'ultima e non andremo a coinvolgere il database a meno che non sia strettamente necessario.
La stessa soluzione può essere adottata anche per operazioni molto dispendiose che non occorre ripetere necessariamente ad ogni richiesta. Resta inteso che occorrerà prevedere dei meccanismi opportuni che consentano di sincronizzare i dati in caso di loro modifica, comprese le aggiunte e le cancellazioni.
Spring Boot mette a disposizione tutto il necessario per implementare un meccanismo di cache appoggiandosi a semplici strutture dati in memoria oppure ricorrendo a provider specifici come Hazelcast o Redis.
In estrema sintesi avremo bisogno di 4 operazioni per
- inserire
- leggere
- aggiornare
- rimuovere
i dati dalla cache.
Il modulo da utilizzare è Spring Cache che richiede l'aggiunta di una semplice dipendenza all'interno del file pom.xml.
Se Spring Boot trova un cache provider nel classpath cerca una configurazione di default, altrimenti configura il provider Simple, corrispondente ad una semplice ConcurrentHashMap.
Per abilitare il caching basta aggiungere l'annotazione @EnableCaching
alla classe principale (quella che contiene @SpringBootApplication
) oppure in una qualsiasi classe annotata con @Configuration
.
Ora vediamo come modificare il progetto utilizzato nella precedente esercitazione per attivare la cache in corrispondenza delle varie chiamate agli endpoint.
Dobbiamo intervenire sulla classe annotata con @Service
che implementa la logica di business ed interagisce con il database.
Iniziamo con il salvataggio dei dati aggiungendo al metodo addPlayer()
l'annotazione @Cacheable("players")
.
Questa annotazione attiva Spring Cache.
I dati sono memorizzati nella cache come una coppia key-value. Di default la key è rappresentata dai parametri in ingresso al metodo, mentre il value corrisponde al valore di ritorno del metodo stesso.
E' tuttavia possibile definire una key personalizzata, ma lo vedremo in un successivo approfondimento.
Se il metodo è chiamato per la prima volta Spring controlla che la key sia presente nella cache. In caso negativo viene eseguito il metodo, ovvero si accede al database, si leggono i dati e si salvano nella cache.
L'annotazione @Cacheable
ha proprio il compito di effettuare il salvataggio in una struttura dati identificata dal nome riportato come parametro (nel nostro caso "players").
Se i dati sono già presenti nella cache si recuperano da quest'ultima senza eseguire il metodo e coinvolgere il database.
Anche nel caso del recupero dei dati basta aggiungere l'annotazione @Cacheable("players")
ai metodi getAllPlayers()
e getPlayer(String id)
.
Spring Cache è in grado di stabilire quali elementi devono essere recuperati, se tutti o una singola voce.
Dal momento che la cache replica i dati "reali", in caso di modifica del contenuto del database occorre procedere ad una sincronizzazione per evitare disallineamenti.
Quindi andiamo ad aggiungere al metodo update()
l'annotazione @CachePut(value = "players", key = "#player.id")
dove il parametro value corrisponde al nome della cache in cui effettuare l'aggiornamento e il parametro key specifica il modo in cui la stessa deve essere valorizzata.
Si utilizza lo Spring Expression Language (SpEL). Poichè il metodo riceve in input un oggetto di tipo Player
, la key corrisponderà all'id di tale oggetto. Rispetto a quanto visto in precedenza il metodo update()
viene sempre eseguito e la cache aggiornata di conseguenza.
Anche la cancellazione dei dati prevede un'opportuna annotazione.
In questo caso interveniamo sul metodo delete()
e aggiungiamo @CacheEvict(value = "players", key = "#id")
dove i parametri specificati seguono la logica vista in precedenza.
Verrà eliminata dalla cache solo la voce identificata dalla key. Tuttavia possiamo svuotare l'intero contenuto impostando l'attributo allEntries=true
.
Per visualizzare tutte le operazioni eseguite sulla cache possiamo abilitare il log aggiungendo
logging.level.org.springframework.cache=TRACE
al file application.properties.
A questo punto eseguiamo l'applicazione con il classico
mvn spring-boot:run
Possiamo sfruttare la documentazione delle API per testare i vari endpoint.
In questo caso l’indirizzo da digitare nel browser è http://localhost:8080/swagger-ui.html
Essendo attiva la Basic Authentication sarà necessario autenticarsi utilizzando le credenziali mauro/1234 come predisposto nella precedente esercitazione.
Ecco come si presenta la pagina generata da Swagger.
Proviamo a visualizzare la scheda del calciatore con "id = 1" e notiamo dai log che viene fatta una ricerca nella cache utilizzando il valore dell'id come key e, non trovandola, viene eseguita la query sul database (tramite Hibernate) salvando il risultato nella cache.
Se ripetiamo la richiesta, questa volta ci verrà restituito il valore presente nella cache senza interrogare il database.
Proviamo ad effettuare la cancellazione del record ed osserviamo come la relativa voce della cache viene invalidata.
Aggiungiamo un nuovo calciatore, sfruttando il template messo a disposizione da Swagger.
Come possiamo notare viene eseguita la chiamata al database e poi aggiunta la voce alla cache.
Per ora è tutto. La prossima volta vedremo come utilizzare un cache provider, tipo Redis o Hazelcast.
[VIDEO]
[LINKS]