1. Przegląd
Hibernate stał się standardowym rozwiązaniem ORM w projektach 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. Istnieje też wiele platform zgodnych z Hibernate, takich jak Spring Boot, Microprofile i Quarkus.
Dialekt Cloud Spanner dla Hibernate ORM umożliwia korzystanie z Hibernate w Cloud Spanner. Możesz korzystać z zalet Cloud Spanner, takich jak skalowalność i semantyka relacyjna, a jednocześnie używać idiomatycznego utrwalania Hibernate. Może to pomóc w migracji dotychczasowych aplikacji do chmury lub w pisaniu nowych z wykorzystaniem większej produktywności programistów, jaką zapewniają technologie oparte na Hibernate.
Czego się nauczysz
- Jak napisać prostą aplikację Hibernate, która łączy się z Cloud Spanner
- Tworzenie bazy danych Cloud Spanner
- Jak używać dialektu Cloud Spanner w Hibernate ORM
- Jak wdrażać operacje tworzenia, odczytywania, aktualizowania i usuwania (CRUD) za pomocą Hibernate
Czego potrzebujesz
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. (Jeśli nie masz jeszcze konta Gmail lub G Suite, musisz je utworzyć).
Zapamiętaj identyfikator projektu, czyli unikalną nazwę we wszystkich projektach Google Cloud (podana powyżej nazwa jest już zajęta i nie będzie działać w Twoim przypadku). W dalszej części tego laboratorium będzie on nazywany PROJECT_ID.
- Następnie musisz włączyć rozliczenia w konsoli Cloud, aby korzystać z zasobów Google Cloud.
Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Wykonaj instrukcje z sekcji „Czyszczenie”, w której znajdziesz informacje o tym, jak wyłączyć zasoby, aby uniknąć naliczenia opłat po zakończeniu tego samouczka. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
Aktywowanie Cloud Shell
- W konsoli Cloud kliknij Aktywuj Cloud Shell
.
Jeśli uruchamiasz Cloud Shell po raz pierwszy, zobaczysz ekran pośredni (część strony widoczna po przewinięciu) z opisem tego środowiska. W takim przypadku kliknij Dalej, a ten ekran nie będzie się już wyświetlać. Ten wyświetlany jednorazowo ekran wygląda tak:
Uzyskanie dostępu do środowiska Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.
Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Większość zadań w tym module, a być może wszystkie, możesz wykonać w przeglądarce lub na Chromebooku.
Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu.
- Aby potwierdzić, że uwierzytelnianie zostało przeprowadzone, uruchom w Cloud Shell to polecenie:
gcloud auth list
Wynik 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
Wynik polecenia
[core] project = <PROJECT_ID>
Jeśli nie, możesz go ustawić za pomocą tego polecenia:
gcloud config set project <PROJECT_ID>
Wynik polecenia
Updated property [core/project].
3. Utwórz bazę danych
Po uruchomieniu Cloud Shell możesz zacząć korzystać z gcloud, aby wchodzić w interakcje 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 go codelab-db.
gcloud spanner databases create codelab-db --instance=codelab-instance
4. Tworzenie pustej aplikacji
Użyjemy archetypu Maven Quickstart, aby utworzyć prostą aplikację konsolową w języku 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 powinna się wyświetlić wartość Hello World!.
5. Dodawanie zależności
Otwórz edytor Cloud Shell i przejdź do katalogu spanner-hibernate-codelab, aby przejrzeć kod źródłowy.

Do tej pory mamy tylko podstawową aplikację konsolową w Javie, która wyświetla znak "Hello World!". Chcemy jednak napisać aplikację w Javie, która będzie używać Hibernate do komunikacji z Cloud Spanner. W tym celu potrzebujemy dialektu Cloud Spanner dla Hibernate, sterownika JDBC Cloud Spanner i rdzenia Hibernate. Dodajmy więc te 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 potem je edytować za pomocą edytora Cloud Shell.
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
Powiedzmy więc Hibernate o oznaczonych klasach encji, które będziemy mapować na bazę danych, wypełniając hibernate.cfg.xml. (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 połączyć się z instancją Cloud Spanner i którego dialektu użyć. Powiedzmy więc, że ma używać SpannerDialect do składni SQL, sterownika Spanner JDBC i ciągu znaków połączenia JDBC ze współrzędnymi bazy danych. Zostanie on zapisany 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żesz uzyskać, uruchamiając to polecenie:
gcloud config get-value project
Nie mamy istniejącego schematu bazy danych, więc dodaliśmy właściwość hibernate.hbm2ddl.auto=update, aby Hibernate utworzył 2 tabele w Cloud Spanner, gdy uruchomimy aplikację po raz pierwszy.
Zazwyczaj należy też skonfigurować dane logowania do uwierzytelniania, używając pliku JSON konta usługi w zmiennej środowiskowej GOOGLE_APPLICATION_CREDENTIALS lub domyślnych danych logowania aplikacji skonfigurowanych za pomocą polecenia gcloud auth application-default login. Ponieważ jednak działamy w Cloud Shell, domyślne dane logowania projektu są już skonfigurowane.
7. Tworzenie klas encji z adnotacjami
Możemy już napisać kod.
Zdefiniujemy 2 zwykłe obiekty Java (POJO), które będą mapowane na tabele w Cloud Spanner – Singer i Album. Element Album będzie powiązany z elementem Singer relacją @ManyToOne. Moglibyśmy też przypisać Singer do list Album z adnotacją @OneToMany, ale w tym przykładzie nie chcemy za każdym razem wczytywać wszystkich albumów, gdy musimy pobrać z bazy danych informacje o wykonawcy.
Dodaj klasy encji 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 jako klucza podstawowego używamy wygenerowanego automatycznie identyfikatora UUID. Jest to preferowany typ identyfikatora w Cloud Spanner, ponieważ pozwala uniknąć hotspotów, ponieważ system dzieli dane między serwery według zakresów kluczy. Sprawdzi się też klucz w postaci rosnącej monotonicznie liczby całkowitej, ale może być mniej wydajny.
8. Zapisywanie i wykonywanie zapytań o encje
Po skonfigurowaniu wszystkiego i zdefiniowaniu obiektów jednostek możemy zacząć zapisywać dane w bazie danych i wysyłać do niej zapytania. Otworzymy sesję Hibernate Session, a następnie użyjemy jej, aby najpierw usunąć wszystkie wiersze tabeli w metodzie clearData(), zapisać niektóre jednostki w metodzie writeData() i uruchomić zapytania za pomocą 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 wyłączyć ostrzeżenia dotyczące czyszczenia wątków demona, które Maven będzie próbował wykonać.
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
Dane wyjściowe powinny wyglądać mniej więcej tak:
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 tym momencie otworzysz konsolę Cloud Spanner i wyświetlisz dane z tabel Singer i Album w bazie danych, zobaczysz coś takiego:


9. Czyszczenie danych
Usuńmy utworzoną na początku instancję Cloud Spanner, aby niepotrzebnie nie zużywała zasobów.
gcloud spanner instances delete codelab-instance
10. Gratulacje
Gratulacje! Udało Ci się utworzyć aplikację w języku Java, która używa Hibernate do utrwalania danych w Cloud Spanner.
- Udało Ci się utworzyć instancję i bazę danych Cloud Spanner.
- Aplikacja została skonfigurowana do korzystania z Hibernate.
- Utworzono 2 jednostki: wykonawcę i album.
- Automatycznie wygenerowano schemat bazy danych aplikacji.
- Udało Ci się zapisać encje w Cloud Spanner i wysłać do nich zapytania.
Znasz już najważniejsze kroki, które należy wykonać, aby napisać aplikację Hibernate z Cloud Spanner.