Cloud Spanner: إنشاء لوحة صدارة للألعاب باستخدام Java

1. نظرة عامة

Google Cloud Spanner هي خدمة قاعدة بيانات ارتباطية وقابلة للتطوير أفقيًا وموزعة عالميًا، وتوفِّر معاملات ACID ودلالات SQL بدون التخلّي عن الأداء والتوفّر العالي.

سوف تتعلم في هذا التمرين المعملي كيفية إعداد مثيل Cloud Spanner. ستتعرف على خطوات إنشاء قاعدة بيانات ومخطط يمكن استخدامهما لقائمة الصدارة في الألعاب. ستبدأ بإنشاء جدول "اللاعبين" لتخزين معلومات اللاعب وجدول النتائج لتخزين نتائج اللاعبين.

بعد ذلك، ستقوم بتعبئة الجداول بنماذج بيانات. ثم ستختتم التمرين المعملي بتشغيل بعض أهم عشرة استعلامات وأخيرًا حذف المثيل لتحرير الموارد.

المعلومات التي ستطّلع عليها

  • كيفية إعداد مثيل Cloud Spanner
  • كيفية إنشاء قاعدة بيانات وجداول
  • كيفية استخدام عمود الطابع الزمني للتنفيذ
  • كيفية تحميل البيانات إلى جدول قاعدة بيانات Cloud Spanner مع الطوابع الزمنية
  • كيفية طلب بحث في قاعدة بيانات Cloud Spanner
  • كيفية حذف مثيل Cloud Spanner

المتطلبات

كيف ستستخدم هذا البرنامج التعليمي؟

القراءة فقط اقرأها وأكمِل التمارين

ما هو تقييمك لتجربتك مع Google Cloud Platform؟

حديث متوسط بارع

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

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

إذا لم يكن لديك حساب Google (Gmail أو Google Apps)، يجب عليك إنشاء حساب. سجِّل الدخول إلى وحدة تحكُّم Google Cloud Platform ( console.cloud.google.com) وأنشئ مشروعًا جديدًا.

إذا كان لديك مشروع بالفعل، فانقر فوق القائمة المنسدلة لاختيار المشروع في أعلى يسار وحدة التحكم:

6c9406d9b014760.png

وانقر على "مشروع جديد" في مربع الحوار الناتج لإنشاء مشروع جديد:

f708315ae07353d0.png

إذا لم يكن لديك مشروع، من المفترض أن يظهر لك مربع حوار مثل هذا لإنشاء مشروعك الأول:

870a3cbd6541ee86.png

يتيح لك مربع الحوار اللاحق لإنشاء المشروع إدخال تفاصيل مشروعك الجديد:

6a92c57d3250a4b3.png

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

بعد ذلك، عليك تفعيل الفوترة في Developers Console لاستخدام موارد Google Cloud وتفعيل Cloud Spanner API إذا لم يسبق لك إجراء ذلك.

15d0ef27a8fbab27.png

لن يكلفك تنفيذ هذا الدرس التطبيقي أكثر من بضعة دولارات، ولكن قد تزيد التكاليف إذا قررت استخدام المزيد من الموارد أو إذا تركتها قيد التشغيل (يُرجى الاطّلاع على قسم "التنظيف" في نهاية هذا المستند). يمكن الاطّلاع على أسعار خدمة Google Cloud Spanner هنا.

إنّ مستخدمي Google Cloud Platform الجدد مؤهّلون للاستفادة من فترة تجريبية مجانية بقيمة 300 دولار أمريكي، ما يجعل هذا الدرس التطبيقي حول الترميز بدون أي تكلفة.

إعداد Google Cloud Shell

يمكن إدارة Google Cloud وSpanner عن بُعد من الكمبيوتر المحمول، ولكن في هذا الدرس التطبيقي حول الترميز، سنستخدم Google Cloud Shell، وهي بيئة سطر أوامر يتم تشغيلها في السحابة الإلكترونية.

هذا الجهاز الافتراضي المستند إلى نظام دبيان محمل بكل أدوات التطوير التي ستحتاج إليها. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 5 غيغابايت ويتم تشغيله في Google Cloud، ما يحسّن بشكل كبير من أداء الشبكة والمصادقة. وهذا يعني أنّ كل ما ستحتاجه في هذا الدرس التطبيقي حول الترميز هو متصفّح (نعم، يعمل على جهاز Chromebook).

  1. لتفعيل Cloud Shell من Cloud Console، ما عليك سوى النقر على تفعيل Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (من المفترَض أن تستغرق عملية الإعداد والاتصال بالبيئة بضع دقائق فقط).

JjEuRXGg0AYYIY6QZ8d-66gx_Mtc-_jDE9ijmbXLJSAXFvJt-qUpNtsBsYjNpv2W6BQSrDc1D-ARINNQ-1EkwUhz-iUK-FUCZhJ-NtjvIEx9pIkE-246DomWuCfiGHK78DgoeWkHRw

لقطة شاشة يوم 14-06-2017 في الساعة 10.13.43 مساءً.png

بعد الربط بتطبيق Cloud Shell، من المفترض أن يظهر لك أنّه قد تمت مصادقتك وأنّ المشروع سبق أن تم ضبطه على PROJECT_ID.

gcloud auth list

مخرجات الأمر

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

مخرجات الأمر

[core]
project = <PROJECT_ID>

إذا لم يتم ضبط المشروع لسبب ما، ما عليك سوى إصدار الأمر التالي:

gcloud config set project <PROJECT_ID>

هل تبحث عن PROJECT_ID؟ تحقَّق من المعرّف الذي استخدمته في خطوات الإعداد أو ابحث عنه في لوحة بيانات Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

تضبط Cloud Shell أيضًا بعض متغيرات البيئة تلقائيًا، وهو ما قد يكون مفيدًا عند تشغيل الأوامر المستقبلية.

echo $GOOGLE_CLOUD_PROJECT

مخرجات الأمر

<PROJECT_ID>
  1. أخيرًا، قم بتعيين تهيئة المنطقة الافتراضية والمشروع.
gcloud config set compute/zone us-central1-f

يمكنك اختيار مجموعة متنوعة من المناطق المختلفة. لمزيد من المعلومات، راجع المناطق المناطق:

ملخّص

في هذه الخطوة، يتم إعداد البيئة.

التالي

بعد ذلك، سيتم إعداد مثيل Cloud Spanner.

3- إعداد مثيل Cloud Spanner

في هذه الخطوة، نُعِدّ مثيل Cloud Spanner لهذا الدرس التطبيقي حول الترميز. ابحث عن إدخال Spanner 1a6580bd3d3e6783.pngفي قائمة همبرغر أعلى اليمين 3129589f7bc9e5ce.png أو ابحث عن Spanner عن طريق الضغط على "/" واكتب "Spanner"

36e52f8df8e13b99.png

بعد ذلك، انقر على 95269e75bc8c3e4d.png واملأ النموذج عن طريق إدخال اسم المثيل cloudspanner-leaderboard للمثيل، واختيار الإعدادات (اختيار مثيل إقليمي)، وضبط عدد العُقد. سنحتاج إلى عقدة واحدة فقط في هذا الدرس التطبيقي حول الترميز. بالنسبة إلى مثيلات الإنتاج وللتأهل لاتفاقية مستوى الخدمة في Cloud Spanner، ستحتاج إلى تشغيل 3 عُقد أو أكثر في مثيل Cloud Spanner.

أخيرًا وليس آخرًا، انقر على "إنشاء" وستكون لديك مثيل Cloud Spanner تحت تصرفك خلال ثوانٍ.

dceb68e9ed3801e8.png

في الخطوة التالية، سنستخدم مكتبة برامج Java لإنشاء قاعدة بيانات ومخطط في المثيل الجديد.

4. إنشاء قاعدة بيانات ومخطط

في هذه الخطوة، سننشئ نموذجًا لقاعدة البيانات والمخطط.

لنستخدم مكتبة برامج Java لإنشاء جدولين؛ جدول "اللاعبين" للحصول على معلومات عن اللاعب وجدول النتائج لتخزين نتائج اللاعبين ولتنفيذ ذلك، سنستعرض خطوات إنشاء تطبيق وحدة تحكُّم Java في Cloud Shell.

استنسِخ أولاً نموذج الرمز البرمجي لهذا الدرس التطبيقي من GitHub من خلال كتابة الأمر التالي في Cloud Shell:

git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

ثم غير الدليل إلى "التطبيقات" الدليل الذي ستنشئ فيه التطبيق.

cd java-docs-samples/spanner/leaderboard

تتوفّر كلّ الرموز المطلوبة لهذا الدرس التطبيقي حول الترميز في دليل java-docs-samples/spanner/leaderboard/complete الحالي كتطبيق C# قابل للتشغيل واسمه Leaderboard، لاستخدامه كمرجع أثناء تقدّمك في الدرس التطبيقي حول الترميز. سننشئ دليلاً جديدًا وننشئ نسخة من تطبيق "ليدربورد" على مراحل.

إنشاء دليل جديد باسم "درس تطبيقي حول الترميز" للتطبيق وتغيير الدليل إليه باستخدام الأمر التالي:

mkdir codelab && cd $_

إنشاء تطبيق Java أساسي جديد باسم "Leaderboard" باستخدام أمر Maven (mvn) التالي:

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.google.codelabs -DartifactId=leaderboard -DarchetypeVersion=1.4

ينشئ هذا الأمر تطبيق وحدة تحكم بسيطًا يتكون من ملفين أساسيين، ملف إعداد تطبيق Maven pom.xml وملف تطبيق Java App.java.

بعد ذلك، غيِّر الدليل إلى دليل "ليدربورد" الذي تم إنشاؤه للتو وأدرِج محتواه:

cd leaderboard && ls

من المفترض أن يظهر لك ملف pom.xml ودليل src مُدرجَين:

pom.xml  src

لنحدث الآن تطبيق وحدة التحكّم هذا عن طريق تعديل App.java لاستخدام مكتبة برامج Java Spanner لإنشاء لوحة صدارة تتألف من جدولَين. اللاعبون والنتائج ويمكنك إجراء ذلك مباشرةً في "محرِّر Cloud Shell" باتّباع الخطوات التالية:

افتح محرِّر Cloud Shell، من خلال النقر على الرمز الموضح أدناه:

73cf70e05f653ca.png

افتح pom.xml ضمن مجلد "ليدربورد". افتح ملف pom.xml المتوفّر في مجلد "java-docs-samples\ spanner\leaderboard\codelab\leaderboard".

أضِف قسم إدارة التبعية الجديد التالي مباشرةً ضمن المواقع </property> الحالية. العنصر:

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-bom</artifactId>
        <version>0.83.0-alpha</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

إضافة تبعية واحدة جديدة أيضًا في عناصر <dependencies> الحالية الذي ستضيف مكتبة برامج Cloud Spanner Java إلى التطبيق.

    <dependency>
      <!-- Version auto-managed by BOM -->
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner</artifactId>
    </dependency>  

بعد ذلك، استبدِل <build> الحالي لملف pom.xml. باستخدام ما يلي <build> :

 <build>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.5.5</version>
        <configuration>
          <finalName>leaderboard</finalName>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.google.codelabs.App</mainClass>
            </manifest>
          </archive>
          <appendAssemblyId>false</appendAssemblyId>
          <attach>false</attach>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
        <configuration>
            <useSystemClassLoader>false</useSystemClassLoader>
        </configuration>
      </plugin>  
    </plugins>
  </build>

احفظ التغييرات التي أجريتها على ملف pom.xml من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة أو بالضغط على "Ctrl" و"S" مفاتيح لوحة المفاتيح معًا.

بعد ذلك، افتح ملف App.java في محرِّر Cloud Shell المتوفّر في مجلد src/main/java/com/google/codelabs/. استبدِل الرمز الحالي للملف بالرمز المطلوب لإنشاء قاعدة بيانات leaderboard والجدولَين Players وScores من خلال لصق رمز Java التالي في ملف App.java:

package com.google.codelabs;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;

/**
 * Example code for using the Cloud Spanner API with the Google Cloud Java client library
 * to create a simple leaderboard.
 * 
 * This example demonstrates:
 *
 * <p>
 *
 * <ul>
 *   <li>Creating a Cloud Spanner database.
 * </ul>
 */
public class App {

  static void create(DatabaseAdminClient dbAdminClient, DatabaseId db) {
    OperationFuture<Database, CreateDatabaseMetadata> op =
        dbAdminClient.createDatabase(
            db.getInstanceId().getInstance(),
            db.getDatabase(),
            Arrays.asList(
                "CREATE TABLE Players(\n"
                    + "  PlayerId INT64 NOT NULL,\n"
                    + "  PlayerName STRING(2048) NOT NULL\n"
                    + ") PRIMARY KEY(PlayerId)",
                "CREATE TABLE Scores(\n"
                    + "  PlayerId INT64 NOT NULL,\n"
                    + "  Score INT64 NOT NULL,\n"
                    + "  Timestamp TIMESTAMP NOT NULL\n"
                    + "  OPTIONS(allow_commit_timestamp=true)\n"
                    + ") PRIMARY KEY(PlayerId, Timestamp),\n"
                    + "INTERLEAVE IN PARENT Players ON DELETE NO ACTION"));
    try {
      // Initiate the request which returns an OperationFuture.
      Database dbOperation = op.get();
      System.out.println("Created database [" + dbOperation.getId() + "]");
    } catch (ExecutionException e) {
      // If the operation failed during execution, expose the cause.
      throw (SpannerException) e.getCause();
    } catch (InterruptedException e) {
      // Throw when a thread is waiting, sleeping, or otherwise occupied,
      // and the thread is interrupted, either before or during the activity.
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }

  static void printUsageAndExit() {
    System.out.println("Leaderboard 1.0.0");
    System.out.println("Usage:");
    System.out.println("  java -jar leaderboard.jar "
        + "<command> <instance_id> <database_id> [command_option]");
    System.out.println("");
    System.out.println("Examples:");
    System.out.println("  java -jar leaderboard.jar create my-instance example-db");
    System.out.println("      - Create a sample Cloud Spanner database along with "
        + "sample tables in your project.\n");
    System.exit(1);
  }

  public static void main(String[] args) throws Exception {
    if (!(args.length == 3 || args.length == 4)) {
      printUsageAndExit();
    }
    SpannerOptions options = SpannerOptions.newBuilder().build();
    Spanner spanner = options.getService();
    try {
      String command = args[0];
      DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]);
      DatabaseClient dbClient = spanner.getDatabaseClient(db);
      DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient();
      switch (command) {
        case "create":
          create(dbAdminClient, db);
          break;
        default:
          printUsageAndExit();
      }
    } finally {
      spanner.close();
    }
    System.out.println("Closed client");
  }
}

احفظ التغييرات التي أجريتها على ملف App.java من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.

يمكنك استخدام ملف App.java في دليل java-docs-samples/spanner/leaderboard/step4/src للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف App.java بعد إضافة الرمز لتفعيل الأمر create.

لإنشاء تطبيقك، عليك تشغيل حزمة mvn من الدليل الذي يتضمّن pom.xml:

mvn package

بعد إنشاء ملف Java jar بنجاح، شغِّل التطبيق الناتج في cloud shell من خلال إدخال الأمر التالي:

java -jar target/leaderboard.jar

من المفترض أن تظهر لك نتيجة على النحو التالي:

Leaderboard 1.0.0
Usage:
  java -jar leaderboard.jar <command> <instance_id> <database_id> [command_option]

Examples:
  java -jar leaderboard.jar create my-instance example-db
      - Create a sample Cloud Spanner database along with sample tables in your project.

من خلال هذا الردّ، يمكننا ملاحظة أنّ هذا هو تطبيق Leaderboard الذي يتضمّن حاليًا أمرًا واحدًا محتملاً: create. يتضح لنا أن الوسيطات المتوقعة للأمر create هي معرّف المثيل ومعرّف قاعدة البيانات.

قم الآن بتشغيل الأمر التالي.

java -jar target/leaderboard.jar create cloudspanner-leaderboard leaderboard

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Created database [projects/your-project/instances/cloudspanner-leaderboard/databases/leaderboard] 

في قسم Cloud Spanner من Cloud Console، من المفترض أن تظهر لك قاعدة البيانات والجداول الجديدة في القائمة الجانبية اليمنى.

ba9008bb84cb90b0.png

في الخطوة التالية سنحدث تطبيقنا لتحميل بعض البيانات إلى قاعدة بياناتك الجديدة.

5- تحميل البيانات

لدينا الآن قاعدة بيانات تسمى leaderboard تحتوي على جدولين؛ Players وScores لنستخدم الآن مكتبة برامج Java لملء جدول Players باللاعبين وجدول Scores بنتائج عشوائية لكل لاعب.

إذا لم يكن التطبيق مفتوحًا، افتح محرِّر Cloud Shell بالنقر على الرمز المميّز أدناه:

ef49fcbaaed19024.png

بعد ذلك، عدِّل ملف App.java في "محرِّر Cloud Shell" لإضافة الأمر insert الذي يمكن استخدامه لإدراج 100 لاعب في جدول Players أو يمكن استخدامه لإدراج 4 نتائج عشوائية في جدول Scores لكل لاعب في جدول Players.

حدِّث أولاً القسم imports أعلى ملف التطبيق، مع استبدال العناصر المتوفرة حاليًا بحيث يظهر على النحو التالي بعد الانتهاء:

package com.google.codelabs;

import static com.google.cloud.spanner.TransactionRunner.TransactionCallable;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.TransactionContext;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;

بعد ذلك، أضِف طرق الإدراج التالية وINSERTPlayers و insertScores أسفل طريقة create() الحالية وأعلى طريقة printUsageAndExit() الحالية:

  static void insert(DatabaseClient dbClient, String insertType) {
    try {
      insertType = insertType.toLowerCase();
    } catch (Exception e) {
      // Invalid input received, set insertType to empty string.
      insertType = "";
    }
    if (insertType.equals("players")) {
      // Insert players.
      insertPlayers(dbClient);
    } else if (insertType.equals("scores")) {
      // Insert scores.
      insertScores(dbClient);
    } else {
      // Invalid input.
      System.out.println("Invalid value for 'type of insert'. "
          + "Specify a valid value: 'players' or 'scores'.");
      System.exit(1);
    }
  }

  static void insertPlayers(DatabaseClient dbClient) {
    dbClient
        .readWriteTransaction()
        .run(
            new TransactionCallable<Void>() {
              @Override
              public Void run(TransactionContext transaction) throws Exception {
                // Get the number of players.
                String sql = "SELECT Count(PlayerId) as PlayerCount FROM Players";
                ResultSet resultSet = transaction.executeQuery(Statement.of(sql));
                long numberOfPlayers = 0;
                if (resultSet.next()) {
                  numberOfPlayers = resultSet.getLong("PlayerCount");
                }
                // Insert 100 player records into the Players table.
                List<Statement> stmts = new ArrayList<Statement>();
                long randomId;
                for (int x = 1; x <= 100; x++) {
                  numberOfPlayers++;
                  randomId = (long) Math.floor(Math.random() * 9_000_000_000L) + 1_000_000_000L;
                  Statement statement =
                      Statement
                        .newBuilder(
                            "INSERT INTO Players (PlayerId, PlayerName) "
                            + "VALUES (@PlayerId, @PlayerName) ")
                        .bind("PlayerId")
                        .to(randomId)
                        .bind("PlayerName")
                        .to("Player " + numberOfPlayers)
                        .build();
                  stmts.add(statement);
                }
                transaction.batchUpdate(stmts);
                return null;
              }
            });
    System.out.println("Done inserting player records...");
  }

  static void insertScores(DatabaseClient dbClient) {
    boolean playerRecordsFound = false;
    ResultSet resultSet =
        dbClient
            .singleUse()
            .executeQuery(Statement.of("SELECT * FROM Players"));
    while (resultSet.next()) {
      playerRecordsFound = true;
      final long playerId = resultSet.getLong("PlayerId");
      dbClient
          .readWriteTransaction()
          .run(
              new TransactionCallable<Void>() {
                @Override
                public Void run(TransactionContext transaction) throws Exception {
                  // Initialize objects for random Score and random Timestamp.
                  LocalDate endDate = LocalDate.now();
                  long end = endDate.toEpochDay();
                  int startYear = endDate.getYear() - 2;
                  int startMonth = endDate.getMonthValue();
                  int startDay = endDate.getDayOfMonth();
                  LocalDate startDate = LocalDate.of(startYear, startMonth, startDay);
                  long start = startDate.toEpochDay();
                  Random r = new Random();
                  List<Statement> stmts = new ArrayList<Statement>();
                  // Insert 4 score records into the Scores table 
                  // for each player in the Players table.
                  for (int x = 1; x <= 4; x++) {
                    // Generate random score between 1,000,000 and 1,000
                    long randomScore = r.nextInt(1000000 - 1000) + 1000;
                    // Get random day within the past two years.
                    long randomDay = ThreadLocalRandom.current().nextLong(start, end);
                    LocalDate randomDayDate = LocalDate.ofEpochDay(randomDay);
                    LocalTime randomTime = LocalTime.of(
                        r.nextInt(23), r.nextInt(59), r.nextInt(59), r.nextInt(9999));
                    LocalDateTime randomDate = LocalDateTime.of(randomDayDate, randomTime);
                    Instant randomInstant = randomDate.toInstant(ZoneOffset.UTC);
                    Statement statement =
                        Statement
                        .newBuilder(
                          "INSERT INTO Scores (PlayerId, Score, Timestamp) "
                          + "VALUES (@PlayerId, @Score, @Timestamp) ")
                        .bind("PlayerId")
                        .to(playerId)
                        .bind("Score")
                        .to(randomScore)
                        .bind("Timestamp")
                        .to(randomInstant.toString())
                        .build();
                    stmts.add(statement);
                  }
                  transaction.batchUpdate(stmts);
                  return null;
                }
              });

    }
    if (!playerRecordsFound) {
      System.out.println("Parameter 'scores' is invalid since "
          + "no player records currently exist. First insert players "
          + "then insert scores.");
      System.exit(1);
    } else {
      System.out.println("Done inserting score records...");
    }
  }

بعد ذلك، لتفعيل الأمر insert، أضِف الرمز التالي إلى "الرئيسي" في تطبيقك. في عبارة switch (command) :

        case "insert":
          String insertType;
          try {
            insertType = args[3];
          } catch (ArrayIndexOutOfBoundsException exception) {
            insertType = "";
          }
          insert(dbClient, insertType);
          break;

بعد الانتهاء، من المفترض أن تظهر عبارة switch (command) على النحو التالي:

      switch (command) {
        case "create":
          create(dbAdminClient, db);
          break;
        case "insert":
          String insertType;
          try {
            insertType = args[3];
          } catch (ArrayIndexOutOfBoundsException exception) {
            insertType = "";
          }
          insert(dbClient, insertType);
          break;
        default:
          printUsageAndExit();
      }

الخطوة الأخيرة لإكمال عملية إضافة "insert" إلى تطبيقك هي إضافة نص مساعدة للحرف "insert" إلى الطريقة printUsageAndExit(). أضِف سطور الرمز التالية إلى الطريقة printUsageAndExit()لتضمين نص المساعدة لأمر الإدراج:

    System.out.println("  java -jar leaderboard.jar insert my-instance example-db players");
    System.out.println("      - Insert 100 sample Player records into the database.\n");
    System.out.println("  java -jar leaderboard.jar insert my-instance example-db scores");
    System.out.println("      - Insert sample score data into Scores sample Cloud Spanner "
        + "database table.\n");

احفظ التغييرات التي أجريتها على ملف App.java من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.

يمكنك استخدام ملف App.java في دليل java-docs-samples/spanner/leaderboard/step5/src للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف App.java بعد إضافة الرمز لتفعيل الأمر insert.

والآن، لنعيد إنشاء التطبيق ونشغِّله للتأكّد من تضمين أمر insert الجديد في قائمة الأوامر المحتملة في التطبيق.

لإنشاء تطبيقك، شغِّل mvn package من الدليل الذي يتوفّر فيه ملف pom.xml:

mvn package

بعد إنشاء ملف Java jar بنجاح، شغّل الأمر التالي:

java -jar target/leaderboard.jar

من المفترض أن يظهر لك الأمر insert الآن ضمن الإخراج التلقائي للتطبيق:

Leaderboard 1.0.0
Usage:
  java -jar leaderboard.jar <command> <instance_id> <database_id> [command_option]

Examples:
  java -jar leaderboard.jar create my-instance example-db
      - Create a sample Cloud Spanner database along with sample tables in your project.

  java -jar leaderboard.jar insert my-instance example-db players
      - Insert 100 sample Player records into the database.

  java -jar leaderboard.jar insert my-instance example-db scores
      - Insert sample score data into Scores sample Cloud Spanner database table.

يمكنك أن ترى من الرد أنه بالإضافة إلى معرّف المثيل ومعرّف قاعدة البيانات، هناك وسيطة أخرى يمكن أن تحتوي على قيمة "players" أو "النتائج".

لنقم الآن بتشغيل الأمر insert باستخدام نفس قيم الوسيطة التي استخدمناها عند استدعينا الأمر create، مع إضافة "players" باعتباره "نوع الإدخال" الإضافي الوسيطة.

java -jar target/leaderboard.jar insert cloudspanner-leaderboard leaderboard players

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Done inserting player records...

لنستخدم الآن مكتبة برامج Java لتعبئة جدول Scores بأربع نتائج عشوائية بالإضافة إلى طوابع زمنية لكل لاعب في جدول Players.

تم تعريف عمود Timestamp في جدول Scores على أنّه "طابع زمني للالتزام". عبر عبارة SQL التالية التي تم تنفيذها عند تشغيل الأمر create سابقًا:

CREATE TABLE Scores(
  PlayerId INT64 NOT NULL,
  Score INT64 NOT NULL,
  Timestamp TIMESTAMP NOT NULL OPTIONS(allow_commit_timestamp=true)
) PRIMARY KEY(PlayerId, Timestamp),
    INTERLEAVE IN PARENT Players ON DELETE NO ACTION

لاحِظ السمة OPTIONS(allow_commit_timestamp=true). يجعل هذا الإجراء Timestamp "طابعًا زمنيًا للالتزام". وتمكين ملؤه تلقائيًا بالطابع الزمني الدقيق للمعاملة لعمليات INSERT وUPDATE في صف جدول معين.

يمكنك أيضًا إدراج قيم الطوابع الزمنية الخاصة بك في "الطابع الزمني للالتزام". طالما تُدرِج طابعًا زمنيًا مع قيمة من الماضي، وهو ما سنفعله في هذا الدرس التطبيقي حول الترميز.

لنقم الآن بتشغيل الأمر insert باستخدام نفس قيم الوسيطة التي استخدمناها عندما استدعينا الأمر create بإضافة "scores" باعتباره "نوع الإدخال" الإضافي الوسيطة.

java -jar target/leaderboard.jar insert cloudspanner-leaderboard leaderboard scores

بعد بضع ثوانٍ، من المفترض أن يظهر لك رد كالتالي:

Done inserting score records...

جارٍ تشغيل insert باستخدام "نوع الإدراج" المحددون على أن scores يستدعي الطريقة insertScores التي تستخدم مقتطفات الرموز التالية لإدراج طابع زمني تم إنشاؤه عشوائيًا مع تاريخ ووقت يحدث في الماضي:

          LocalDate endDate = LocalDate.now();
          long end = endDate.toEpochDay();
          int startYear = endDate.getYear() - 2;
          int startMonth = endDate.getMonthValue();
          int startDay = endDate.getDayOfMonth();
          LocalDate startDate = LocalDate.of(startYear, startMonth, startDay);
          long start = startDate.toEpochDay();
...
            long randomDay = ThreadLocalRandom.current().nextLong(start, end);
            LocalDate randomDayDate = LocalDate.ofEpochDay(randomDay);
            LocalTime randomTime = LocalTime.of(
                        r.nextInt(23), r.nextInt(59), r.nextInt(59), r.nextInt(9999));
            LocalDateTime randomDate = LocalDateTime.of(randomDayDate, randomTime);
            Instant randomInstant = randomDate.toInstant(ZoneOffset.UTC);

...
               .bind("Timestamp")
               .to(randomInstant.toString())

لتعبئة عمود Timestamp تلقائيًا بالطابع الزمني عند ضبط الزر "إدراج" بالضبط تحدث المعاملات، يمكنك بدلاً من ذلك إدراج Value.COMMIT_TIMESTAMP ثابت لـ Java كما في مقتطف الرمز التالي:

               .bind("Timestamp")
               .to(Value.COMMIT_TIMESTAMP)

الآن وبعد أن أكملنا تحميل البيانات، لنتحقق من القيم التي كتبناها للتو إلى جداولنا الجديدة. اختَر أولاً قاعدة بيانات leaderboard ثم اختَر الجدول Players. انقر على علامة التبويب Data. من المفترض أن يظهر لك أنّ لديك بيانات في العمودَين PlayerId وPlayerName في الجدول.

7bc2c96293c31c49.png

بعد ذلك، لنتحقق من أن جدول "النتائج" يحتوي أيضًا على بيانات من خلال النقر على جدول Scores واختيار علامة التبويب Data. من المفترض أن يظهر لك أنّ لديك بيانات في الأعمدة PlayerId وTimestamp وScore في الجدول.

d8a4ee4f13244c19.png

أحسنت! لنعدّل تطبيقنا لتشغيل بعض طلبات البحث التي يمكننا استخدامها لإنشاء قائمة الصدارة للألعاب.

6- تنفيذ طلبات بحث في قائمة الصدارة

الآن بعد أن أعددت قاعدة البيانات وتحميل المعلومات إلى جداولنا، لنقم بإنشاء قائمة صدارة باستخدام هذه البيانات. ولتنفيذ ذلك، يجب الإجابة عن الأسئلة الأربعة التالية:

  1. مَن هم اللاعبين ضمن "أفضل عشرة" من جميع الأوقات؟
  2. مَن هم اللاعبين ضمن "أفضل عشرة" من العام؟
  3. مَن هم اللاعبين ضمن "أفضل عشرة" من الشهر؟
  4. مَن هم اللاعبين ضمن "أفضل عشرة" من الأسبوع؟

لنحدث تطبيقنا لتشغيل استعلامات SQL التي ستجيب على هذه الأسئلة.

سنضيف الأمر query الذي يوفر طريقة لتشغيل الاستعلامات للإجابة عن الأسئلة التي ستنتج المعلومات المطلوبة لقائمة الصدارة.

عدِّل ملف App.java في "محرِّر Cloud Shell" لتحديث التطبيق من أجل إضافة أمر query. يتكون الأمر query من طريقتين query، تستخدم الطريقة الأولى وسيطة DatabaseClient والأخرى تستخدم وسيطة timespan إضافية لتسهيل فلترة النتائج خلال فترة زمنية محددة بالساعات.

أضِف طريقتَي query التاليتَين أسفل طريقة insertScores() الحالية وأعلى طريقة printUsageAndExit() الحالية:

  static void query(DatabaseClient dbClient) {
    String scoreDate;
    String score;
    ResultSet resultSet =
        dbClient
            .singleUse()
            .executeQuery(
                Statement.of(
                    "SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp "
                        + "FROM Players p "
                        + "JOIN Scores s ON p.PlayerId = s.PlayerId "
                        + "ORDER BY s.Score DESC LIMIT 10"));
    while (resultSet.next()) {
      scoreDate = String.valueOf(resultSet.getTimestamp("Timestamp"));
      score = String.format("%,d", resultSet.getLong("Score"));
      System.out.printf(
          "PlayerId: %d  PlayerName: %s  Score: %s  Timestamp: %s\n",
          resultSet.getLong("PlayerId"), resultSet.getString("PlayerName"), score,
          scoreDate.substring(0,10));
    }
  }

  static void query(DatabaseClient dbClient, int timespan) {
    String scoreDate;
    String score;
    Statement statement =
        Statement
            .newBuilder(
              "SELECT p.PlayerId, p.PlayerName, s.Score, s.Timestamp "
              + "FROM Players p "
              + "JOIN Scores s ON p.PlayerId = s.PlayerId "
              + "WHERE s.Timestamp > "
              + "TIMESTAMP_SUB(CURRENT_TIMESTAMP(), "
              + "    INTERVAL @Timespan HOUR) "
              + "ORDER BY s.Score DESC LIMIT 10")
            .bind("Timespan")
            .to(timespan)
            .build();
    ResultSet resultSet =
        dbClient
            .singleUse()
            .executeQuery(statement);
    while (resultSet.next()) {
      scoreDate = String.valueOf(resultSet.getTimestamp("Timestamp"));
      score = String.format("%,d", resultSet.getLong("Score"));
      System.out.printf(
          "PlayerId: %d  PlayerName: %s  Score: %s  Timestamp: %s\n",
          resultSet.getLong("PlayerId"), resultSet.getString("PlayerName"), score,
          scoreDate.substring(0,10));
    }
  }

بعد ذلك، لتفعيل الأمر query، أضِف الرمز التالي إلى عبارة switch(command) في الحقل "الرئيسي" لتطبيقك. :

        case "query":
          if (args.length == 4) {
            int timespan = 0;
            try {
              timespan = Integer.parseInt(args[3]);
            } catch (NumberFormatException e) {
              System.err.println("query command's 'timespan' parameter must be a valid integer.");
              System.exit(1);
            }
            query(dbClient, timespan);
          } else {
            query(dbClient);
          }
          break;

الخطوة الأخيرة لإكمال إضافة "طلب البحث" إلى تطبيقك، هي إضافة نص مساعدة لطلب البحث إلى الطريقة printUsageAndExit(). أضِف سطور الرمز التالية إلى طريقة printUsageAndExit()لتضمين نص المساعدة الخاص بـ "طلب البحث". :

    System.out.println("  java -jar leaderboard.jar query my-instance example-db");
    System.out.println("      - Query players with top ten scores of all time.\n");
    System.out.println("  java -jar leaderboard.jar query my-instance example-db 168");
    System.out.println("      - Query players with top ten scores within a timespan "
        + "specified in hours.\n");

احفظ التغييرات التي أجريتها على ملف App.java من خلال اختيار "حفظ". ضمن الزر "File" في محرر Cloud Shell القائمة.

يمكنك استخدام ملف App.java في دليل dotnet-docs-samples/applications/leaderboard/step6/src للاطّلاع على مثال حول الشكل الذي يجب أن يظهر به ملف App.java بعد إضافة الرمز لتفعيل الأمر query.

لإنشاء تطبيقك، شغِّل mvn package من الدليل الذي يتوفّر فيه ملف pom.xml:

mvn package

لنشغّل التطبيق الآن للتأكّد من تضمين الأمر query الجديد في قائمة الأوامر المحتملة في التطبيق. شغِّل الأمر التالي:

java -jar target/leaderboard.jar

من المفترض أن يظهر لك الأمر query الآن ضمن الإخراج التلقائي للتطبيق كخيار أمر جديد:

Leaderboard 1.0.0
Usage:
  java -jar leaderboard.jar <command> <instance_id> <database_id> [command_option]

Examples:
  java -jar leaderboard.jar create my-instance example-db
      - Create a sample Cloud Spanner database along with sample tables in your project.

  java -jar leaderboard.jar insert my-instance example-db players
      - Insert 100 sample Player records into the database.

  java -jar leaderboard.jar insert my-instance example-db scores
      - Insert sample score data into Scores sample Cloud Spanner database table.

  java -jar leaderboard.jar query my-instance example-db
      - Query players with top ten scores of all time.

  java -jar leaderboard.jar query my-instance example-db 168
      - Query players with top ten scores within a timespan specified in hours.

يمكنك الاطّلاع من الردّ على أنّه بالإضافة إلى وسيطات معرّف المثيل ومعرّف قاعدة البيانات، يتيح لنا الأمر query تحديد فترة زمنية اختيارية بعدد الساعات لاستخدامها في فلترة السجلّات استنادًا إلى قيمتها في العمود Timestamp في الجدول Scores. بما أنّ وسيطة النطاق الزمني اختيارية، يعني هذا أنّه في حال عدم تضمين وسيطة فترة زمنية، لن تتم فلترة أي سجلّات باستخدام الطوابع الزمنية. إذًا، يمكننا استخدام الأمر query بدون "timespan" للحصول على قائمة "أهم عشرة" لاعبين في جميع الأوقات.

لنشغِّل الأمر query بدون تحديد "timespan" باستخدام قيم الوسيطات نفسها التي استخدمناها عند تشغيل الأمر create.

java -jar target/leaderboard.jar query cloudspanner-leaderboard leaderboard

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبين من جميع العصور مثل ما يلي:

PlayerId: 4018687297  PlayerName: Player 83  Score: 999,618  Timestamp: 2017-07-01
PlayerId: 4018687297  PlayerName: Player 83  Score: 998,956  Timestamp: 2017-09-02
PlayerId: 4285713246  PlayerName: Player 51  Score: 998,648  Timestamp: 2017-12-01
PlayerId: 5267931774  PlayerName: Player 49  Score: 997,733  Timestamp: 2017-11-09
PlayerId: 1981654448  PlayerName: Player 35  Score: 997,480  Timestamp: 2018-12-06
PlayerId: 4953940705  PlayerName: Player 87  Score: 995,184  Timestamp: 2018-09-14
PlayerId: 2456736905  PlayerName: Player 84  Score: 992,881  Timestamp: 2017-04-14
PlayerId: 8234617611  PlayerName: Player 19  Score: 992,399  Timestamp: 2017-12-27
PlayerId: 1788051688  PlayerName: Player 76  Score: 992,265  Timestamp: 2018-11-22
PlayerId: 7127686505  PlayerName: Player 97  Score: 992,038  Timestamp: 2017-12-02

لنُشغِّل الآن الأمر query مع الوسيطات اللازمة للاستعلام عن "أهم عشرة". أفضل لاعب في العام من خلال تحديد "فترة زمنية" يساوي عدد الساعات في السنة وهو 8760.

java -jar target/leaderboard.jar query cloudspanner-leaderboard leaderboard 8760

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" أفضل لاعبي العام كما يلي:

PlayerId: 1981654448  PlayerName: Player 35  Score: 997,480  Timestamp: 2018-12-06
PlayerId: 4953940705  PlayerName: Player 87  Score: 995,184  Timestamp: 2018-09-14
PlayerId: 1788051688  PlayerName: Player 76  Score: 992,265  Timestamp: 2018-11-22
PlayerId: 6862349579  PlayerName: Player 30  Score: 990,877  Timestamp: 2018-09-14
PlayerId: 5529627211  PlayerName: Player 16  Score: 989,142  Timestamp: 2018-03-30
PlayerId: 9743904155  PlayerName: Player 1  Score: 988,765  Timestamp: 2018-05-30
PlayerId: 6809119884  PlayerName: Player 7  Score: 986,673  Timestamp: 2018-05-16
PlayerId: 2132710638  PlayerName: Player 54  Score: 983,108  Timestamp: 2018-09-11
PlayerId: 2320093590  PlayerName: Player 79  Score: 981,373  Timestamp: 2018-05-07
PlayerId: 9554181430  PlayerName: Player 80  Score: 981,087  Timestamp: 2018-06-21

لنقم الآن بتشغيل الأمر query للاستعلام عن "أعلى عشرة" لاعب الشهر من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الشهر وهو 730.

java -jar target/leaderboard.jar query cloudspanner-leaderboard leaderboard 730

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" اللاعبين الأفضل في الشهر كما يلي:

PlayerId: 3869829195  PlayerName: Player 69  Score: 949,686  Timestamp: 2019-02-19
PlayerId: 7448359883  PlayerName: Player 20  Score: 938,998  Timestamp: 2019-02-07
PlayerId: 1981654448  PlayerName: Player 35  Score: 929,003  Timestamp: 2019-02-22
PlayerId: 9336678658  PlayerName: Player 44  Score: 914,106  Timestamp: 2019-01-27
PlayerId: 6968576389  PlayerName: Player 40  Score: 898,041  Timestamp: 2019-02-21
PlayerId: 5529627211  PlayerName: Player 16  Score: 896,433  Timestamp: 2019-01-29
PlayerId: 9395039625  PlayerName: Player 59  Score: 879,495  Timestamp: 2019-02-09
PlayerId: 2094604854  PlayerName: Player 39  Score: 860,434  Timestamp: 2019-02-01
PlayerId: 9395039625  PlayerName: Player 59  Score: 849,955  Timestamp: 2019-02-21
PlayerId: 4285713246  PlayerName: Player 51  Score: 805,654  Timestamp: 2019-02-02

لنقم الآن بتشغيل الأمر query للاستعلام عن "أعلى عشرة" لاعبو الأسبوع من خلال تحديد "فترة زمنية" يساوي عدد الساعات في الأسبوع وهو 168.

java -jar target/leaderboard.jar query cloudspanner-leaderboard leaderboard 168

من المفترض أن يظهر لك رد يتضمن "أفضل عشرة" لاعبي الأسبوع مثل ما يلي:

PlayerId: 3869829195  PlayerName: Player 69  Score: 949,686  Timestamp: 2019-02-19
PlayerId: 1981654448  PlayerName: Player 35  Score: 929,003  Timestamp: 2019-02-22
PlayerId: 6968576389  PlayerName: Player 40  Score: 898,041  Timestamp: 2019-02-21
PlayerId: 9395039625  PlayerName: Player 59  Score: 849,955  Timestamp: 2019-02-21
PlayerId: 5954045812  PlayerName: Player 8  Score: 795,639  Timestamp: 2019-02-22
PlayerId: 3889939638  PlayerName: Player 71  Score: 775,252  Timestamp: 2019-02-21
PlayerId: 5529627211  PlayerName: Player 16  Score: 604,695  Timestamp: 2019-02-19
PlayerId: 9006728426  PlayerName: Player 3  Score: 457,208  Timestamp: 2019-02-22
PlayerId: 8289497066  PlayerName: Player 58  Score: 227,697  Timestamp: 2019-02-20
PlayerId: 8065482904  PlayerName: Player 99  Score: 198,429  Timestamp: 2019-02-24

ممتاز!

والآن بعد إضافة السجلات، ستتمكن خدمة Cloud Spanner من تغيير حجم قاعدة البيانات إلى الحجم الذي تحتاج إليه. بغض النظر عن حجم قاعدة بياناتك، يمكن أن يستمر تغيير قائمة الصدارة في لعبتك بدقة باستخدام Cloud Spanner وتقنية Truetime الخاصة بها.

7. تنظيف

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

8. تهانينا!

المواضيع التي تناولناها:

  • مثيلات Google Cloud Spanner وقواعد البيانات ومخطط الجدول لقائمة الصدارة
  • كيفية إنشاء تطبيق وحدة تحكم Java
  • كيفية إنشاء قاعدة بيانات وجدول Spanner باستخدام مكتبة برامج Java
  • كيفية تحميل البيانات إلى قاعدة بيانات Spanner باستخدام مكتبة برامج Java
  • كيفية الاستعلام عن "أعلى عشرة" نتائج من بياناتك باستخدام الطوابع الزمنية لتنفيذ Spanner ومكتبة برامج Java

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

يُرجى إرسال ملاحظاتك إلينا

  • يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.