springBootPiuOggetti

applicazione che gestisce più oggetti

Scenario

Le applicazioni che scriviamo con Spring boot sono tecnicamente dei microservizi, per quanto abbiamo visto finora gli oggetti che rappresentano la nostra realtà sono non correlati (in verità abbiamo gestito un solo oggetto ma gestirne di più allo stesso modo non fa differenza). Questa situazione però è troppo semplice per essere generalizzabile, in questo capitolo vediamo come si realizza un sistema che usa due oggeti che sono in una relazione uno a molti.

La realtà che andiamo a modellare prevede che ci siano degli hotel ciascuno dei quali ha più stanze. Non scriveremeo il sistema per intero ma soltanto le parti "diverse" rispetto al caso di entità di un solo tipo.

Il modello dei dati

Come dicevamo un oggeto Hotel con alcune proprietà legato a più oggetti Stanza con altre proprietà.

Il database va creato con queste due istruzioni (o con piccoli aggiustamenti dovuti al fatto che si usa h2 o MySQL o altro):

create table hotel ( id integer not null auto_increment, nome varchar(50), telefono varchar(20), provincia char(2), primary key (id) ); create table stanza ( id integer not null auto_increment, nome varchar(255), prezzo float(53) not null, hotel_id integer references hotel(id), primary key (id) );

Attenzione al campo chiamato hotel_id: il nome non è casuale ma formato dal nome della tabella a cui punta e dal relativo campo chiave legati da un underscore (solito discorso "Convention over configuration", FIXME: serve riferimento). Se si vogliono utilizzare nomi diversi è possibile usare l'annotazione @JoinColumn nella classe che rappresenta il "molti" della relazione, Stanza nel nostro caso.

package it.aspix.hotel; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; @Entity public class Hotel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String nome; private String telefono; private String provincia; @OneToMany(mappedBy = "hotel", fetch = FetchType.LAZY) private List<Stanza> stanze = new ArrayList<Stanza>(); /* si prosegue con usuali get/set */

Risulta evidente che l'unica cosa di cui parlare è l'attributo stanze: questo attributo non risiede nel database e lo si capisce dalla annotazione @OneToMany che specifica tre cose: intanto che un Hotel è legato a più Stanza, poi con mappedBy = "hotel" il fatto che nella classe Stanza è la proprietà hotel che la lega ad un hotel, fetch = FetchType.LAZY indica invece che una volta che è stato letto un Hotel dal database le relative stanze vanno lette soltanto all'occorrenza (sarebbe a dire se qualcuno usa la proprietà stanze).

package it.aspix.hotel; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; @Entity public class Stanza { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String nome; private double prezzo; @ManyToOne private Hotel hotel; /* si prosegue con usuali get/set */

Nella classe Stanza c'è soltanto una annotazione di cui parlare: @ManyToOne che indica semplicemente che molte istanze della classe Stanza appartengono allo stesso Hotel, in pratica fa il paio con @OneToMany presente in Hotel.

Per tutto il resto non cambia nulla, ogni classe dovrà avere il relativo JpaRepository scritto come in precedenza e potrenno esserci una o più classi annotate con @RestController.

Serializzazione in JSON

Non ce ne occupiamo noi ma Spring boot giusto? Si e no... tutte le cose di default vengono fatte in automatico ma in questo caso sorge un problema: quando deve essere rappresentato con testo JSON un Hotel vengono automaticamente inserite tutte le Stanza relative (il che ci fa comodo) ma ogni Stanza ha a sua volta un Hotel, che ha delle Stanza e via di seguito: si viene a creare una specie di ciclo infinito.

La libreria che usiamo che crea il testo JSON (formalmente di dice "serializza gli oggetti in JSON") si chiama jackson ed è possibile indicargli che deve passare da Hotel a Stanza ma non viceversa: quando stampa una stanza non vogliamo che metta di nuovo l'Hotel. Di nuovo si usano due annotazioni: @JsonManagedReference e @JsonBackReference. La prima va inserita in Hotel (da Hotel devo passare a Stanza) mentre la seconda in Stanza (da Stanza non devo passare ad Hotel).

Due righe in più in totale!

package it.aspix.hotel; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import com.fasterxml.jackson.annotation.JsonManagedReference; @Entity public class Hotel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String nome; private String telefono; private String provincia; @OneToMany(mappedBy = "hotel", fetch = FetchType.LAZY) @JsonManagedReference private List<Stanza> stanze = new ArrayList<Stanza>(); /* si prosegue con usuali get/set */
package it.aspix.hotel; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne import com.fasterxml.jackson.annotation.JsonBackReference; @Entity public class Stanza { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String nome; private double prezzo; @ManyToOne @JsonBackReference private Hotel hotel; /* si prosegue con usuali get/set */

Le annotazioni @JsonManagedReference/JsonBackReference hanno un attributo value che può essere omesso (come nell'esempio sopra) ma che diventa necessario ad esempio quando in una classe si trovano più @JsonBackReference, nel caso del nostro esempio l'attributo value va usato nel seguente modo:

public class Hotel { ... @JsonManagedReference(value="hotel-stanza") private List<Stanza> stanze = new ArrayList<Stanza>(); ...
package it.aspix.hotel; public class Stanza { ... @JsonBackReference(value="hotel-stanza") private Hotel hotel; ...

Ancora sui nomi dei servizi

Nulla cambia rispetto a ciò che è scritto nel capitolo precedente, ad esempio per avere le informazioni sull'Hotel il cui id è 75 useremo la url /hotel/75 ma come ci si regola se voglio la stanza 654 dell'hotel 75? Così: /hotel/75/stanza/654