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) وأنشِئ مشروعًا جديدًا.
إذا كان لديك مشروع حالي، انقر على القائمة المنسدلة لاختيار المشروع في أعلى يمين وحدة التحكّم:

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

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

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

تذكَّر رقم تعريف المشروع، وهو اسم فريد في جميع مشاريع Google Cloud (الاسم أعلاه مستخدَم حاليًا ولن يكون متاحًا لك، نأسف لذلك). سيتم الإشارة إليه لاحقًا في هذا الدرس العملي باسم PROJECT_ID.
بعد ذلك، إذا لم يسبق لك إجراء ذلك، عليك تفعيل الفوترة في Developers Console من أجل استخدام موارد Google Cloud وتفعيل Cloud Spanner API.

لن تكلفك تجربة هذا الدرس التطبيقي حول الترميز أكثر من بضعة دولارات، ولكن قد تكون التكلفة أعلى إذا قررت استخدام المزيد من الموارد أو إذا تركتها قيد التشغيل (راجِع قسم "التنظيف" في نهاية هذا المستند). يمكنك الاطّلاع على مستندات أسعار Google Cloud Spanner هنا.
يمكن لمستخدمي Google Cloud Platform الجدد الاستفادة من فترة تجريبية مجانية بقيمة 300 دولار أمريكي، ما يجعل هذا الدرس العملي مجانيًا تمامًا.
إعداد Google Cloud Shell
على الرغم من إمكانية تشغيل Google Cloud وSpanner عن بُعد من الكمبيوتر المحمول، سنستخدم في هذا الدرس التطبيقي حول الترميز Google Cloud Shell، وهي بيئة سطر أوامر تعمل في السحابة الإلكترونية.
يتم تحميل هذا الجهاز الافتراضي المستند إلى Debian بجميع أدوات التطوير التي تحتاج إليها. توفّر هذه الخدمة دليلًا رئيسيًا دائمًا بسعة 5 غيغابايت وتعمل في Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. وهذا يعني أنّ كل ما تحتاجه لهذا الدرس التطبيقي حول الترميز هو متصفّح (نعم، يمكن استخدامه على جهاز Chromebook).
- لتفعيل Cloud Shell من Cloud Console، ما عليك سوى النقر على تفعيل Cloud Shell
(يستغرق توفير البيئة والاتصال بها بضع لحظات فقط).
بعد الاتصال بـ 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:
يضبط Cloud Shell أيضًا بعض متغيرات البيئة تلقائيًا، ما قد يكون مفيدًا عند تنفيذ الأوامر المستقبلية.
echo $GOOGLE_CLOUD_PROJECT
ناتج الأمر
<PROJECT_ID>
- أخيرًا، اضبط المنطقة التلقائية وإعدادات المشروع.
gcloud config set compute/zone us-central1-f
يمكنك اختيار مجموعة متنوعة من المناطق المختلفة. لمزيد من المعلومات، يُرجى الاطّلاع على الأقاليم والمناطق.
ملخّص
في هذه الخطوة، عليك إعداد بيئتك.
التالي
بعد ذلك، عليك إعداد مثيل Cloud Spanner.
3- إعداد مثيل Cloud Spanner
في هذه الخطوة، سنُعدّ مثيل Cloud Spanner لهذا الدرس العملي. ابحث عن إدخال Spanner
في قائمة الهمبرغر في أعلى يمين الصفحة
أو ابحث عن Spanner بالضغط على "/" وكتابة "Spanner".

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

في الخطوة التالية، سنستخدم مكتبة برامج Java للعملاء لإنشاء قاعدة بيانات ومخطط في مثيلنا الجديد.
4. إنشاء قاعدة بيانات ومخطط
في هذه الخطوة، سننشئ قاعدة البيانات والنموذج الخاصين بنا.
لنستخدِم مكتبة برامج Java لإنشاء جدولَين: جدول "اللاعبون" الذي يتضمّن معلومات اللاعبين وجدول "النتائج" لتخزين نتائج اللاعبين. لإجراء ذلك، سنشرح خطوات إنشاء تطبيق وحدة تحكّم Java في Cloud Shell.
أولاً، استنسِخ الرمز النموذجي لهذا الدرس التطبيقي حول الترميز من GitHub عن طريق كتابة الأمر التالي في Cloud Shell:
git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git
بعد ذلك، غيِّر الدليل إلى دليل "التطبيقات" (applications) الذي ستنشئ فيه تطبيقك.
cd java-docs-samples/spanner/leaderboard
يقع كل الرمز البرمجي المطلوب لهذا الدرس التطبيقي حول الترميز في الدليل java-docs-samples/spanner/leaderboard/complete الحالي كتطبيق C# قابل للتنفيذ باسم Leaderboard ليتم استخدامه كمرجع أثناء تقدّمك في الدرس التطبيقي حول الترميز. سننشئ دليلاً جديدًا وننشئ نسخة من تطبيق "قائمة الصدارة" على مراحل.
أنشئ دليلاً جديدًا باسم "codelab" للتطبيق وانتقِل إلى الدليل باستخدام الأمر التالي:
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" من خلال النقر على الرمز المميّز أدناه:

افتح ملف pom.xml ضِمن مجلد leaderboard. افتح ملف pom.xml الموجود في المجلد java-docs-samples\ spanner\leaderboard\codelab\leaderboard.يضبط هذا الملف نظام التصميم Maven لإنشاء تطبيقنا في ملف jar، بما في ذلك جميع الاعتماديات.
أضِف قسم إدارة التبعيات الجديد التالي مباشرةً أسفل العنصر </properties> الحالي:
<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 من خلال النقر على "حفظ" في القائمة "ملف" في "محرّر 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 من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر 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، من المفترض أن تظهر قاعدة البيانات والجداول الجديدة في القائمة على الجانب الأيمن.

في الخطوة التالية، سنعدّل تطبيقنا لتحميل بعض البيانات إلى قاعدة البيانات الجديدة.
5- تحميل البيانات
لدينا الآن قاعدة بيانات باسم leaderboard تحتوي على جدولَين هما Players وScores. لنستخدِم الآن مكتبة برامج Java لملء جدول Players باللاعبين وجدول Scores بالنتائج العشوائية لكل لاعب.
إذا لم يكن مفتوحًا من قبل، افتح "محرِّر Cloud Shell" بالنقر على الرمز المميّز أدناه:

بعد ذلك، عدِّل ملف App.java في Cloud Shell Editor لإضافة أمر 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;
بعد ذلك، أضِف طرق insert و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 يعمل، أضِف الرمز التالي إلى طريقة "main" في تطبيقك ضمن عبارة 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();
}
الخطوة الأخيرة لإكمال إضافة وظيفة "الإدراج" إلى تطبيقك هي إضافة نص مساعدة لأمر "الإدراج" إلى طريقة 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 من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر 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.
يمكنك أن ترى من الردّ أنّه بالإضافة إلى معرّف المثيل ومعرّف قاعدة البيانات، هناك وسيطة أخرى يمكن أن تكون قيمتها "اللاعبون" أو "النتائج".
لننفّذ الآن الأمر 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 في صف جدول معيّن.
يمكنك أيضًا إدراج قيم الطابع الزمني الخاصة بك في عمود "الطابع الزمني للتثبيت" (commit timestamp) طالما أنّك تُدرج طابعًا زمنيًا بقيمة في الماضي، وهو ما سنفعله لأغراض هذا الدرس العملي.
لننفّذ الآن الأمر 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 تلقائيًا بالطابع الزمني لوقت إجراء المعاملة "إدراج" بالضبط، يمكنك بدلاً من ذلك إدراج الثابت Java Value.COMMIT_TIMESTAMP كما في مقتطف الرمز التالي:
.bind("Timestamp")
.to(Value.COMMIT_TIMESTAMP)
بعد إكمال عملية تحميل البيانات، لنتحقّق من القيم التي كتبناها للتو في جداولنا الجديدة. اختَر أولاً قاعدة بيانات leaderboard ثم اختَر جدول Players. انقر على علامة التبويب Data. يجب أن تظهر لك بيانات في العمودَين PlayerId وPlayerName في الجدول.

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

أحسنت! لنحدّث تطبيقنا لتنفيذ بعض طلبات البحث التي يمكننا استخدامها لإنشاء قائمة صدارة للألعاب.
6. تنفيذ طلبات البحث في قائمة الصدارة
بعد أن أعددنا قاعدة البيانات وحمّلنا المعلومات في جداولنا، لننشئ الآن قائمة صدارة باستخدام هذه البيانات. ولإجراء ذلك، علينا الإجابة عن الأسئلة الأربعة التالية:
- ما هي قائمة "أفضل عشرة لاعبين" على الإطلاق؟
- ما هي قائمة "أفضل عشرة لاعبين" لهذا العام؟
- ما هي "أفضل عشرة" لاعبين في الشهر؟
- ما هي "أفضل عشرة" لاعبين في الأسبوع؟
لنحدّث تطبيقنا لتنفيذ طلبات 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) في طريقة "main" في تطبيقك:
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() لتضمين نص المساعدة للأمر "query":
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 من خلال النقر على "حفظ" ضمن قائمة "ملف" في "محرّر 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 بدون قيمة "فترة زمنية" للحصول على قائمة بأفضل عشرة لاعبين على الإطلاق.
لننفّذ الأمر query بدون تحديد "الفترة الزمنية"، وذلك باستخدام قيم الوسيطة نفسها التي استخدمناها عند تنفيذ الأمر 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 وحذف المثيل الذي أنشأناه في خطوة codelab المسماة "إعداد مثيل Cloud Spanner".
8. تهانينا!
المواضيع التي تناولناها:
- مثيلات وقواعد بيانات ومخطط جداول Google Cloud Spanner للوحات الصدارة
- كيفية إنشاء تطبيق وحدة تحكّم Java
- كيفية إنشاء قاعدة بيانات وجداول Spanner باستخدام مكتبة برامج Java
- كيفية تحميل البيانات إلى قاعدة بيانات Spanner باستخدام مكتبة برامج Java
- كيفية طلب نتائج "أفضل عشرة" من بياناتك باستخدام الطوابع الزمنية لعمليات الإرسال في Spanner ومكتبة برامج Java
الخطوات التالية:
- الاطّلاع على التقرير الموجز حول إمكانات Spanner
- مزيد من المعلومات عن تصميم المخطط وأفضل الممارسات المتعلّقة بالطلبات
- مزيد من المعلومات عن الطوابع الزمنية لعمليات الإيداع في Cloud Spanner
تقديم ملاحظاتك
- يُرجى تخصيص بعض الوقت لإكمال الاستطلاع القصير جدًا.