Cloud Spanner: tworzenie tabeli wyników w grach w języku Java

1. Omówienie

Google Cloud Spanner to w pełni zarządzana, skalowalna w poziomie, rozproszona globalnie, relacyjna usługa baz danych, która zapewnia transakcje ACID i semantykę SQL bez utraty wydajności i wysokiej dostępności.

W tym module dowiesz się, jak skonfigurować instancję Cloud Spanner. Wykonasz kolejne kroki tworzenia bazy danych i schematu, które będą służyć do tworzenia tabeli wyników w grach. Na początek utwórz tabelę zawodników, w której będą przechowywane informacje o graczach, oraz tabelę wyników, by zapisać wyniki.

Następnie wypełnij tabele przykładowymi danymi. Następnie na koniec modułu uruchomisz kilka przykładowych zapytań o 10 najczęstszych pytań i usuniesz instancję, aby zwolnić zasoby.

Czego się nauczysz

  • Jak skonfigurować instancję Cloud Spanner.
  • Jak utworzyć bazę danych i tabele.
  • Jak używać kolumny z sygnaturą czasową zatwierdzenia.
  • Jak wczytywać dane do tabeli bazy danych Cloud Spanner z sygnaturami czasowymi.
  • Jak wysyłać zapytania do bazy danych Cloud Spanner.
  • Jak usunąć instancję Cloud Spanner.

Czego potrzebujesz

Jak wykorzystasz ten samouczek?

Tylko do przeczytania Przeczytaj go i wykonaj ćwiczenia

Jak oceniasz swoje doświadczenia z Google Cloud Platform?

Początkujący Poziom średnio zaawansowany Biegły
.

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

Jeśli nie masz jeszcze konta Google (w Gmailu lub Google Apps), musisz je utworzyć. Zaloguj się w konsoli Google Cloud Platform ( console.cloud.google.com) i utwórz nowy projekt.

Jeśli masz już projekt, kliknij menu wyboru projektu w lewym górnym rogu konsoli:

6c9406d9b014760.png

i kliknij „NOWY PROJEKT”. w wyświetlonym oknie, aby utworzyć nowy projekt:

f708315ae07353d0.png

Jeśli nie masz jeszcze projektu, zobaczysz takie okno dialogowe umożliwiające utworzenie pierwszego:

870a3cbd6541ee86.png

W kolejnym oknie tworzenia projektu możesz wpisać szczegóły nowego projektu:

6a92c57d3250a4b3.png

Zapamiętaj identyfikator projektu, który jest niepowtarzalną nazwą we wszystkich projektach Google Cloud (powyższa nazwa jest już zajęta i nie będzie Ci odpowiadać). W dalszej części tego ćwiczenia w programie będzie ona określana jako PROJECT_ID.

Następnie musisz włączyć płatności w Developers Console, aby korzystać z zasobów Google Cloud i włączyć interfejs Cloud Spanner API.

15d0ef27a8fbab27.png

Ukończenie tego ćwiczenia w Codelabs nie powinno kosztować więcej niż kilka dolarów, ale może być droższe, jeśli zdecydujesz się użyć więcej zasobów lub nie chcesz ich uruchamiać (patrz sekcja „Czyszczenie” na końcu tego dokumentu). Cennik Google Cloud Spanner znajdziesz tutaj.

Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego w wysokości 300 USD, dzięki czemu te ćwiczenia z programowania są całkowicie bezpłatne.

Konfiguracja Google Cloud Shell

Usługi Google Cloud i Spanner można obsługiwać zdalnie z poziomu laptopa, ale w ramach tego ćwiczenia w programowaniu użyjemy Google Cloud Shell – środowiska wiersza poleceń działającego w chmurze.

Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia z programowania wystarczy przeglądarka (tak, działa ona na Chromebooku).

  1. Aby aktywować Cloud Shell z poziomu konsoli Cloud, kliknij Aktywuj Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (udostępnienie środowiska i połączenie z nim powinno zająć tylko chwilę).

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

Zrzut ekranu 2017-06-14 o 10.13.43 PM.png

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest już uwierzytelniony, a projekt jest już ustawiony na PROJECT_ID.

gcloud auth list

Dane wyjściowe polecenia

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

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest skonfigurowany, uruchom po prostu to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź identyfikator użyty w krokach konfiguracji lub wyszukaj go w panelu Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell ustawia też domyślnie niektóre zmienne środowiskowe, które mogą być przydatne podczas uruchamiania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Dane wyjściowe polecenia

<PROJECT_ID>
  1. Na koniec ustaw domyślną strefę i konfigurację projektu.
gcloud config set compute/zone us-central1-f

Możesz wybrać różne strefy. Więcej informacji znajdziesz w artykule Regiony i Strefy.

Podsumowanie

W tym kroku skonfigurujesz środowisko.

Dalsze czynności

Następnie skonfigurujesz instancję Cloud Spanner.

3. Konfigurowanie instancji Cloud Spanner

W tym kroku skonfigurujemy instancję Cloud Spanner na potrzeby tego ćwiczenia w Codelabs. Wyszukaj pozycję Spannera 1a6580bd3d3e6783.png w menu Hamburger po lewej stronie 3129589f7bc9e5ce.png lub wyszukaj usługę Spanner, naciskając „/” i wpisz „Spanner”

36e52f8df8e13b99.png

Następnie kliknij 95269e75bc8c3e4d.png i wypełnij formularz, wpisując nazwę instancji cloudspanner-leaderboard dla swojej instancji, wybierając konfigurację (wybierz instancję regionalną) i ustaw liczbę węzłów. Do tego ćwiczenia w Codelabs potrzebujemy tylko 1 węzła. Aby korzystać z instancji produkcyjnych i kwalifikować się do gwarancji jakości usług Cloud Spanner, w instancji Cloud Spanner musisz mieć co najmniej 3 węzły.

Kliknij „Utwórz”. a w ciągu kilku sekund będziesz mieć do dyspozycji instancję Cloud Spanner.

dceb68e9ed3801e8.png

W następnym kroku użyjemy biblioteki klienta w języku Java do utworzenia bazy danych i schematu w naszej nowej instancji.

4. Tworzenie bazy danych i schematu

W tym kroku utworzymy naszą przykładową bazę danych i schemat.

Utwórzmy dwie tabele za pomocą biblioteki klienta w Javie: tabela zawodników z informacjami o graczach i tabela wyników, w której są przechowywane ich wyniki. Przeprowadzimy Cię przez proces tworzenia w Cloud Shell aplikacji konsoli Java.

Najpierw skopiuj z GitHub przykładowy kod tego ćwiczenia z programowania, wpisując w Cloud Shell to polecenie:

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

Następnie zmień katalog na „applications”. w którym utworzysz aplikację.

cd java-docs-samples/spanner/leaderboard

Cały kod wymagany w tym ćwiczeniu w Codelabs znajduje się w istniejącym katalogu java-docs-samples/spanner/leaderboard/complete jako uruchamiana aplikacja w języku C# o nazwie Leaderboard, która będzie służyć jako wzorzec. Utworzymy nowy katalog i etapami utworzymy kopię aplikacji Tabela wyników.

Utwórz nowy katalog o nazwie „codelab” dla aplikacji i zmień na nią katalog za pomocą tego polecenia:

mkdir codelab && cd $_

Utwórz nową podstawową aplikację w Javie o nazwie „Tabela wyników”. za pomocą tego polecenia Maven (mvn):

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

To polecenie tworzy prostą aplikację konsolową składającą się z 2 plików głównych – pliku konfiguracji aplikacji Maven pom.xml i pliku aplikacji Java App.java.

Następnie zmień katalog na utworzony właśnie katalog tabeli wyników i wymień jego zawartość:

cd leaderboard && ls

Plik pom.xml i katalog src powinny wyświetlić się na liście:

pom.xml  src

Teraz zaktualizujmy aplikację konsoli, edytując obiekt App.java, aby użyć biblioteki klienta Java Spanner do utworzenia tabeli wyników złożonej z 2 tabel. Gracze i wyniki. Możesz to zrobić w edytorze Cloud Shell:

Otwórz edytor Cloud Shell, klikając podświetloną ikonę:

73cf70e05f653ca.png

Otwórz plik pom.xml w folderze tabeli wyników. Otwórz plik pom.xml, który znajduje się w folderze java-docs-samples\ spanner\leaderboard\codelab\leaderboard.Ten plik konfiguruje system kompilacji Maven do kompilowania naszej aplikacji w pliku jar z uwzględnieniem wszystkich zależności.

Dodaj następującą 1 nową sekcję zarządzania zależnościami tuż pod istniejącymi elementami </properties> element:

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

Dodaj też 1 nową zależność w istniejących <zależności> , co spowoduje dodanie do aplikacji biblioteki klienta Cloud Spanner w języku Java.

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

Następnie zastąp istniejący tag <build> w pliku pom.xml. z następującym tagiem <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>

Zapisz zmiany wprowadzone w pliku pom.xml, wybierając „Zapisz” w menu „Plik” edytora Cloud Shell lub naciskając klawisz „Ctrl” i „S” klawiszy na klawiaturze.

Następnie otwórz plik App.java w edytorze Cloud Shell w folderze src/main/java/com/google/codelabs/. Zastąp istniejący kod pliku kodem wymaganym do utworzenia bazy danych leaderboard oraz tabel Players i Scores. W tym celu wklej w pliku App.java ten kod 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");
  }
}

Aby zapisać zmiany wprowadzone w pliku App.java, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku App.java po dodaniu kodu włączającego polecenie create, możesz użyć pliku App.java z katalogu java-docs-samples/spanner/leaderboard/step4/src.

Aby skompilować aplikację, uruchom pakiet mvn z katalogu, w którym znajduje się pom.xml:

mvn package

Po skompilowaniu pliku jar w Javie uruchom powstałą w ten sposób aplikację w Cloud Shell, wpisując to polecenie:

java -jar target/leaderboard.jar

Zostaną wyświetlone dane wyjściowe podobne do tych:

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.

Na podstawie tej odpowiedzi widzimy, że jest to aplikacja Leaderboard, która obecnie ma 1 możliwe polecenie: create. Jak widać, oczekiwane argumenty polecenia create to identyfikator instancji i identyfikator bazy danych.

Teraz uruchom następujące polecenie.

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

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

W sekcji Cloud Spanner konsoli Cloud powinna pojawić się nowa baza danych i tabele w menu po lewej stronie.

ba9008bb84cb90b0.png

W następnym kroku zaktualizujemy naszą aplikację, aby wczytywała część danych do Twojej nowej bazy danych.

5. Wczytaj dane

Mamy teraz bazę danych o nazwie leaderboard, która zawiera 2 tabele; Players i Scores. Teraz za pomocą biblioteki klienta w Javie zapełnimy tabelę Players graczami, a tabelę Scores losowymi wynikami każdego z nich.

Jeśli edytor Cloud Shell nie jest jeszcze otwarty, otwórz go, klikając podświetloną ikonę:

ef49fcbaaed19024.png

Następnie zmodyfikuj plik App.java w edytorze Cloud Shell, dodając polecenie insert, które umożliwia wstawienie 100 odtwarzaczy do tabeli Players lub pozwala wstawić 4 losowe wyniki w tabeli Scores każdego gracza w tabeli Players.

Najpierw zaktualizuj sekcję imports u góry pliku aplikacji, zastępując jej obecną zawartość. Gdy to zrobisz, powinna wyglądać mniej więcej tak:

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;

Następnie dodaj te metody insert, insertPlayers i insertScores pod istniejącą metodą create() oraz nad istniejącą metodą 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...");
    }
  }

Aby polecenie insert działało, dodaj ten kod do sekcji „main” aplikacji metoda w instrukcji switch (command) :

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

Gdy skończysz, instrukcja switch (command) powinna wyglądać tak:

      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();
      }

Ostatni krok potrzebny do dodania funkcji „insert” do Twojej aplikacji jest dodanie tekstu pomocy dla polecenia „insert” do metody printUsageAndExit(). Dodaj do metody printUsageAndExit() te wiersze kodu, aby dołączyć tekst pomocy do polecenia wstawiania:

    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");

Aby zapisać zmiany wprowadzone w pliku App.java, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku App.java po dodaniu kodu włączającego polecenie insert, możesz użyć pliku App.java z katalogu java-docs-samples/spanner/leaderboard/step5/src.

Teraz ponownie skompiluj i uruchommy aplikację, aby sprawdzić, czy nowe polecenie insert znajduje się na liście możliwych poleceń aplikacji.

Aby skompilować aplikację, uruchom mvn package z katalogu, w którym znajduje się plik pom.xml:

mvn package

Po skompilowaniu pliku jar w Javie uruchom następujące polecenie:

java -jar target/leaderboard.jar

Polecenie insert powinno pojawić się w domyślnych danych wyjściowych aplikacji:

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.

Z odpowiedzi wynika, że oprócz identyfikatora instancji i identyfikatora bazy danych istnieje jeszcze inny argument, który może mieć wartość „odtwarzacze” czyli „wyniki”.

Teraz uruchom polecenie insert z tymi samymi wartościami argumentów, które użyliśmy przy wywołaniu polecenia create, dodając „players” jako dodatkowy „typ wstawiania”, .

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

Done inserting player records...

Teraz za pomocą biblioteki klienta w języku Java uzupełnimy tabelę Scores 4 losowymi wynikami wraz z sygnaturami czasowymi każdego gracza w tabeli Players.

Kolumna Timestamp w tabeli Scores jest zdefiniowana jako „sygnatura czasowa zatwierdzenia” za pomocą tej instrukcji SQL, która została wykonana podczas wcześniejszego uruchamiania polecenia 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

Zwróć uwagę na atrybut OPTIONS(allow_commit_timestamp=true). Powoduje to, że plik Timestamp jest „sygnaturą czasową zatwierdzenia” i umożliwia jego automatyczne wypełnianie dokładną sygnaturą czasową transakcji w przypadku operacji INSERT i UPDATE w danym wierszu tabeli.

W polu „sygnatura czasowa zatwierdzenia” możesz też wstawić własne wartości sygnatury czasowej tak długo, jak długo wstawisz sygnaturę czasową z wartością z przeszłości, co zrobimy na potrzeby tego ćwiczenia z programowania.

Teraz uruchom polecenie insert z tymi samymi wartościami argumentów, które użyliśmy przy wywołaniu polecenia create, dodając „wyniki”. jako dodatkowy „typ wstawiania”, .

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

Po kilku sekundach powinna pojawić się taka odpowiedź:

Done inserting score records...

Uruchomiono insert z typem wstawiania określony jako scores wywołuje metodę insertScores, która używa tych fragmentów kodu, aby wstawić losowo wygenerowaną sygnaturę czasową z datą i godziną przypadającą w przeszłości:

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

Aby automatycznie wypełniać kolumnę Timestamp sygnaturą czasową, w której znajduje się pole „Wstaw” zamiast transakcji, możesz zamiast tego wstawić stałą Java Value.COMMIT_TIMESTAMP jak w tym fragmencie kodu:

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

Po zakończeniu wczytywania danych sprawdźmy wartości, które właśnie zapisaliśmy w nowych tabelach. Najpierw wybierz bazę danych leaderboard, a potem tabelę Players. Kliknij kartę Data. Powinny być widoczne dane w kolumnach PlayerId i PlayerName tabeli.

7bc2c96293c31c49.png

Następnie sprawdźmy, czy tabela wyników zawiera też dane, klikając tabelę Scores i wybierając kartę Data. Powinny być widoczne dane w kolumnach PlayerId, Timestamp i Score tabeli.

d8a4ee4f13244c19.png

Brawo! Zaktualizujmy aplikację, aby uruchomić kilka zapytań, które pozwolą nam utworzyć tabelę wyników w grach.

6. Uruchamianie zapytań dotyczących tabel wyników

Po skonfigurowaniu bazy danych i wczytaniu informacji do tabel utwórzmy długi baner przy użyciu tych danych. W tym celu musimy odpowiedzieć na następujące 4 pytania:

  1. Którzy gracze są w „największej dziesiątce” wszech czasów?
  2. Którzy gracze są w „największej dziesiątce” roku?
  3. Którzy gracze są w „największej dziesiątce” dzień miesiąca?
  4. Którzy gracze są w „największej dziesiątce” dnia tygodnia?

Zaktualizujmy aplikację, aby uruchomić zapytania SQL, które pozwolą odpowiedzieć na te pytania.

Dodamy polecenie query, które umożliwi uruchamianie zapytań w celu uzyskania informacji wymaganych na potrzeby tablicy wyników.

Edytuj plik App.java w edytorze Cloud Shell, aby zaktualizować aplikację przez dodanie polecenia query. Polecenie query składa się z 2 metod query. Jedna z nich wymaga tylko argumentu DatabaseClient, a druga używa dodatkowego argumentu timespan, aby ułatwić filtrowanie wyników według przedziału czasu określonego w godzinach.

Dodaj dwie metody query pod istniejącą metodą insertScores() i nad istniejącą metodą 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));
    }
  }

Aby polecenie query działało, dodaj ten kod do instrukcji switch(command) w sekcji „main” aplikacji :

        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;

Ostatni krok potrzebny do dodania „zapytania” funkcji w aplikacji jest dodanie tekstu pomocy do zapytania do metody printUsageAndExit(). Dodaj następujące wiersze kodu do metody printUsageAndExit(), aby dodać tekst pomocy do „zapytania” polecenie:

    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");

Aby zapisać zmiany wprowadzone w pliku App.java, kliknij „Zapisz” w menu „Plik” edytora Cloud Shell .

Aby zobaczyć przykładowy wygląd pliku App.java po dodaniu kodu włączającego polecenie query, możesz użyć pliku App.java z katalogu dotnet-docs-samples/applications/leaderboard/step6/src.

Aby skompilować aplikację, uruchom mvn package z katalogu, w którym znajduje się plik pom.xml:

mvn package

Teraz uruchom aplikację, aby sprawdzić, czy nowe polecenie query znajduje się na liście możliwych poleceń aplikacji. Uruchom to polecenie:

java -jar target/leaderboard.jar

Polecenie query powinno być widoczne w domyślnych danych wyjściowych aplikacji jako nowa opcja polecenia:

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.

Z odpowiedzi możesz zauważyć, że oprócz argumentów Identyfikator instancji i Identyfikator bazy danych polecenie query pozwala nam określić opcjonalny przedział czasu w godzinach, który będzie używany do filtrowania rekordów na podstawie ich wartości w kolumnie Timestamp tabeli Scores. Ponieważ argument zakresu czasu w opcji opcjonalny oznacza, że jeśli ten argument nie zostanie uwzględniony, żadne rekordy nie będą filtrowane według sygnatur czasowych. Możemy więc użyć polecenia query bez atrybutu „okres” aby otrzymać listę 10 najlepszych graczy wszechczasów.

Uruchommy polecenie query bez określania zakresu czasu, używając tych samych wartości argumentów, które zostały użyte podczas uruchamiania polecenia create.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy wszech czasów jak poniżej:

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

Teraz uruchom polecenie query z niezbędnymi argumentami, aby wysłać zapytanie do listy „10 najwyższych wyników”. graczy w danym roku, określając przedział czasu równy liczbie godzin w roku, czyli 8760.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy roku, na przykład:

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

Teraz uruchommy polecenie query, aby wysłać zapytanie do listy „10 największych” graczy w miesiącu, określając „timespan” równa się liczbie godzin w miesiącu, która wynosi 730.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy w miesiącu na przykład tak:

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

Teraz uruchommy polecenie query, aby wysłać zapytanie do listy „10 największych” graczy tygodnia poprzez określenie „czasu trwania”, co tydzień, czyli 168 godzin.

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

Powinna pojawić się odpowiedź zawierająca pozycję „Dziesięć najlepszych” graczy tygodnia na przykład poniżej:

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

Doskonale!

Teraz podczas dodawania rekordów Cloud Spanner przeskaluje bazę danych do potrzebnego rozmiaru. Bez względu na to, jak bardzo Twoja baza danych się rozrasta, tablica wyników Twojej gry może być nadal skalowalna z dokładnością dzięki usłudze Cloud Spanner i technologii Truetime.

7. Czyszczenie

Po całej zabawie ze Spannerem musimy też posprzątać nasz plac zabaw, by zaoszczędzić cenne zasoby i pieniądze. Na szczęście to łatwy krok. Wystarczy przejść do sekcji Cloud Spanner w konsoli Cloud i usunąć instancję, którą utworzyliśmy w kroku w Codelabs o nazwie „Skonfiguruj instancję Cloud Spanner”.

8. Gratulacje!

Omówione zagadnienia:

  • Schemat instancji, baz danych i tabel Google Cloud Spanner do tablicy wyników
  • Jak utworzyć aplikację konsoli Java
  • Jak utworzyć bazę danych i tabele Spannera przy użyciu biblioteki klienta w Javie
  • Jak wczytać dane do bazy danych Spannera za pomocą biblioteki klienta w Javie
  • Jak wysłać zapytanie dotyczące listy „Top 10” na podstawie Twoich danych za pomocą sygnatur czasowych zatwierdzenia usługi Spanner i biblioteki klienta w języku Java

Dalsze kroki:

Prześlij nam swoją opinię

  • Poświęć chwilę na wypełnienie naszej bardzo krótkiej ankiety