1. Panoramica
Hibernate è diventata la soluzione ORM standard di fatto per i progetti Java. Supporta tutti i principali database relazionali e consente di utilizzare strumenti ORM ancora più potenti come Spring Data JPA. Inoltre, esistono molti framework compatibili con Hibernate, come Spring Boot, Microprofile e Quarkus.
Cloud Spanner Dialect per Hibernate ORM consente di utilizzare Hibernate con Cloud Spanner. Ottieni i vantaggi di Cloud Spanner (scalabilità e semantica relazionale) con la persistenza idiomatica di Hibernate. In questo modo, puoi eseguire la migrazione delle applicazioni esistenti al cloud o scriverne di nuove sfruttando la maggiore produttività degli sviluppatori offerta dalle tecnologie basate su Hibernate.
Cosa imparerai a fare
- Come scrivere una semplice applicazione Hibernate che si connette a Cloud Spanner
- Come creare un database Cloud Spanner
- Come utilizzare il dialetto Cloud Spanner per Hibernate ORM
- Come implementare le operazioni di creazione, lettura, aggiornamento ed eliminazione (CRUD) con Hibernate
Che cosa ti serve
2. Configurazione e requisiti
Configurazione dell'ambiente autonomo
- Accedi alla console Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai già un account Gmail o G Suite, devi crearne uno.
Ricorda l'ID progetto, un nome univoco tra tutti i progetti Google Cloud (il nome sopra è già stato utilizzato e non funzionerà per te, mi dispiace). In questo codelab verrà chiamato PROJECT_ID.
- Successivamente, dovrai abilitare la fatturazione in Cloud Console per utilizzare le risorse Google Cloud.
L'esecuzione di questo codelab non dovrebbe costare molto, se non nulla. Assicurati di seguire le istruzioni riportate nella sezione "Pulizia", che ti consiglia come arrestare le risorse in modo da non incorrere in addebiti oltre questo tutorial. I nuovi utenti di Google Cloud possono beneficiare del programma prova senza costi di 300$.
Attiva Cloud Shell
- Nella console Cloud, fai clic su Attiva Cloud Shell
.
Se non hai mai avviato Cloud Shell, viene visualizzata una schermata intermedia (sotto la piega) che ne descrive le funzionalità. In questo caso, fai clic su Continua e non comparirà più. Ecco come si presenta la schermata intermedia:
Bastano pochi istanti per eseguire il provisioning e connettersi a Cloud Shell.
Questa macchina virtuale è caricata con tutti gli strumenti per sviluppatori di cui avrai bisogno. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Gran parte del lavoro per questo codelab, se non tutto, può essere svolto semplicemente con un browser o con Chromebook.
Una volta eseguita la connessione a Cloud Shell, dovresti vedere che il tuo account è già autenticato e il progetto è già impostato sul tuo ID progetto.
- Esegui questo comando in Cloud Shell per verificare che l'account sia autenticato:
gcloud auth list
Output comando
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
gcloud config list project
Output comando
[core] project = <PROJECT_ID>
In caso contrario, puoi impostarlo con questo comando:
gcloud config set project <PROJECT_ID>
Output comando
Updated property [core/project].
3. Crea un database
Dopo l'avvio di Cloud Shell, puoi iniziare a utilizzare gcloud per interagire con il tuo progetto GCP.
Innanzitutto, abilita l'API Cloud Spanner.
gcloud services enable spanner.googleapis.com
Ora creiamo un'istanza Cloud Spanner denominata codelab-instance.
gcloud spanner instances create codelab-instance \ --config=regional-us-central1 \ --description="Codelab Instance" --nodes=1
Ora dobbiamo aggiungere un database a questa istanza. Lo chiameremo codelab-db.
gcloud spanner databases create codelab-db --instance=codelab-instance
4. Crea un'app vuota
Utilizzeremo l'archetipo Maven Quickstart per creare una semplice applicazione console Java.
mvn archetype:generate \ -DgroupId=codelab \ -DartifactId=spanner-hibernate-codelab \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DinteractiveMode=false
Passa alla directory dell'app.
cd spanner-hibernate-codelab
Compila ed esegui l'app utilizzando Maven.
mvn compile exec:java -Dexec.mainClass=codelab.App
Dovresti vedere Hello World! stampato nella console.
5. Aggiungi dipendenze
Esploriamo il codice sorgente aprendo l'editor di Cloud Shell e sfogliando la directory spanner-hibernate-codelab.

Finora abbiamo solo un'app console Java di base che stampa "Hello World!". Tuttavia, vogliamo scrivere un'applicazione Java che utilizzi Hibernate per comunicare con Cloud Spanner. A questo scopo, avremo bisogno del dialetto Cloud Spanner per Hibernate, del driver JDBC Cloud Spanner e di Hibernate Core. Aggiungiamo quindi le seguenti dipendenze al blocco <dependencies> all'interno del file pom.xml.
pom.xml
<!-- Spanner Dialect -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
<version>1.5.0</version>
</dependency>
<!-- JDBC Driver -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-jdbc</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.29.Final</version>
</dependency>
6. Configura Hibernate ORM
Successivamente, creeremo i file di configurazione di Hibernate hibernate.cfg.xml e hibernate.properties. Esegui questo comando per creare i file vuoti e poi modificali utilizzando l'editor di Cloud Shell.
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
Quindi, comunichiamo a Hibernate le classi di entità annotate che mapperemo al database compilando hibernate.cfg.xml. Creeremo le classi di entità in un secondo momento.
src/main/resources/hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<!-- Annotated entity classes -->
<mapping class="codelab.Album"/>
<mapping class="codelab.Singer"/>
</session-factory>
</hibernate-configuration>
Hibernate deve anche sapere come connettersi all'istanza Cloud Spanner e quale dialetto utilizzare. Quindi, gli diremo di utilizzare SpannerDialect per la sintassi SQL, il driver JDBC Spanner e la stringa di connessione JDBC con le coordinate del database. Queste informazioni vengono inserite nel file hibernate.properties.
src/main/resources/hibernate.properties
hibernate.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
hibernate.connection.driver_class=com.google.cloud.spanner.jdbc.JdbcDriver
hibernate.connection.url=jdbc:cloudspanner:/projects/{PROJECT_ID}/instances/codelab-instance/databases/codelab-db
# auto-create or update DB schema
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
Ricorda di sostituire {PROJECT_ID} con il tuo ID progetto, che puoi ottenere eseguendo questo comando:
gcloud config get-value project
Poiché non abbiamo uno schema di database esistente, abbiamo aggiunto la proprietà hibernate.hbm2ddl.auto=update per consentire a Hibernate di creare le due tabelle in Cloud Spanner quando eseguiamo l'app per la prima volta.
In genere, devi anche assicurarti che le credenziali di autenticazione siano configurate utilizzando un file JSON del service account nella variabile di ambiente GOOGLE_APPLICATION_CREDENTIALS o le credenziali predefinite dell'applicazione configurate utilizzando il comando gcloud auth application-default login. Tuttavia, poiché l'esecuzione avviene in Cloud Shell, le credenziali del progetto predefinite sono già configurate.
7. Creare classi di entità annotate
Ora siamo pronti per scrivere un po' di codice.
Definiremo due POJO (Plain Old Java Object) che verranno mappati alle tabelle in Cloud Spanner: Singer e Album. Album avrà una relazione @ManyToOne con Singer. Avremmo anche potuto mappare gli Singer agli elenchi dei loro Album con un'annotazione @OneToMany, ma per questo esempio non vogliamo caricare tutti gli album ogni volta che dobbiamo recuperare un cantante dal database.
Aggiungi le classi di entità Singer e Album.
Crea i file della classe.
touch src/main/java/codelab/Singer.java \ && touch src/main/java/codelab/Album.java
Incolla i contenuti dei file.
src/main/java/codelab/Singer.java
package codelab;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Type;
@Entity
public class Singer {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID singerId;
private String firstName;
private String lastName;
@Temporal(TemporalType.DATE)
private Date birthDate;
public Singer() {
}
public Singer(String firstName, String lastName, Date birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public UUID getSingerId() {
return singerId;
}
public void setSingerId(UUID singerId) {
this.singerId = singerId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Singer)) {
return false;
}
Singer singer = (Singer) o;
if (!firstName.equals(singer.firstName)) {
return false;
}
if (!lastName.equals(singer.lastName)) {
return false;
}
return birthDate.equals(singer.birthDate);
}
@Override
public int hashCode() {
int result = firstName.hashCode();
result = 31 * result + lastName.hashCode();
result = 31 * result + birthDate.hashCode();
return result;
}
}
src/main/java/codelab/Album.java
package codelab;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Type;
@Entity
public class Album {
@Id
@GeneratedValue
@Type(type = "uuid-char")
UUID albumId;
@ManyToOne
Singer singer;
String albumTitle;
public Album() {
}
public Album(Singer singer, String albumTitle) {
this.singer = singer;
this.albumTitle = albumTitle;
}
public UUID getAlbumId() {
return albumId;
}
public void setAlbumId(UUID albumId) {
this.albumId = albumId;
}
public Singer getSinger() {
return singer;
}
public void setSinger(Singer singer) {
this.singer = singer;
}
public String getAlbumTitle() {
return albumTitle;
}
public void setAlbumTitle(String albumTitle) {
this.albumTitle = albumTitle;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Album)) {
return false;
}
Album album = (Album) o;
if (!singer.equals(album.singer)) {
return false;
}
return albumTitle.equals(album.albumTitle);
}
@Override
public int hashCode() {
int result = singer.hashCode();
result = 31 * result + albumTitle.hashCode();
return result;
}
}
Tieni presente che per questo esempio utilizziamo un UUID generato automaticamente per la chiave primaria. Questo è un tipo di ID preferito in Cloud Spanner, perché evita gli hotspot in quanto il sistema divide i dati tra i server in base agli intervalli di chiavi. Funzionerebbe anche una chiave intera crescente in modo monotono, ma potrebbe essere meno efficiente.
8. Salvare ed eseguire query sulle entità
Una volta configurato tutto e definiti gli oggetti entità, possiamo iniziare a scrivere nel database ed eseguire query. Apriremo un Session Hibernate e lo utilizzeremo per eliminare prima tutte le righe della tabella nel metodo clearData(), salvare alcune entità nel metodo writeData() ed eseguire alcune query utilizzando il linguaggio di query Hibernate (HQL) nel metodo readData().
Sostituisci i contenuti di App.java come segue:
src/main/java/codelab/App.java
package codelab;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class App {
public final static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
// create a Hibernate sessionFactory and session
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata()
.buildSessionFactory();
Session session = sessionFactory.openSession();
clearData(session);
writeData(session);
readData(session);
// close Hibernate session and sessionFactory
session.close();
sessionFactory.close();
}
private static void clearData(Session session) {
session.beginTransaction();
session.createQuery("delete from Album where 1=1").executeUpdate();
session.createQuery("delete from Singer where 1=1").executeUpdate();
session.getTransaction().commit();
}
private static void writeData(Session session) {
session.beginTransaction();
Singer singerMelissa = new Singer("Melissa", "Garcia", makeDate("1981-03-19"));
Album albumGoGoGo = new Album(singerMelissa, "Go, Go, Go");
session.save(singerMelissa);
session.save(albumGoGoGo);
session.save(new Singer("Russell", "Morales", makeDate("1978-12-02")));
session.save(new Singer("Jacqueline", "Long", makeDate("1990-07-29")));
session.save(new Singer("Dylan", "Shaw", makeDate("1998-05-02")));
session.getTransaction().commit();
}
private static void readData(Session session) {
List<Singer> singers = session.createQuery("from Singer where birthDate >= '1990-01-01' order by lastName")
.list();
List<Album> albums = session.createQuery("from Album").list();
System.out.println("Singers who were born in 1990 or later:");
for (Singer singer : singers) {
System.out.println(singer.getFirstName() + " " + singer.getLastName() + " born on "
+ DATE_FORMAT.format(singer.getBirthDate()));
}
System.out.println("Albums: ");
for (Album album : albums) {
System.out
.println("\"" + album.getAlbumTitle() + "\" by " + album.getSinger().getFirstName() + " "
+ album.getSinger().getLastName());
}
}
private static Date makeDate(String dateString) {
try {
return DATE_FORMAT.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}
Ora compiliamo ed eseguiamo il codice. Aggiungeremo l'opzione -Dexec.cleanupDaemonThreads=false per eliminare gli avvisi relativi alla pulizia dei thread daemon che Maven tenterà di eseguire.
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
Nell'output dovresti vedere qualcosa di simile a questo:
Singers who were born in 1990 or later: Jacqueline Long born on 1990-07-29 Dylan Shaw born on 1998-05-02 Albums: "Go, Go, Go" by Melissa Garcia
A questo punto, se vai alla console Cloud Spanner e visualizzi i dati delle tabelle Singer e Album nel database, vedrai qualcosa di simile a questo:


9. Esegui la pulizia
Eliminiamo l'istanza Cloud Spanner che abbiamo creato all'inizio per assicurarci che non utilizzi risorse inutilmente.
gcloud spanner instances delete codelab-instance
10. Complimenti
Congratulazioni, hai creato un'applicazione Java che utilizza Hibernate per rendere persistenti i dati in Cloud Spanner.
- Hai creato un'istanza Cloud Spanner e un database
- Hai configurato l'applicazione per utilizzare Hibernate
- Hai creato due entità: Artista e Album
- Hai generato automaticamente lo schema del database per la tua applicazione
- Hai salvato correttamente le entità in Cloud Spanner e le hai interrogate
Ora conosci i passaggi chiave necessari per scrivere un'applicazione Hibernate con Cloud Spanner.