آچار ابری با Hibernate ORM

۱. مرور کلی

Hibernate به راهکار استاندارد ORM برای پروژه‌های جاوا تبدیل شده است. این ابزار از تمام پایگاه‌های داده رابطه‌ای اصلی پشتیبانی می‌کند و ابزارهای ORM قدرتمندتری مانند Spring Data JPA را نیز فعال می‌کند. علاوه بر این، چارچوب‌های سازگار با Hibernate زیادی مانند Spring Boot، Microprofile و Quarkus وجود دارند.

زبان برنامه‌نویسی ابری اسپنر برای Hibernate ORM امکان استفاده از Hibernate را با Cloud Spanner فراهم می‌کند. شما از مزایای Cloud Spanner - مقیاس‌پذیری و معناشناسی رابطه‌ای - به همراه پایداری اصطلاحی Hibernate بهره‌مند می‌شوید. این می‌تواند به شما کمک کند تا برنامه‌های موجود را به ابر منتقل کنید یا برنامه‌های جدیدی بنویسید که از افزایش بهره‌وری توسعه‌دهندگان ناشی از فناوری‌های مبتنی بر Hibernate بهره می‌برند.

آنچه یاد خواهید گرفت

  • چگونه یک برنامه ساده Hibernate بنویسیم که به Cloud Spanner متصل شود؟
  • نحوه ایجاد پایگاه داده Cloud Spanner
  • نحوه استفاده از Cloud Spanner Dialect برای Hibernate ORM
  • نحوه پیاده‌سازی عملیات‌های ایجاد-خواندن-به‌روزرسانی-حذف (CRUD) با Hibernate

آنچه نیاز دارید

۲. تنظیمات و الزامات

تنظیم محیط خودتنظیم

  1. وارد Cloud Console شوید و یک پروژه جدید ایجاد کنید یا از یک پروژه موجود دوباره استفاده کنید. (اگر از قبل حساب Gmail یا G Suite ندارید، باید یکی ایجاد کنید .)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3CH0kuQRxdtP0ws43t5-O2d4d0WXDUfaw

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

شناسه پروژه را به خاطر بسپارید، یک نام منحصر به فرد در تمام پروژه‌های Google Cloud (نام بالا قبلاً گرفته شده و برای شما کار نخواهد کرد، متاسفیم!). بعداً در این آزمایشگاه کد به آن PROJECT_ID گفته خواهد شد.

  1. در مرحله بعد، برای استفاده از منابع گوگل کلود، باید پرداخت را در Cloud Console فعال کنید .

اجرای این آزمایشگاه کد، اگر اصلاً هزینه‌ای نداشته باشد، نباید هزینه زیادی داشته باشد. حتماً دستورالعمل‌های بخش «پاکسازی» را که به شما نحوه خاموش کردن منابع را آموزش می‌دهد، دنبال کنید تا پس از این آموزش، متحمل هزینه نشوید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان ۳۰۰ دلاری هستند.

فعال کردن پوسته ابری

  1. از کنسول ابری، روی فعال کردن پوسته ابری کلیک کنید R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZ_mj0pR4sZ8eSwOwUlWADR7ARCqrMTQPQA .

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

اگر قبلاً Cloud Shell را شروع نکرده‌اید، یک صفحه میانی (در زیر صفحه) به شما نمایش داده می‌شود که توضیح می‌دهد چیست. در این صورت، روی ادامه کلیک کنید (و دیگر هرگز آن را نخواهید دید). آن صفحه یکبار مصرف به این شکل است:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

آماده‌سازی و اتصال به Cloud Shell فقط چند لحظه طول می‌کشد.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

این ماشین مجازی با تمام ابزارهای توسعه‌ای که نیاز دارید، مجهز شده است. این ماشین یک دایرکتوری خانگی ۵ گیگابایتی پایدار ارائه می‌دهد و در فضای ابری گوگل اجرا می‌شود که عملکرد شبکه و احراز هویت را تا حد زیادی بهبود می‌بخشد. بخش عمده‌ای از کار شما در این آزمایشگاه کد، اگر نگوییم همه، را می‌توان به سادگی با یک مرورگر یا کروم‌بوک انجام داد.

پس از اتصال به 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].

۳. ایجاد یک پایگاه داده

پس از راه‌اندازی 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

۴. یک برنامه خالی ایجاد کنید

ما از Maven Quickstart Archetype برای ایجاد یک برنامه کنسول ساده جاوا استفاده خواهیم کرد.

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! را در کنسول چاپ شده ببینید.

۵. وابستگی‌ها را اضافه کنید

بیایید با باز کردن ویرایشگر Cloud Shell و مرور درون دایرکتوری spanner-hibernate-codelab ، کد منبع را بررسی کنیم.

b5cb37d043d4d2b0.png

تا اینجا، ما فقط یک برنامه کنسول جاوا ساده داریم که عبارت "Hello World!" را چاپ می‌کند. با این حال، واقعاً می‌خواهیم یک برنامه جاوا بنویسیم که از Hibernate برای ارتباط با Cloud Spanner استفاده کند. برای این کار، به Cloud Spanner Dialect برای 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>

۶. پیکربندی 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.cfg.xml ، به Hibernate در مورد کلاس‌های موجودیت حاشیه‌نویسی‌شده‌ای که به پایگاه داده نگاشت خواهیم کرد، اطلاع دهیم. (کلاس‌های موجودیت را بعداً ایجاد خواهیم کرد.)

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، درایور JDBC Spanner و رشته اتصال 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 اجرا می‌کنیم، اعتبارنامه‌های پیش‌فرض پروژه از قبل تنظیم شده‌اند.

۷. ایجاد کلاس‌های موجودیت حاشیه‌نویسی‌شده

حالا آماده‌ایم که کمی کد بنویسیم.

ما دو شیء ساده و قدیمی جاوا (POJO) تعریف خواهیم کرد که به جداول در Cloud Spanner نگاشت می‌شوند - Singer و Album . Album یک رابطه @ManyToOne با Singer خواهد داشت. ما همچنین می‌توانستیم Singer ها را با حاشیه‌نویسی @OneToMany به لیست Album نگاشت کنیم، اما برای این مثال، واقعاً نمی‌خواهیم هر بار که نیاز به واکشی یک Singer از پایگاه داده داریم، همه آلبوم‌ها را بارگذاری کنیم.

کلاس‌های موجودیت 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 ترجیح داده می‌شود، زیرا از نقاط حساس جلوگیری می‌کند، زیرا سیستم داده‌ها را بر اساس محدوده کلیدها بین سرورها تقسیم می‌کند. یک کلید عدد صحیح که به صورت یکنواخت افزایش می‌یابد نیز کار می‌کند، اما می‌تواند عملکرد کمتری داشته باشد.

۸. ذخیره و پرس‌وجو از موجودیت‌ها

با پیکربندی همه چیز و تعریف اشیاء موجودیت، می‌توانیم شروع به نوشتن در پایگاه داده و پرس‌وجو از آن کنیم. یک 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 را اضافه خواهیم کرد تا هشدارهای مربوط به پاکسازی رشته‌های daemon که 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

۹۵۲d۹۴۵۰dd۶۵۹e۷۵.png

۹. تمیز کردن

بیایید نمونه Cloud Spanner را که در ابتدا ایجاد کردیم حذف کنیم تا مطمئن شویم که منابع را بی‌جهت مصرف نمی‌کند.

gcloud spanner instances delete codelab-instance

۱۰. تبریک

تبریک می‌گویم، شما با موفقیت یک برنامه جاوا ساختید که از Hibernate برای ذخیره داده‌ها در Cloud Spanner استفاده می‌کند.

  • شما یک نمونه Cloud Spanner و یک پایگاه داده ایجاد کردید
  • شما برنامه را برای استفاده از Hibernate پیکربندی کرده‌اید
  • شما دو موجودیت ایجاد کردید: هنرمند و آلبوم
  • شما به طور خودکار طرحواره پایگاه داده را برای برنامه خود ایجاد کردید
  • شما با موفقیت موجودیت‌ها را در Cloud Spanner ذخیره کردید و از آنها کوئری گرفتید

اکنون مراحل کلیدی مورد نیاز برای نوشتن یک برنامه Hibernate با Cloud Spanner را می‌دانید.

بعدش چی؟