1. Обзор
Hibernate стал де-факто стандартом ORM для Java-проектов. Он поддерживает все основные реляционные базы данных и позволяет использовать ещё более мощные ORM-инструменты, такие как Spring Data JPA . Кроме того, существует множество совместимых с Hibernate фреймворков, таких как Spring Boot, Microprofile и Quarkus.
Диалект Cloud Spanner для ORM Hibernate позволяет использовать Hibernate с Cloud Spanner. Вы получаете преимущества Cloud Spanner — масштабируемость и реляционную семантику — с идиоматической персистентностью Hibernate. Это может помочь вам мигрировать существующие приложения в облако или писать новые, используя преимущества повышения производительности разработчиков, обеспечиваемые технологиями на основе Hibernate.
Что вы узнаете
- Как написать простое приложение Hibernate, которое подключается к Cloud Spanner
- Как создать базу данных Cloud Spanner
- Как использовать диалект Cloud Spanner для ORM Hibernate
- Как реализовать операции создания, чтения, обновления и удаления (CRUD) с помощью Hibernate.
Что вам понадобится
2. Настройка и требования
Настройка среды для самостоятельного обучения
- Войдите в Cloud Console и создайте новый проект или используйте существующий. (Если у вас еще нет учетной записи Gmail или G Suite, вам необходимо ее создать .)
Запомните идентификатор проекта (Project ID) — уникальное имя для всех проектов Google Cloud (указанное выше имя уже занято и вам не подойдёт, извините!). В дальнейшем в этом практическом занятии оно будет обозначаться как PROJECT_ID .
- Далее вам потребуется включить оплату в Cloud Console, чтобы использовать ресурсы Google Cloud.
Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Обязательно следуйте инструкциям в разделе «Очистка», где указано, как отключить ресурсы, чтобы избежать дополнительных расходов после завершения этого урока. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .
Активировать Cloud Shell
- В консоли Cloud нажмите «Активировать Cloud Shell» .
.
Если вы никогда раньше не запускали Cloud Shell, вам будет показан промежуточный экран (внизу), описывающий его назначение. В этом случае нажмите «Продолжить» (и вы больше никогда его не увидите). Вот как выглядит этот одноразовый экран:
Подготовка и подключение к Cloud Shell займут всего несколько минут.
Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, работа в этом практическом задании может быть выполнена с помощью обычного браузера или вашего Chromebook.
После подключения к Cloud Shell вы увидите, что ваша аутентификация пройдена и что проект уже настроен на ваш идентификатор проекта.
- Выполните следующую команду в Cloud Shell, чтобы подтвердить свою аутентификацию:
gcloud auth list
вывод команды
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
gcloud config list project
вывод команды
[core] project = <PROJECT_ID>
Если это не так, вы можете установить это с помощью следующей команды:
gcloud config set project <PROJECT_ID>
вывод команды
Updated property [core/project].
3. Создайте базу данных.
После запуска Cloud Shell вы сможете начать использовать gcloud для взаимодействия со своим проектом GCP.
Сначала активируйте API Cloud Spanner.
gcloud services enable spanner.googleapis.com
Теперь давайте создадим экземпляр Cloud Spanner под названием codelab-instance .
gcloud spanner instances create codelab-instance \ --config=regional-us-central1 \ --description="Codelab Instance" --nodes=1
Теперь нам нужно добавить базу данных к этому экземпляру. Назовём её codelab-db .
gcloud spanner databases create codelab-db --instance=codelab-instance
4. Создайте пустое приложение.
Мы воспользуемся шаблоном Maven Quickstart Archetype для создания простого консольного Java-приложения.
mvn archetype:generate \ -DgroupId=codelab \ -DartifactId=spanner-hibernate-codelab \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DinteractiveMode=false
Перейдите в каталог приложения.
cd spanner-hibernate-codelab
Скомпилируйте и запустите приложение с помощью Maven.
mvn compile exec:java -Dexec.mainClass=codelab.App
В консоли должно отобразиться сообщение Hello World! .
5. Добавьте зависимости
Давайте изучим исходный код, открыв редактор Cloud Shell и просмотрев содержимое каталога spanner-hibernate-codelab .

Пока у нас есть только базовое консольное Java-приложение, которое выводит "Hello World!" . Однако нам действительно нужно написать Java-приложение, которое будет использовать Hibernate для взаимодействия с Cloud Spanner. Для этого нам понадобится диалект Cloud Spanner для Hibernate, драйвер Cloud Spanner JDBC и ядро Hibernate. Итак, давайте добавим следующие зависимости в блок <dependencies> внутри файла 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. Настройка Hibernate ORM
Далее мы создадим конфигурационные файлы Hibernate: hibernate.cfg.xml и hibernate.properties . Выполните следующую команду, чтобы создать пустые файлы, а затем отредактируйте их с помощью редактора Cloud Shell.
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
Итак, давайте сообщим Hibernate об аннотированных классах сущностей, которые мы будем сопоставлять с базой данных, заполнив файл hibernate.cfg.xml . (Классы сущностей мы создадим позже.)
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 также должен знать, как подключиться к экземпляру Cloud Spanner и какой диалект использовать. Поэтому мы укажем ему использовать SpannerDialect для синтаксиса SQL, драйвер Spanner JDBC и строку подключения JDBC с координатами базы данных. Это нужно добавить в файл 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
Не забудьте заменить {PROJECT_ID} на идентификатор вашего проекта, который можно получить, выполнив следующую команду:
gcloud config get-value project
Поскольку у нас нет существующей схемы базы данных, мы добавили свойство hibernate.hbm2ddl.auto=update , чтобы Hibernate мог создать две таблицы в Cloud Spanner при первом запуске приложения.
Обычно также необходимо убедиться, что учетные данные для аутентификации настроены, используя либо JSON-файл учетной записи службы в переменной среды GOOGLE_APPLICATION_CREDENTIALS , либо учетные данные приложения по умолчанию, настроенные с помощью команды gcloud auth application-default login . Однако, поскольку мы работаем в Cloud Shell, учетные данные проекта по умолчанию уже настроены.
7. Создайте аннотированные классы сущностей.
Теперь мы готовы написать код.
Мы определим два простых Java-объекта (POJO), которые будут сопоставляться с таблицами в Cloud Spanner — Singer и Album . У Album будет отношение @ManyToOne с Singer . Мы могли бы также сопоставить объекты Singer со списками их Album с помощью аннотации @OneToMany , но для этого примера нам не нужно загружать все альбомы каждый раз, когда нам нужно получить информацию о певце из базы данных.
Добавьте классы сущностей Singer и « Album .
Создайте файлы классов.
touch src/main/java/codelab/Singer.java \ && touch src/main/java/codelab/Album.java
Вставьте содержимое файлов.
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;
}
}
Обратите внимание, что в этом примере мы используем автоматически сгенерированный UUID в качестве первичного ключа. Это предпочтительный тип идентификатора в Cloud Spanner, поскольку он позволяет избежать «горячих точек», так как система распределяет данные между серверами по диапазонам ключей. Монотонно возрастающий целочисленный ключ также подошел бы, но может быть менее производительным.
8. Сохранение и запрос сущностей.
После того, как все настроено и объекты сущностей определены, мы можем начать запись в базу данных и выполнение запросов. Мы откроем Session Hibernate, а затем используем ее для того, чтобы сначала удалить все строки таблицы в методе clearData() , сохранить некоторые сущности в методе writeData() и выполнить несколько запросов с использованием языка запросов Hibernate (HQL) в методе readData() .
Замените содержимое файла App.java следующим:
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;
}
}
}
Теперь давайте скомпилируем и запустим код. Добавим опцию -Dexec.cleanupDaemonThreads=false , чтобы подавить предупреждения об очистке потоков демона, которую Maven попытается выполнить.
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
В результате вы должны увидеть что-то подобное:
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
На этом этапе, если вы перейдете в консоль Cloud Spanner и просмотрите данные таблиц Singer и Album в базе данных, вы увидите примерно следующее:


9. Уборка
Давайте удалим созданный нами в начале экземпляр Cloud Spanner, чтобы убедиться, что он не потребляет ресурсы без необходимости.
gcloud spanner instances delete codelab-instance
10. Поздравляем!
Поздравляем, вы успешно создали Java-приложение, использующее Hibernate для сохранения данных в Cloud Spanner.
- Вы создали экземпляр Cloud Spanner и базу данных.
- Вы настроили приложение для использования Hibernate.
- Вы создали две сущности: Исполнитель и Альбом.
- Вы автоматически сгенерировали схему базы данных для вашего приложения.
- Вы успешно сохранили объекты в Cloud Spanner и выполнили к ним запрос.
Теперь вы знаете основные шаги, необходимые для написания приложения Hibernate с использованием Cloud Spanner.