Esercitazioni di Spring Boot 4: lavorare con i database
In questa nuova esercitazione vedremo come collegare il nostro servizio REST ad un database MySQL in cui verranno memorizzati i dati relativi ai calciatori.
Per fare questo ci serviremo del progetto Spring Data che fornisce tutti gli strumenti necessari per gestire la persistenza. In particolare offre delle interfacce che consentono l'accesso ai dati, indipendentemente dal DBMS utilizzato. In questo modo potremo passare da una soluzione ad un'altra senza dover riscrivere il nostro codice.
Mettiamoci subito all'opera.
In primo luogo sono richieste due ulteriori dipendenze per gestire JPA (Java Persistence API)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
e MySQL (attraverso un opportuno connector)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
Diamo per scontato che sia disponibile un'istanza di MySQL in esecuzione localmente (come in questo esempio) o su un server remoto.
Nel file application.properties aggiungiamo i parametri di configurazione del database utilizzato
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/players
spring.datasource.username=root
spring.datasource.password=root
Si tratta della stringa di connessione al database in cui sono riportati l'host (localhost), la porta (3306 quella di default), il nome del database (players) e le relative credenziali di accesso.
Particolare attenzione va riservata alla proprietà specificata nella prima riga, che consente di creare in automatico lo schema della tabella. Tale operazione sarà effettuata ad ogni riavvio. Il suo utilizzo "in produzione" è caldamente sconsigliato perché, lavorando con un database esistente, si andrebbero a cancellare i dati già presenti.
E' possibile che venga riportato il seguente errore
Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
per cui disabilitiamo temporaneamente SSL (Secure Socket Layer) modificando la stringa di connessione nel modo seguente
spring.datasource.url=jdbc:mysql://localhost:3306/players?useSSL=false
Ora dobbiamo creare un'entità, ovvero una classe opportunamente annotata, che sarà il fulcro del mapping tra gli oggetti Java e il database. In particolare ciascuna istanza di questa classe conterrà i dati da salvare nel database oppure quelli recuperati da quest'ultimo in fase di lettura.
In sostanza si tratta del classico POJO (nel nostro caso la classe Player
) a cui sono state aggiunte delle annotazioni specifiche che andiamo a commentare.
@Entity
indica per l'appunto che la classe rappresenta un'entità e come tale verrà trattata da Spring, mentre @Id
identifica l'attributo che fungerà da chiave primaria per il database. In particolare abbiamo scelto la generazione automatica dei valori. Volendo sarebbe possibile assegnare direttamente un valore all'id ma occorrerebbe gestire i vincoli imposti dalla primary key. Se non strettamente necessario meglio lasciar fare al framework.
Per una migliore organizzazione del codice la classe Player
è stata inserita nella cartella models che ospiterà tutte le entità dell'applicazione.
Ma come si riesce ad interagire con il database per l'inserimento e/o il recupero dei dati?
Introduciamo il concetto di repository, che consente a Spring Data di fare la "magia".
A noi spetta il compito di creare un'interfaccia, che chiameremo PlayersRepository e posizioneremo nella cartella repositories.
Tale interfaccia va annotata con @Repository
, che non è altro che una specializzazione di @Component
e serve ad affidare a Spring la gestione del lifecycle.
Tale interfaccia deve estendere la JpaRepository
indicando il tipo dell'entità e quello della chiave primaria. Sarebbe possibile estendere anche la CrudRepository
che però non gestisce altrettanto bene la paginazione dei risultati.
In sostanza si tratta di scrivere il seguente codice
@Repository
public interface PlayerRepository extends JpaRepository<Player, Long> { }
Non è richiesta alcuna implementazione in quanto Spring si occuperà di metterci a disposizione una serie di metodi pronti all'uso come
findAll()
findOne()
save()
delete()
per citarne alcuni. Quindi il nostro "sforzo" di programmazione è davvero ridotto all'osso.
In realtà abbiamo a disposizione strumenti ben più complessi, come i query method, che per ora tralasciamo per semplificare al massimo il tutorial.
Non ci resta che collegare il repository con il controller REST, attraverso un service.
Nella precedente esercitazione abbiamo parlato dell'importanza di un service per separare gli endpoint presenti nel controller dai metodi che gestiscono effettivamente le richieste.
Nel nostro caso si tratterà di [PlayerServiceDB](https://github.com/mcicolella/esercitazioni-spring-boot/blob/master/esercitazione-4/players/src/main/java/net/emmecilab/players/services/PlayerServiceDB.java)
.
Sempre nell'ottica di una migliore organizzazione del codice è stata introdotta l'interfaccia [PlayerService](https://github.com/mcicolella/esercitazioni-spring-boot/blob/master/esercitazione-4/players/src/main/java/net/emmecilab/players/services/PlayerService.java)
.
In questo modo sarà ridotto al minimo l'impatto delle modifiche sul controller.
Infatti non dovremo fare altro che "iniettare" il nuovo service con
@Autowired
private PlayerServiceDB playerService;
sostituendo il precedente. Non è richiesta alcun'altra modifica dal momento che tutti i service implementano la stessa interfaccia.
Ovviamente, sotto il cofano, il PlayerServiceDB
utilizzerà i metodi del repository JPA che abbiamo creato e che sarà "iniettato" al suo interno.
Dopo aver compilato ed eseguito l'applicazione, proviamo ad inserire un nuovo giocatore da riga di comando
curl -X POST -H "Content-Type: application/json" -d '{"firstName":"Gonzalo","lastName":"Higuain","team":"Napoli","position":"forward"}' http://localhost:8081/players-0.0.1-SNAPSHOT/players
Come possiamo notare dai log l'inserimento è avvenuto con successo
Aggiornando la lista dei calciatori vedremo un nuovo elemento a cui è stata assegnata automaticamente la primary key che non avevamo specificato nel json.
Discorso analogo per le altre operazioni che potrete testare direttamente.
La prossima volta vedremo come documentare le API servendoci di Swagger.
[VIDEO]
[LINKS]
- Esercitazioni di Spring Boot - Indice argomenti
- Tutorial di Spring Boot - Indice argomenti
- Flashcards Anki per lo studio
- Libri consigliati