أداة 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
  • كيفية تنفيذ عمليات create-read-update-delete (CRUD) باستخدام Hibernate

المتطلبات

  • مشروع Google Cloud Platform
  • متصفح، مثل Chrome أو Firefox

2. الإعداد والمتطلبات

إعداد بيئة ذاتية

  1. سجِّل الدخول إلى Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. (إذا لم يكن لديك حساب على Gmail أو G Suite، عليك إنشاء حساب.)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2t3DawtPKoQR

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

يُرجى تذكُّر رقم تعريف المشروع، وهو اسم فريد في جميع مشاريع Google Cloud (سبق أن تم استخدام الاسم أعلاه ولن يكون مناسبًا لك). ستتم الإشارة إليها لاحقًا في هذا الدرس التطبيقي حول الترميز باسم PROJECT_ID.

  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام موارد Google Cloud.

إنّ تنفيذ هذا الدرس التطبيقي حول الترميز لن يكون مكلفًا أو مكلفًا على الإطلاق. احرص على اتّباع أي تعليمات في قسم "الحذف سريعًا". الذي يقدم لك نصائح حول كيفية إيقاف تشغيل الموارد حتى لا تتكبّد أي فواتير خارج نطاق هذا البرنامج التعليمي. يكون مستخدمو Google Cloud الجدد مؤهَّلون للانضمام إلى برنامج الفترة التجريبية المجانية التي تبلغ قيمتها 300 دولار أمريكي.

تفعيل Cloud Shell

  1. من Cloud Console، انقر على تفعيل Cloud Shell R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1I1OdZczXP2tzqZ4S_mj0pR.

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 للتفاعل مع مشروع Google Cloud Platform.

أولاً، عليك تفعيل واجهة برمجة تطبيقات Cloud Spanner.

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 Quickstart Aretype لإنشاء تطبيق بسيط لوحدة تحكم 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، وبرنامج تشغيل Cloud Spanner JDBC، ونواة 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 Session، ثم نستخدمه أولاً لحذف جميع صفوف الجدول بطريقة 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 و"الألبوم" في قاعدة البيانات، سيظهر لك بالشكل التالي:

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.

الخطوات التالية