Cloud Spanner dengan ORM Hibernasi

1. Ringkasan

Hibernate telah menjadi solusi ORM standar de facto untuk project Java. Alat 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 mendapatkan manfaat Cloud Spanner - skalabilitas dan semantik relasional - dengan persistensi idiomatis Hibernate. Hal ini dapat membantu Anda memigrasikan aplikasi yang 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 Hibernate ORM
  • Cara menerapkan operasi buat-baca-perbarui-hapus (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-CAQC2t3CH0kuQRxdtP0ws43t5-O2d4d0WXDUfaw

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_mj0pR4sZ8eSwOwUlWADR7ARCqrMTQPQA.

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 menyediakan 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 Anda.

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. Buat aplikasi kosong

Kita akan menggunakan Maven Quickstart Archetype 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

Kompilasi dan jalankan aplikasi menggunakan Maven.

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

Anda akan melihat Hello World! dicetak di konsol.

5. Menambahkan dependensi

Mari jelajahi kode sumber dengan membuka Editor Cloud Shell, dan menjelajah di dalam direktori spanner-hibernate-codelab.

b5cb37d043d4d2b0.png

Sejauh ini, kita hanya memiliki aplikasi konsol Java dasar yang mencetak "Hello World!". Namun, kita benar-benar ingin menulis aplikasi Java yang menggunakan Hibernate untuk berkomunikasi dengan Cloud Spanner. Untuk itu, kita memerlukan Dialek Cloud Spanner untuk Hibernate, driver JDBC Cloud Spanner, dan inti Hibernate. Jadi, mari 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 Hibernate ORM

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, mari kita beri tahu Hibernate tentang class entity yang dianotasi yang akan kita petakan ke database dengan mengisi hibernate.cfg.xml. (Kita akan membuat class entitas 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 memberi tahu Spanner untuk menggunakan sintaksis SQL SpannerDialect, driver JDBC Spanner, dan string koneksi JDBC dengan koordinat database. Ini akan masuk 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 tidak memiliki skema database yang ada, kita menambahkan properti hibernate.hbm2ddl.auto=update agar Hibernate membuat dua tabel di Cloud Spanner saat kita menjalankan aplikasi untuk pertama kalinya.

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

7. Membuat class entity beranotasi

Sekarang kita siap menulis beberapa kode.

Kita akan menentukan dua objek Java lama (POJO) 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.

Buat file class.

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

Tempelkan isi 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 pilihan di Cloud Spanner, karena menghindari hotspot saat sistem membagi data di antara server berdasarkan rentang kunci. Kunci bilangan bulat yang meningkat secara monoton juga dapat digunakan, tetapi performanya mungkin lebih rendah.

8. Menyimpan dan membuat kueri entitas

Setelah semuanya dikonfigurasi dan objek entity ditentukan, kita dapat mulai menulis ke database dan membuat kueri. Kita akan membuka Session Hibernate, lalu menggunakannya untuk menghapus semua baris tabel terlebih dahulu dalam metode clearData(), 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 kode tersebut. Kami akan menambahkan opsi -Dexec.cleanupDaemonThreads=false untuk menekan peringatan tentang pembersihan thread daemon yang akan dicoba dilakukan oleh 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 Singer dan Album di database, Anda akan melihat sesuatu seperti ini:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Pembersihan

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

gcloud spanner instances delete codelab-instance

10. Selamat

Selamat, Anda telah berhasil membuat aplikasi Java yang menggunakan Hibernate untuk menyimpan data secara persisten di Cloud Spanner.

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

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

Apa selanjutnya?