Cloud Spanner с Hibernate ORM

1. Обзор

Hibernate стал де-факто стандартным решением ORM для проектов Java. Он поддерживает все основные реляционные базы данных и позволяет использовать еще более мощные инструменты ORM, такие как Spring Data JPA . Кроме того, существует множество Hibernate-совместимых фреймворков, таких как Spring Boot, Microprofile и Quarkus.

Диалект Cloud Spanner для Hibernate ORM позволяет использовать Hibernate с Cloud Spanner. Вы получаете преимущества Cloud Spanner — масштабируемость и реляционную семантику — с идиоматической устойчивостью Hibernate. Это может помочь вам перенести существующие приложения в облако или написать новые, используя повышенную производительность разработчиков, обеспечиваемую технологиями на основе Hibernate.

Что вы узнаете

  • Как написать простое приложение Hibernate, которое подключается к Cloud Spanner
  • Как создать базу данных Cloud Spanner
  • Как использовать диалект Cloud Spanner для Hibernate ORM
  • Как реализовать операции создания-чтения-обновления-удаления (CRUD) с помощью Hibernate

Что вам понадобится

  • Проект облачной платформы Google
  • Браузер, например Chrome или Firefox.

2. Настройка и требования

Самостоятельная настройка среды

  1. Войдите в Cloud Console и создайте новый проект или повторно используйте существующий. (Если у вас еще нет учетной записи Gmail или G Suite, вам необходимо ее создать .)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCoreWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH0kuQRxdtP0ws43t5-O2d4d0WXDUfaw

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQ CXA6w

Запомните идентификатор проекта — уникальное имя для всех проектов Google Cloud (имя, указанное выше, уже занято и не подойдет вам, извините!). Позже в этой лаборатории он будет называться PROJECT_ID .

  1. Далее вам необходимо включить биллинг в Cloud Console, чтобы использовать ресурсы Google Cloud.

Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Обязательно следуйте всем инструкциям в разделе «Очистка», в которых рассказывается, как отключить ресурсы, чтобы вам не приходилось нести расходы, выходящие за рамки этого руководства. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .

Активировать Cloud Shell

  1. В Cloud Console нажмите «Активировать Cloud Shell». R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZ_mj0pR4sZ8eSwOwUlWADR7ARCqrMTQPQA .

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPMAH-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Если вы никогда раньше не запускали Cloud Shell, вам будет представлен промежуточный экран (ниже сгиба) с описанием того, что это такое. В этом случае нажмите «Продолжить» (и вы больше никогда его не увидите). Вот как выглядит этот одноразовый экран:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Подготовка и подключение к Cloud Shell займет всего несколько минут.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Большую часть, если не всю, работу в этой лаборатории кода можно выполнить с помощью просто браузера или Chromebook.

После подключения к Cloud Shell вы увидите, что вы уже прошли аутентификацию и что для проекта уже установлен ваш идентификатор проекта.

  1. Выполните следующую команду в 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.

Сначала включите Cloud Spanner API.

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 для создания простого консольного приложения 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 .

b5cb37d043d4d2b0.png

Пока что у нас есть простое консольное приложение Java, которое печатает "Hello World!" . Однако мы действительно хотим написать приложение Java, которое использует Hibernate для взаимодействия с Cloud Spanner. Для этого нам понадобится диалект Cloud Spanner для Hibernate, драйвер JDBC Cloud Spanner и ядро ​​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 в базе данных, вы увидите что-то вроде этого:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Очистка

Давайте удалим экземпляр Cloud Spanner, который мы создали вначале, чтобы убедиться, что он не использует ресурсы без необходимости.

gcloud spanner instances delete codelab-instance

10. Поздравления

Поздравляем, вы успешно создали Java-приложение, которое использует Hibernate для сохранения данных в Cloud Spanner.

  • Вы создали экземпляр Cloud Spanner и базу данных.
  • Вы настроили приложение для использования Hibernate.
  • Вы создали две сущности: Исполнитель и Альбом.
  • Вы автоматически сгенерировали схему базы данных для своего приложения.
  • Вы успешно сохранили объекты в Cloud Spanner и запросили их.

Теперь вы знаете ключевые шаги, необходимые для написания приложения Hibernate с помощью Cloud Spanner.

Что дальше?