Cloud Spanner con ORM Hibernate

1. Panoramica

Hibernate è diventata di fatto la soluzione ORM standard per i progetti Java. Supporta tutti i principali database relazionali e consente strumenti ORM ancora più potenti come Spring Data JPA. Inoltre, sono disponibili molti framework compatibili con Hibernate, ad esempio Spring Boot, Microprofile e Quarkus.

Cloud Spanner Dialect for 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. Questo può aiutarti a migrare le applicazioni esistenti nel cloud o a 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 connetta a Cloud Spanner
  • Creare un database Cloud Spanner
  • Come utilizzare il dialetto Cloud Spanner per l'ORM Hibernate
  • Come implementare le operazioni create-read-update-delete (CRUD) con Hibernate

Che cosa ti serve

  • Un progetto Google Cloud
  • Un browser, ad esempio Chrome o Firefox

2. Configurazione e requisiti

Configurazione dell'ambiente da seguire in modo autonomo

  1. Accedi alla console Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o G Suite, devi crearne uno.

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH0Paw0QRxd3

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Ricorda l'ID progetto, un nome univoco in tutti i progetti Google Cloud (il nome precedente è già stato utilizzato e non funzionerà correttamente). Verrà indicato più avanti in questo codelab come PROJECT_ID.

  1. Successivamente, dovrai abilitare la fatturazione in Cloud Console per utilizzare le risorse Google Cloud.

Eseguire questo codelab non dovrebbe costare molto. Assicurati di seguire le istruzioni nella sezione "Pulizia" in cui viene spiegato come arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial. I nuovi utenti di Google Cloud sono idonei al programma di prova senza costi di 300$.

Attiva Cloud Shell

  1. Dalla console Cloud, fai clic su Attiva Cloud Shell R47NpBm6yyzso5vnxnRBiciDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZwsZqPQAR.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Se non hai mai avviato Cloud Shell, verrà visualizzata una schermata intermedia (below the fold) in cui viene descritto di cosa si tratta. In tal caso, fai clic su Continua (e non la vedrai più). Ecco come appare quella singola schermata:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Il provisioning e la connessione a Cloud Shell dovrebbero richiedere solo qualche istante.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Questa macchina virtuale viene caricata con tutti gli strumenti di sviluppo necessari. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Gran parte, se non tutto, del lavoro in questo codelab può essere svolto semplicemente con un browser o 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.

  1. 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 il lancio di Cloud Shell, puoi iniziare a utilizzare gcloud per interagire con il tuo progetto Google Cloud.

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'archetype della guida rapida Maven per creare una semplice applicazione per 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 sulla console.

5. Aggiungi dipendenze

Esploriamo il codice sorgente aprendo l'editor di Cloud Shell e sfogliando la directory spanner-hibernate-codelab.

b5cb37d043d4d2b0.png

Finora abbiamo solo un'app console Java di base che stampa "Hello World!". Tuttavia, vogliamo davvero scrivere un'applicazione Java che utilizzi Hibernate per comunicare con Cloud Spanner. Per farlo, abbiamo bisogno del dialetto Cloud Spanner per Hibernate, del driver JDBC di Cloud Spanner e del core Hibernate. 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

Poi creeremo i file di configurazione Hibernate hibernate.cfg.xml e hibernate.properties. Esegui questo comando per creare i file vuoti, quindi 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 entità più tardi.

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 inoltre sapere come connettersi all'istanza Cloud Spanner e quale dialetto utilizzare. Pertanto, gli specifichiamo di utilizzare SpannerDialect per la sintassi SQL, il driver JDBC di Spanner e la stringa di connessione JDBC con le coordinate del database. Questo nome verrà inserito 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 esiste uno schema di database, 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, ti conviene anche assicurarti che siano configurate le credenziali di autenticazione, utilizzando un file JSON dell'account di servizio nella variabile di ambiente GOOGLE_APPLICATION_CREDENTIALS o le credenziali predefinite dell'applicazione configurate utilizzando il comando gcloud auth application-default login. Tuttavia, poiché viene eseguito in Cloud Shell, le credenziali di progetto predefinite sono già configurate.

7. crea classi di entità annotate

Ora siamo pronti a scrivere del codice.

Definiamo due semplici oggetti Java (POJO) che verranno mappati alle tabelle in Cloud Spanner: Singer e Album. Album avrà una relazione @ManyToOne con Singer. Potremmo anche aver mappato i 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 del corso.

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. Si tratta di un tipo di ID preferito in Cloud Spanner, perché evita gli hotspot mentre il sistema divide i dati tra i server in base a intervalli di chiavi. Funzionerebbe anche una chiave intero monotonica che aumenta in modo monotonico, ma le prestazioni sono inferiori.

8. Salvare ed eseguire query sulle entità

Una volta definiti tutti gli oggetti configurati e le entità, possiamo iniziare a scrivere sul database ed eseguirvi query. Apriremo un Session Hibernate, quindi lo utilizzeremo per eliminare prima tutte le righe di tabella nel metodo clearData(), salvare alcune entità nel metodo writeData() ed eseguire alcune query utilizzando Hibernate Query Language (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 sulla pulizia dei thread del daemon che Maven proverà a 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 relativi alle tabelle Cantante e Album nel database, vedrai quanto segue:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Esegui la pulizia

Eliminamo 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 l'utilizzo di Hibernate
  • Hai creato due entità: Artista e Album
  • Hai generato automaticamente lo schema del database per la tua applicazione
  • Hai salvato le entità in Cloud Spanner ed hai eseguito query

Ora conosci i passaggi chiave necessari per scrivere un'applicazione Hibernate con Cloud Spanner.

Passaggi successivi