أداة Cloud Spanner مع Hibernate ORM

1. نظرة عامة

أصبح Hibernate هو الحلّ المعياري الفعلي لعمليات ربط البيانات بالكائنات (ORM) في مشاريع Java. يتوافق مع جميع قواعد البيانات الارتباطية الرئيسية ويتيح استخدام أدوات ORM أكثر فعالية، مثل Spring Data JPA. بالإضافة إلى ذلك، هناك العديد من الأُطر المتوافقة مع Hibernate، مثل Spring Boot وMicroprofile وQuarkus.

يتيح Cloud Spanner Dialect for Hibernate ORM استخدام Hibernate مع Cloud Spanner. يمكنك الاستفادة من مزايا Cloud Spanner، مثل قابلية التوسّع والدلالات الارتباطية، مع إمكانية استمرار Hibernate في العمل بشكل طبيعي. يمكن أن يساعدك ذلك في نقل التطبيقات الحالية إلى السحابة الإلكترونية أو كتابة تطبيقات جديدة تستفيد من زيادة إنتاجية المطوّرين التي توفّرها التقنيات المستندة إلى Hibernate.

ما ستتعلمه

  • كيفية كتابة تطبيق Hibernate بسيط يتصل بخدمة Cloud Spanner
  • كيفية إنشاء قاعدة بيانات Cloud Spanner
  • كيفية استخدام لغة Cloud Spanner لـ Hibernate ORM
  • كيفية تنفيذ عمليات الإنشاء والقراءة والتعديل والحذف (CRUD) باستخدام Hibernate

المتطلبات

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

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

إعداد البيئة بالسرعة التي تناسبك

  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.

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

تفعيل Cloud Shell

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

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

4. إنشاء تطبيق فارغ

سنستخدم Maven Quickstart Archetype لإنشاء تطبيق بسيط لوحدة تحكّم 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 Dialect for Hibernate وبرنامج تشغيل Cloud Spanner JDBC وHibernate core. لذا، لنضِف الاعتماديات التالية إلى حظر <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 مرتبطًا بـ Singer من خلال علاقة @ManyToOne. كان بإمكاننا أيضًا ربط 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، ثم نستخدمه أولاً لحذف جميع صفوف الجدول في الطريقة 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 واطّلعت على بيانات جدولَي "المغني" و"الألبوم" في قاعدة البيانات، سيظهر لك ما يلي:

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.

ما هي الخطوات التالية؟