Cloud Spanner kết hợp với ORM chế độ ngủ đông

1. Tổng quan

Trên thực tế, Hibernate đã trở thành giải pháp ORM chuẩn cho các dự án Java. API này hỗ trợ mọi cơ sở dữ liệu quan hệ chính và hỗ trợ các công cụ ORM mạnh mẽ hơn nữa như Spring Data JPA. Ngoài ra, có nhiều khung tương thích với chế độ Hibernate, chẳng hạn như Spring Boot, Microprofile và Quarkus.

Phương ngữ Cloud Spanner cho Hibernate ORM giúp bạn có thể sử dụng chế độ Hibernate cùng với Cloud Spanner. Bạn được hưởng những lợi ích của Cloud Spanner, cụ thể là khả năng mở rộng và ngữ nghĩa quan hệ, cùng với tính bền vững đặc trưng của chế độ Hibernate. Nhờ đó, bạn có thể di chuyển các ứng dụng hiện có lên đám mây hoặc viết các ứng dụng mới nhờ khả năng tăng năng suất của nhà phát triển nhờ các công nghệ dựa trên chế độ Hibernate.

Kiến thức bạn sẽ học được

  • Cách viết một ứng dụng Hibernate đơn giản kết nối với Cloud Spanner
  • Cách tạo cơ sở dữ liệu Cloud Spanner
  • Cách sử dụng phương diện Cloud Spanner cho ORM chế độ ngủ đông
  • Cách triển khai các thao tác tạo-đọc-cập nhật-xoá (CRUD) bằng chế độ Hibernate

Bạn cần có

  • Một dự án trên Google Cloud Platform
  • Một trình duyệt, chẳng hạn như Chrome hoặc Firefox

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tiến độ riêng

  1. Đăng nhập vào Cloud Console rồi tạo dự án mới hoặc sử dụng lại dự án hiện có. (Nếu chưa có tài khoản Gmail hoặc G Suite, bạn phải tạo một tài khoản.)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCHoặcEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH0kuQRxdt4D5ws

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Xin lưu ý rằng mã dự án là một tên riêng biệt trong tất cả dự án Google Cloud (tên ở trên đã được sử dụng nên sẽ không phù hợp với bạn!). Lớp này sẽ được đề cập sau trong lớp học lập trình này là PROJECT_ID.

  1. Tiếp theo, bạn sẽ cần bật tính năng thanh toán trong Cloud Console để sử dụng tài nguyên của Google Cloud.

Việc chạy qua lớp học lập trình này sẽ không tốn nhiều chi phí. Hãy nhớ làm theo mọi hướng dẫn trong phần "Dọn dẹp" sẽ tư vấn cho bạn cách tắt tài nguyên để bạn không phải chịu thanh toán ngoài hướng dẫn này. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí 300 USD.

Kích hoạt Cloud Shell

  1. Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZSwUWAWA.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Nếu trước đây chưa từng khởi động Cloud Shell, bạn sẽ được trình bày một màn hình trung gian (dưới màn hình đầu tiên) mô tả về ứng dụng này. Nếu trường hợp đó xảy ra, hãy nhấp vào Tiếp tục (và bạn sẽ không thấy thông báo đó nữa). Màn hình một lần đó sẽ có dạng như sau:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Quá trình cấp phép và kết nối với Cloud Shell chỉ mất vài phút.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Máy ảo này chứa tất cả các công cụ phát triển mà bạn cần. Dịch vụ này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trong Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Trong lớp học lập trình này, đa số mọi người đều có thể thực hiện chỉ bằng một trình duyệt hoặc Chromebook.

Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án đã được đặt thành mã dự án.

  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
gcloud auth list

Kết quả lệnh

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

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

Kết quả lệnh

[core]
project = <PROJECT_ID>

Nếu chưa, bạn có thể đặt chế độ này bằng lệnh sau:

gcloud config set project <PROJECT_ID>

Kết quả lệnh

Updated property [core/project].

3. Tạo cơ sở dữ liệu

Sau khi Cloud Shell chạy, bạn có thể bắt đầu sử dụng gcloud để tương tác với dự án GCP.

Trước tiên, hãy bật Cloud Spanner API.

gcloud services enable spanner.googleapis.com

Bây giờ, hãy tạo một thực thể Cloud Spanner có tên là codelab-instance.

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

Bây giờ, chúng ta cần thêm một cơ sở dữ liệu vào thực thể này. Chúng tôi sẽ đặt tên mục này là codelab-db.

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

4. Tạo một ứng dụng trống

Chúng ta sẽ sử dụng Maven Quickstart Archetype để tạo một ứng dụng đơn giản trên bảng điều khiển Java.

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

Thay đổi sang thư mục ứng dụng.

cd spanner-hibernate-codelab

Biên dịch và chạy ứng dụng bằng Maven.

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

Bạn sẽ thấy Hello World! được in trong bảng điều khiển.

5. Thêm phần phụ thuộc

Hãy tìm hiểu mã nguồn bằng cách mở Cloud Shell Editor rồi duyệt bên trong thư mục spanner-hibernate-codelab.

b5cb37d043d4d2b0.png

Cho đến nay, chúng ta chỉ có một ứng dụng bảng điều khiển Java cơ bản có thể in "Hello World!". Tuy nhiên, chúng ta thực sự muốn viết một ứng dụng Java sử dụng chế độ Hibernate để giao tiếp với Cloud Spanner. Do đó, chúng ta sẽ cần Phương ngữ Cloud Spanner cho chế độ Hibernate, trình điều khiển JDBC của Cloud Spanner và lõi Hibernate. Vì vậy, hãy thêm các phần phụ thuộc sau vào khối <dependencies> bên trong tệp 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. Định cấu hình ORM chế độ Hibernate

Tiếp theo, chúng ta sẽ tạo các tệp cấu hình Hibernate hibernate.cfg.xmlhibernate.properties. Chạy lệnh sau để tạo các tệp trống rồi chỉnh sửa các tệp đó bằng Trình chỉnh sửa Cloud Shell.

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

Vì vậy, hãy cho Hibernate biết về các lớp thực thể được chú giải mà chúng ta sẽ ánh xạ đến cơ sở dữ liệu bằng cách điền vào hibernate.cfg.xml. (Chúng ta sẽ tạo các lớp thực thể sau.)

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>

Chế độ ngủ đông cũng cần biết cách kết nối với thực thể Cloud Spanner và phương ngữ cần sử dụng. Vì vậy, chúng ta sẽ yêu cầu công cụ này sử dụng SpannerDialect cho cú pháp SQL, trình điều khiển JDBC của Spanner và chuỗi kết nối JDBC có toạ độ cơ sở dữ liệu. Thao tác này sẽ chuyển sang tệp 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

Hãy nhớ thay thế {PROJECT_ID} bằng mã dự án của bạn. Bạn có thể nhận được mã này bằng cách chạy lệnh sau:

gcloud config get-value project

Vì không có giản đồ cơ sở dữ liệu, nên chúng ta đã thêm thuộc tính hibernate.hbm2ddl.auto=update để cho phép chế độ Hibernate tạo 2 bảng trong Cloud Spanner trong lần đầu chạy ứng dụng.

Thông thường, bạn cũng phải đảm bảo đã thiết lập thông tin xác thực, bằng cách sử dụng tệp JSON của tài khoản dịch vụ trong biến môi trường GOOGLE_APPLICATION_CREDENTIALS hoặc thông tin xác thực mặc định của ứng dụng được định cấu hình bằng lệnh gcloud auth application-default login. Tuy nhiên, vì chúng ta đang chạy trong Cloud Shell nên thông tin đăng nhập dự án mặc định đã được thiết lập.

7. Tạo các lớp thực thể có chú giải

Bây giờ, chúng ta đã sẵn sàng để viết một số mã.

Chúng ta sẽ xác định 2 đối tượng Java cũ (POJO) thuần tuý sẽ ánh xạ tới các bảng trong Cloud Spanner là SingerAlbum. Album sẽ có mối quan hệ @ManyToOne với Singer. Chúng ta cũng có thể ánh xạ Singer đến danh sách các Album của chúng bằng chú thích @OneToMany, nhưng trong ví dụ này, chúng ta không thực sự muốn tải tất cả các album mỗi khi cần tìm nạp ca sĩ từ cơ sở dữ liệu.

Thêm các lớp thực thể SingerAlbum.

Tạo các tệp của lớp.

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

Dán nội dung của tệp.

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

Xin lưu ý rằng trong ví dụ này, chúng ta đang sử dụng mã nhận dạng duy nhất (UUID) được tạo tự động cho khoá chính. Đây là loại mã nhận dạng được ưu tiên dùng trong Cloud Spanner vì loại mã này giúp tránh được các điểm phát sóng khi hệ thống chia dữ liệu giữa các máy chủ theo phạm vi khoá. Khoá số nguyên tăng đơn điệu cũng sẽ hoạt động, nhưng có thể kém hiệu quả hơn.

8. Lưu và truy vấn các thực thể

Sau khi xác định mọi đối tượng đã định cấu hình và thực thể, chúng ta có thể bắt đầu ghi vào cơ sở dữ liệu và truy vấn cơ sở dữ liệu đó. Chúng ta sẽ mở một Session Hibernate, sau đó sử dụng nó để xoá tất cả hàng trong bảng trước tiên trong phương thức clearData(), lưu một số thực thể trong phương thức writeData() và chạy một số truy vấn bằng ngôn ngữ truy vấn Hibernate (HQL) trong phương thức readData().

Thay thế nội dung của App.java bằng:

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

Bây giờ, hãy biên dịch và chạy mã. Chúng ta sẽ thêm tuỳ chọn -Dexec.cleanupDaemonThreads=false để loại bỏ các cảnh báo về việc dọn dẹp các luồng trình nền mà Maven sẽ cố gắng thực hiện.

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

Trong dữ liệu đầu ra, bạn sẽ thấy như sau:

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

Tại thời điểm này, nếu chuyển đến bảng điều khiển Cloud Spanner và xem dữ liệu cho các bảng Ca sĩ và Đĩa nhạc trong cơ sở dữ liệu, bạn sẽ thấy như sau:

f18276ea54cc266f.png

952d9450dd659e75.pngS

9. Dọn dẹp

Hãy xoá thực thể Cloud Spanner mà chúng ta đã tạo ban đầu để đảm bảo thực thể đó không sử dụng tài nguyên một cách không cần thiết.

gcloud spanner instances delete codelab-instance

10. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo thành công một ứng dụng Java sử dụng chế độ Hibernate để lưu trữ dữ liệu trong Cloud Spanner.

  • Bạn đã tạo một thực thể Cloud Spanner và một cơ sở dữ liệu
  • Bạn đã định cấu hình ứng dụng để dùng chế độ ngủ đông
  • Bạn đã tạo hai thực thể: Nghệ sĩ và Đĩa nhạc
  • Bạn đã tự động tạo giản đồ cơ sở dữ liệu cho ứng dụng của mình
  • Bạn đã lưu thành công các thực thể vào Cloud Spanner và truy vấn chúng

Giờ đây, bạn đã biết các bước chính cần thiết để viết ứng dụng Hibernate bằng Cloud Spanner.

Tiếp theo là gì?