Cloud Spanner z Hibernate ORM

Cloud Spanner z Hibernate ORM

Informacje o tym ćwiczeniu (w Codelabs)

subjectOstatnia aktualizacja: mar 30, 2021
account_circleAutorzy: Mike Eltsufin

1. Omówienie

Hibernate jest obecnie standardowym rozwiązaniem ORM do projektów w języku Java. Obsługuje wszystkie główne relacyjne bazy danych i umożliwia korzystanie z jeszcze bardziej zaawansowanych narzędzi ORM, takich jak Spring Data JPA. Dodatkowo istnieje wiele platform zgodnych z hibernate, takich jak Spring Boot, Microprofile i Quarkus.

Cloud Spanner Dialect for Hibernate ORM umożliwia korzystanie z hibernate w usłudze Cloud Spanner. Masz dostęp do zalet usługi Cloud Spanner – skalowalności i semantyki relacyjnej – dzięki idiomatycznemu zachowaniu architektury Hibernate. Dzięki temu możesz przenieść istniejące aplikacje do chmury lub napisać nowe, wykorzystując zwiększoną produktywność programistów, jaką zapewnia technologie oparte na Hibernate.

Czego się nauczysz

  • Jak napisać prostą aplikację Hibernate, która łączy się z Cloud Spanner
  • Jak utworzyć bazę danych Cloud Spanner
  • Jak używać Dialect Cloud Spanner do hibernacji ORM
  • Jak wdrożyć operacje CRUD (create-read-update-delete) za pomocą Hibernate

Czego potrzebujesz

  • Projekt Google Cloud Platform
  • przeglądarkę, np. Chrome lub Firefox;

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail lub G Suite, musisz je utworzyć.

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH00QRxd

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Zapamiętaj identyfikator projektu, unikalną nazwę we wszystkich projektach Google Cloud (powyższa nazwa jest już zajęta i nie będzie Ci odpowiadać). W dalszej części tego ćwiczenia w programie będzie ona określana jako PROJECT_ID.

  1. Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Google Cloud.

Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Postępuj zgodnie z instrukcjami podanymi w sekcji „Czyszczenie” W tym samouczku znajdziesz wskazówki, jak wyłączyć zasoby, aby uniknąć naliczania opłat. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.

Aktywowanie Cloud Shell

  1. W konsoli Cloud kliknij Aktywuj Cloud Shell R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI111ZczXP2tzqZrQUWAQ9QDRQ8.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Jeśli dopiero zaczynasz korzystać z Cloud Shell, wyświetli się ekran pośredni (w części strony widocznej po przewinięciu) z opisem tej funkcji. W takim przypadku kliknij Dalej (nie zobaczysz go więcej). Tak wygląda ten jednorazowy ekran:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Uzyskanie dostępu do Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Większość czynności z tego ćwiczenia z programowania można wykonać w przeglądarce lub na Chromebooku.

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się informacja, że użytkownik jest już uwierzytelniony i że projekt jest już ustawiony na identyfikator Twojego projektu.

  1. Uruchom to polecenie w Cloud Shell, aby potwierdzić, że jesteś uwierzytelniony:
gcloud auth list

Dane wyjściowe polecenia

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
gcloud config list project

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

Jeśli tak nie jest, możesz go ustawić za pomocą tego polecenia:

gcloud config set project <PROJECT_ID>

Dane wyjściowe polecenia

Updated property [core/project].

3. Utwórz bazę danych

Po uruchomieniu Cloud Shell możesz zacząć używać narzędzia gcloud do interakcji z projektem GCP.

Najpierw włącz interfejs Cloud Spanner API.

gcloud services enable spanner.googleapis.com

Teraz utwórzmy instancję Cloud Spanner o nazwie codelab-instance.

gcloud spanner instances create codelab-instance \
 --config=regional-us-central1 \
 --description="Codelab Instance" --nodes=1

Teraz musimy dodać do tej instancji bazę danych. Nazwiemy ją codelab-db.

gcloud spanner databases create codelab-db --instance=codelab-instance

4. Tworzenie pustej aplikacji

Wykorzystamy archetyp krótkiego wprowadzenia Maven do utworzenia prostej aplikacji konsoli Java.

mvn archetype:generate \
 -DgroupId=codelab \
 -DartifactId=spanner-hibernate-codelab \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DarchetypeVersion=1.4 \
 -DinteractiveMode=false

Przejdź do katalogu aplikacji.

cd spanner-hibernate-codelab

Skompiluj i uruchom aplikację za pomocą Maven.

mvn compile exec:java -Dexec.mainClass=codelab.App

W konsoli powinien pojawić się numer Hello World!.

5. Dodaj zależności

Aby poznać kod źródłowy, otwórz edytor Cloud Shell i przejrzyjmy katalog spanner-hibernate-codelab.

b5cb37d043d4d2b0.png

Do tej pory mamy tylko podstawową aplikację konsoli Java, która drukuje wersję "Hello World!". Chcemy jednak napisać aplikację w języku Java, która używa Hibernate do komunikacji z Cloud Spanner. Do tego będą potrzebne Dialect Cloud Spanner dla Hibernate, sterownik JDBC Cloud Spanner i rdzeń Hibernate. Dodajmy więc następujące zależności do bloku <dependencies> w pliku 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. Konfigurowanie Hibernate ORM

Następnie utworzymy pliki konfiguracji Hibernate hibernate.cfg.xml i hibernate.properties. Uruchom to polecenie, aby utworzyć puste pliki, a następnie zmodyfikuj je za pomocą edytora Cloud Shell.

mkdir src/main/resources \
 && touch src/main/resources/hibernate.cfg.xml \
 && touch src/main/resources/hibernate.properties

Wypełnijmy pole hibernate.cfg.xml, by więc opowiedzieć usłudze Hibernate o klasach encji z adnotacjami, które będziemy mapować na bazę danych. (klasy encji utworzymy później).

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 musi też wiedzieć, jak nawiązać połączenie z instancją Cloud Spanner i którego dialektu ma używać. Dlatego polecimy użycie SpannerDialect w przypadku składni SQL, sterownika JDBC Spannera i ciągu połączenia JDBC ze współrzędnymi bazy danych. Zostanie ono umieszczone w pliku 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

Pamiętaj, aby zastąpić {PROJECT_ID} identyfikatorem projektu, który można uzyskać, uruchamiając to polecenie:

gcloud config get-value project

Ponieważ nie mamy schematu bazy danych, dodaliśmy właściwość hibernate.hbm2ddl.auto=update, aby umożliwić usłudze Hibernate utworzenie 2 tabel w usłudze Cloud Spanner przy pierwszym uruchomieniu aplikacji.

Aby się upewnić, że dane uwierzytelniające są też skonfigurowane, możesz użyć pliku JSON konta usługi w zmiennej środowiskowej GOOGLE_APPLICATION_CREDENTIALS lub domyślnych danych uwierzytelniających aplikacji skonfigurowanych za pomocą polecenia gcloud auth application-default login. Ponieważ jednak działamy w Cloud Shell, domyślne dane logowania do projektu są już skonfigurowane.

7. Tworzenie klas encji z adnotacjami

Teraz możemy napisać trochę kodu.

Zdefiniujemy 2 stare obiekty Java (POJO), które zostaną zmapowane na tabele w Cloud Spanner – Singer i Album. Element Album będzie miał relację @ManyToOne z usługą Singer. Mogliśmy też zmapować albumy Singer na listy ich elementów Album z adnotacją @OneToMany, ale w tym przykładzie nie chcemy wczytywać wszystkich albumów za każdym razem, gdy musimy pobrać piosenkarza z bazy danych.

Dodaj klasy elementu Singer i Album.

Utwórz pliki zajęć.

touch src/main/java/codelab/Singer.java \
&& touch src/main/java/codelab/Album.java

Wklej zawartość plików.

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;
 
}
}

Zwróć uwagę, że w tym przykładzie używamy automatycznie wygenerowanego identyfikatora UUID klucza podstawowego. Jest to preferowany typ identyfikatora w Cloud Spanner, ponieważ pozwala uniknąć hotspotów, ponieważ system dzieli dane między serwerami według zakresów kluczy. Monotonicznie rosnący klucz całkowity również zadziałał, ale może być mniej wydajny.

8. Zapisz i odpytuj encje

Po skonfigurowaniu wszystkich obiektów i zdefiniowaniu obiektów encji możemy zacząć zapisywać dane w bazie danych i wysyłać do niej zapytania. Otworzymy tabelę Session, a następnie użyjemy jej, aby najpierw usunąć wszystkie wiersze tabeli w metodzie clearData(), zapisać niektóre encje w metodzie writeData() i uruchomić kilka zapytań z użyciem języka zapytań Hibernate (HQL) w metodzie readData().

Zamień zawartość pliku App.java na taką:

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;
   
}
 
}
}

Teraz skompilujmy i uruchommy kod. Dodamy opcję -Dexec.cleanupDaemonThreads=false, aby pomijać ostrzeżenia o czyszczeniu wątków demonów, które będzie próbował wykonać Maven.

mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false

W danych wyjściowych powinny pojawić się coś takiego:

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

Jeśli w tej chwili otworzysz konsolę Cloud Spanner i wyświetlisz w bazie danych tabele piosenkarz i album, zobaczysz coś takiego:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Czyszczenie danych

Usuńmy instancję Cloud Spanner, którą utworzyliśmy na początku, aby mieć pewność, że nie wykorzystuje niepotrzebnie zasobów.

gcloud spanner instances delete codelab-instance

10. Gratulacje

Gratulujemy! Udało Ci się utworzyć aplikację w Javie, która używa Hibernate do przechowywania danych w Cloud Spanner.

  • Udało Ci się utworzyć instancję Cloud Spanner i bazę danych
  • Aplikacja została skonfigurowana tak, aby używała programu Hibernate
  • Zostały przez Ciebie utworzone 2 elementy: Wykonawca i Album
  • Schemat bazy danych dla Twojej aplikacji został wygenerowany automatycznie
  • Udało Ci się zapisać encje w Cloud Spanner i wysłać do nich zapytanie

Znasz już najważniejsze kroki wymagane do napisania aplikacji Hibernate za pomocą Cloud Spanner.

Co dalej?