Cloud Spanner dengan ORM Hibernasi

1. Ringkasan

Hibernate telah menjadi solusi ORM standar yang sudah teruji untuk project Java. Hal ini mendukung semua database relasional utama dan memungkinkan alat ORM yang lebih canggih seperti Spring Data JPA. Selain itu, ada banyak framework yang kompatibel dengan Hibernate seperti Spring Boot, Microprofile, dan Quarkus.

Cloud Spanner Dialect for Hibernate ORM memungkinkan penggunaan Hibernate dengan Cloud Spanner. Anda akan mendapatkan manfaat Cloud Spanner - skalabilitas dan semantik relasional - dengan persistensi idiomatis Hibernate. Hal ini dapat membantu Anda memigrasikan aplikasi yang sudah ada ke cloud atau menulis aplikasi baru dengan memanfaatkan peningkatan produktivitas developer yang diberikan oleh teknologi berbasis Hibernate.

Yang akan Anda pelajari

  • Cara menulis aplikasi Hibernate sederhana yang terhubung ke Cloud Spanner
  • Cara membuat database Cloud Spanner
  • Cara menggunakan Dialek Cloud Spanner untuk ORM Hibernate
  • Cara menerapkan operasi create-read-update-delete (CRUD) dengan Hibernate

Yang Anda butuhkan

  • Project Google Cloud Platform
  • Browser, seperti Chrome atau Firefox

2. Penyiapan dan Persyaratan

Penyiapan lingkungan mandiri

  1. Login ke Cloud Console dan buat project baru atau gunakan kembali project yang sudah ada. (Jika belum memiliki akun Gmail atau G Suite, Anda harus membuatnya.)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH0wOD4dtP

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Ingat project ID, nama unik di semua project Google Cloud (maaf, nama di atas telah digunakan dan tidak akan berfungsi untuk Anda!) Project ID tersebut selanjutnya akan dirujuk di codelab ini sebagai PROJECT_ID.

  1. Selanjutnya, Anda harus mengaktifkan penagihan di Cloud Console untuk menggunakan resource Google Cloud.

Menjalankan operasi dalam codelab ini seharusnya tidak memerlukan banyak biaya, bahkan mungkin tidak sama sekali. Pastikan untuk mengikuti petunjuk yang ada di bagian "Membersihkan" yang memberi tahu Anda cara menonaktifkan resource sehingga tidak menimbulkan penagihan di luar tutorial ini. Pengguna baru Google Cloud memenuhi syarat untuk mengikuti program Uji Coba Gratis senilai $300 USD.

Mengaktifkan Cloud Shell

  1. Dari Cloud Console, klik Aktifkan Cloud Shell R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZ_mj0pRAR.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Jika belum pernah memulai Cloud Shell, Anda akan melihat layar perantara (di paruh bawah) yang menjelaskan apa itu Cloud Shell. Jika demikian, klik Lanjutkan (dan Anda tidak akan pernah melihatnya lagi). Berikut tampilan layar sekali-tampil tersebut:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Perlu waktu beberapa saat untuk penyediaan dan terhubung ke Cloud Shell.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Mesin virtual ini berisi semua alat pengembangan yang Anda perlukan. Layanan ini menawarkan direktori beranda tetap sebesar 5 GB dan beroperasi di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Sebagian besar pekerjaan Anda dalam codelab ini dapat dilakukan hanya dengan browser atau Chromebook.

Setelah terhubung ke Cloud Shell, Anda akan melihat bahwa Anda sudah diautentikasi dan project sudah ditetapkan ke project ID Anda.

  1. Jalankan perintah berikut di Cloud Shell untuk mengonfirmasi bahwa Anda telah diautentikasi:
gcloud auth list

Output perintah

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

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

Output perintah

[core]
project = <PROJECT_ID>

Jika tidak, Anda dapat menyetelnya dengan perintah ini:

gcloud config set project <PROJECT_ID>

Output perintah

Updated property [core/project].

3. Buat database

Setelah Cloud Shell diluncurkan, Anda dapat mulai menggunakan gcloud untuk berinteraksi dengan project GCP.

Pertama, aktifkan Cloud Spanner API.

gcloud services enable spanner.googleapis.com

Sekarang, mari kita buat instance Cloud Spanner bernama codelab-instance.

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

Sekarang, kita perlu menambahkan database ke instance ini. Kita akan menyebutnya codelab-db.

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

4. Membuat aplikasi kosong

Kita akan menggunakan Arketipe Panduan Memulai Maven untuk membuat aplikasi konsol Java sederhana.

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

Ubah ke direktori aplikasi.

cd spanner-hibernate-codelab

Mengompilasi dan menjalankan aplikasi menggunakan Maven.

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

Anda akan melihat Hello World! yang dicetak di konsol.

5. Menambahkan dependensi

Mari kita pelajari kode sumber dengan membuka Cloud Shell Editor, dan menjelajahi di dalam direktori spanner-hibernate-codelab.

b5cb37d043d4d2b0.png

Sejauh ini, kita baru saja memiliki aplikasi konsol Java dasar yang mencetak "Hello World!". Namun, kita ingin menulis aplikasi Java yang menggunakan Hibernate untuk berkomunikasi dengan Cloud Spanner. Untuk itu, kita memerlukan Cloud Spanner Dialect untuk Hibernate, driver JDBC Cloud Spanner, dan Hibernate core. Jadi, mari kita tambahkan dependensi berikut ke blok <dependencies> di dalam file 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. Mengonfigurasi ORM Hibernasi

Selanjutnya, kita akan membuat file konfigurasi Hibernate hibernate.cfg.xml dan hibernate.properties. Jalankan perintah berikut untuk membuat file kosong, lalu edit menggunakan Cloud Shell Editor.

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

Jadi, beri tahu Hibernate tentang class entity yang dianotasi dan akan kita petakan ke database dengan mengisi hibernate.cfg.xml. (Kita akan membuat class entity nanti.)

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 juga perlu mengetahui cara terhubung ke instance Cloud Spanner dan dialek mana yang akan digunakan. Jadi, kita akan memintanya menggunakan SpannerDialect untuk sintaksis SQL, driver JDBC Spanner, dan string koneksi JDBC dengan koordinat database. File ini akan dimasukkan ke file 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

Jangan lupa untuk mengganti {PROJECT_ID} dengan project ID Anda, yang bisa Anda dapatkan dengan menjalankan perintah berikut:

gcloud config get-value project

Karena belum memiliki skema database, kami menambahkan properti hibernate.hbm2ddl.auto=update agar Hibernate dapat membuat dua tabel di Cloud Spanner saat menjalankan aplikasi untuk pertama kalinya.

Biasanya, Anda juga akan memastikan bahwa kredensial autentikasi telah disiapkan, menggunakan file JSON akun layanan dalam variabel lingkungan GOOGLE_APPLICATION_CREDENTIALS atau kredensial default aplikasi yang dikonfigurasi menggunakan perintah gcloud auth application-default login. Namun, karena kita berjalan di Cloud Shell, kredensial project default sudah disiapkan.

7. Membuat class entity yang telah dianotasi

Sekarang kita siap untuk menulis beberapa kode.

Kita akan menentukan dua objek Java lama (POJO) biasa yang akan dipetakan ke tabel di Cloud Spanner—Singer dan Album. Album akan memiliki hubungan @ManyToOne dengan Singer. Kita juga dapat memetakan Singer ke daftar Album dengan anotasi @OneToMany, tetapi untuk contoh ini, kita tidak ingin memuat semua album setiap kali kita perlu mengambil penyanyi dari database.

Tambahkan class entity Singer dan Album.

Membuat file kelas.

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

Tempelkan konten file.

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

Perhatikan bahwa untuk contoh ini, kita menggunakan UUID yang dibuat secara otomatis untuk kunci utama. Ini adalah jenis ID yang lebih disukai di Cloud Spanner, karena menghindari hotspot saat sistem membagi data di antara server berdasarkan rentang kunci. Kunci bilangan bulat yang meningkat secara monoton juga akan berfungsi, tetapi performanya mungkin kurang baik.

8. Menyimpan dan membuat kueri entity

Setelah semua hal yang telah dikonfigurasi dan objek entity telah ditentukan, kita dapat mulai menulis ke database dan membuat kuerinya. Kita akan membuka Session Hibernasi, lalu menggunakannya untuk menghapus semua baris tabel dalam metode clearData() terlebih dahulu, menyimpan beberapa entity dalam metode writeData(), dan menjalankan beberapa kueri menggunakan bahasa kueri Hibernate (HQL) dalam metode readData().

Ganti konten App.java dengan konten berikut:

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

Sekarang, mari kita kompilasi dan jalankan kodenya. Kita akan menambahkan opsi -Dexec.cleanupDaemonThreads=false untuk menyembunyikan peringatan tentang pembersihan thread daemon yang akan coba dilakukan Maven.

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

Di output, Anda akan melihat sesuatu seperti ini:

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

Pada tahap ini, jika Anda membuka konsol Cloud Spanner dan melihat data untuk tabel Penyanyi dan Album dalam database, Anda akan melihat data seperti ini:

f18276ea54cc266f.png

952d9450dd659e75.pngS

9. Pembersihan

Mari kita hapus instance Cloud Spanner yang kita buat di awal untuk memastikan bahwa instance tersebut tidak menghabiskan resource secara tidak perlu.

gcloud spanner instances delete codelab-instance

10. Selamat

Selamat, Anda telah berhasil membangun aplikasi Java yang menggunakan Hibernate untuk mempertahankan data di Cloud Spanner.

  • Anda telah membuat instance dan database Cloud Spanner
  • Anda telah mengonfigurasi aplikasi untuk menggunakan Hibernate
  • Anda membuat dua entitas: Artis dan Album
  • Anda secara otomatis membuat skema database untuk aplikasi
  • Anda berhasil menyimpan entity ke Cloud Spanner dan membuat kuerinya

Anda sekarang mengetahui langkah-langkah utama yang diperlukan untuk menulis aplikasi Hibernate dengan Cloud Spanner.

Apa selanjutnya?