Cloud Spanner : créer un classement pour un jeu en Java

Google Cloud Spanner est un service de base de données relationnelle entièrement géré, distribué à l'échelle mondiale et évolutif horizontalement qui fournit des transactions ACID et une sémantique SQL sans compromis sur les performances et le niveau de disponibilité.

Dans cet atelier, vous allez apprendre à configurer une instance Cloud Spanner. Au fil des étapes, vous allez créer une base de données et un schéma qui peuvent être utilisés pour un classement de jeu. Vous allez commencer par créer une table "Players" (Joueurs) pour stocker les informations concernant les joueurs et un tableau "Scores" pour stocker les scores des joueurs.

Vous remplirez ensuite ces tables avec des exemples de données. Vous finirez l'atelier en exécutant quelques exemples de requêtes de "Top 10" avant de supprimer l'instance pour libérer les ressources.

Points abordés

  • Comment configurer une instance Cloud Spanner.
  • Comment créer une base de données et des tables.
  • Comment utiliser une colonne d'horodatage de commit.
  • Comment charger des données dans une table de base de données Cloud Spanner avec des horodatages.
  • Comment interroger votre base de données Cloud Spanner.
  • Comment supprimer une instance Cloud Spanner.

Prérequis

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Quel est votre niveau d'expérience avec Google Cloud Platform ?

Débutant Intermédiaire Expert

Configuration de l'environnement au rythme de chacun

Si vous ne possédez pas encore de compte Google (Gmail ou Google Apps), vous devez en créer un. Connectez-vous à la console Google Cloud Platform (console.cloud.google.com) et créez un projet.

Si vous avez déjà un projet, cliquez sur le menu déroulant de sélection du projet dans l'angle supérieur gauche de la console :

6c9406d9b014760.png

Cliquez ensuite sur le bouton "NEW PROJECT" (NOUVEAU PROJET) dans la boîte de dialogue qui s'affiche pour créer un projet :

f708315ae07353d0.png

Si vous n'avez pas encore de projet, une boîte de dialogue semblable à celle-ci apparaîtra pour vous permettre d'en créer un :

870a3cbd6541ee86.png

La boîte de dialogue de création de projet suivante vous permet de saisir les détails de votre nouveau projet :

6a92c57d3250a4b3.png

Notez l'ID du projet. Il s'agit d'un nom unique pour tous les projets Google Cloud, ce qui implique que le nom ci-dessus n'est plus disponible pour vous… Désolé ! Tout au long de cet atelier de programmation, nous utiliserons PROJECT_ID pour faire référence à cet ID.

Ensuite, si ce n'est pas déjà fait, vous devez activer la facturation dans Developers Console afin de pouvoir utiliser les ressources Google Cloud puis activer l'API Cloud Spanner.

15d0ef27a8fbab27.png

Suivre cet atelier de programmation ne devrait pas vous coûter plus d'un euro. Cependant, cela peut s'avérer plus coûteux si vous décidez d'utiliser davantage de ressources ou si vous n'interrompez pas les ressources (voir la section "Effectuer un nettoyage" à la fin du présent document). Les tarifs de Google Cloud Spanner sont décrits sur cette page.

Les nouveaux utilisateurs de Google Cloud Platform peuvent bénéficier d'un Essai gratuit avec 300 $ de crédits afin de suivre gratuitement le présent atelier.

Configuration de Google Cloud Shell

Bien que Google Cloud et Spanner puissent être utilisés à distance depuis votre ordinateur portable, nous allons utiliser Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Cette machine virtuelle basée sur Debian contient tous les outils de développement dont vous aurez besoin. Elle intègre un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Cela signifie que tout ce dont vous avez besoin pour cet atelier de programmation est un navigateur (oui, tout fonctionne sur un Chromebook).

  1. Pour activer Cloud Shell à partir de Cloud Console, cliquez simplement sur Activer Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (l'opération de provisionnement et la connexion à l'environnement ne devraient prendre que quelques minutes).

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

Capture d'écran du 2017-06-14 à 10.13.43 PM.png

Une fois connecté à Cloud Shell, vous êtes normalement déjà authentifié et le projet PROJECT_ID est sélectionné :

gcloud auth list

Résultat de la commande

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

Résultat de la commande

[core]
project = <PROJECT_ID>

Si, pour une raison quelconque, le projet n'est pas défini, exécutez simplement la commande suivante :

gcloud config set project <PROJECT_ID>

Vous recherchez votre PROJECT_ID ? Vérifiez l'ID que vous avez utilisé pendant les étapes de configuration ou recherchez-le dans le tableau de bord Cloud Console :

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Par défaut, Cloud Shell définit certaines variables d'environnement qui pourront s'avérer utiles pour exécuter certaines commandes dans le futur.

echo $GOOGLE_CLOUD_PROJECT

Résultat de la commande

<PROJECT_ID>
  1. Pour finir, définissez la configuration du projet et de la zone par défaut :
gcloud config set compute/zone us-central1-f

Vous pouvez choisir parmi différentes zones. Pour en savoir plus, consultez la page Régions et zones.

Résumé

Cette étape consiste à configurer votre environnement.

Étapes suivantes

Vous allez maintenant configurer une instance Cloud Spanner.

Dans cette étape, nous allons configurer notre instance Cloud Spanner pour cet atelier de programmation. Recherchez l'entrée Spanner 1a6580bd3d3e6783.pngdans le menu principal en haut à gauche 3129589f7bc9e5ce.png ou recherchez Spanner en appuyant sur "/" et saisissez "Spanner"

36e52f8df8e13b99.png

Cliquez ensuite sur 95269e75bc8c3e4d.png et remplissez le formulaire en renseignant le nom d'instance cloudspanner-leaderboard, en choisissant une configuration (sélectionnez une instance régionale), puis en définissant le nombre de nœuds (pour le présent atelier, nous n'aurons besoin que d'un seul nœud). Pour les instances en production et pour bénéficier du contrat de niveau de service de Cloud Spanner, vous devez exécuter au moins trois nœuds dans votre instance Cloud Spanner.

Dernière étape mais pas des moindres, cliquez sur "Créer". L'instance Cloud Spanner sera prête en quelques secondes.

dceb68e9ed3801e8.png

Pour l'étape suivante, nous allons utiliser la bibliothèque cliente Java afin de créer une base de données et un schéma dans notre nouvelle instance.

Au cours de cette étape, nous allons créer notre exemple de base de données et notre schéma.

Utilisons la bibliothèque cliente Java pour créer deux tables : une table "Players" (Joueurs) pour stocker les informations concernant les joueurs et une table "Scores" pour stocker les scores des joueurs. Pour ce faire, nous allons vous indiquer comment créer une application de console Java dans Cloud Shell.

Commencez par cloner l'exemple de code du présent atelier de programmation à partir de GitHub en saisissant la commande suivante dans Cloud Shell :

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

Accédez ensuite au répertoire "applications" dans lequel vous allez créer votre application.

cd java-docs-samples/spanner/leaderboard

L'ensemble du code requis pour cet atelier de programmation se trouve dans le répertoire java-docs-samples/spanner/leaderboard/complete existant en tant qu'application C# exécutable Leaderboard, pour vous servir de référence tout au long de cet atelier. Nous allons créer un répertoire et créer une copie de l'application de classement étape par étape.

Créez un répertoire nommé "codelab" pour l'application et accédez-y avec la commande suivante :

mkdir codelab && cd $_

Créez une application Java de base nommée "Leaderboard" à l'aide de la commande Maven (mvn) suivante :

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

Cette commande crée une application de console simple composée de deux fichiers principaux : le fichier de configuration de l'application Maven pom.xml et le fichier d'application Java App.java.

Accédez ensuite au répertoire "leaderboard" qui vient d'être créé et répertoriez-en le contenu :

cd leaderboard && ls

Vous devriez voir le fichier pom.xml et le répertoire src :

pom.xml  src

Vous allez maintenant mettre à jour cette application de console en modifiant App.java pour utiliser la bibliothèque cliente Java Spanner afin de créer un classement composé de deux tables, "Players" (Joueurs) et "Scores". Vous pouvez faire tout cela directement depuis l'éditeur Cloud Shell :

Ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :

73cf70e05f653ca.png

Ouvrez le fichier pom.xml dans le dossier "leaderboard". Ouvrez le fichier pom.xml situé dans le dossier java-docs-samples\ spanner\leaderboard\codelab\leaderboard. Ce fichier configure le système de compilation Maven afin de compiler notre application dans un fichier jar en incluant toutes nos dépendances.

Ajoutez la nouvelle section de gestion des dépendances ci-dessous directement sous l'élément </properties> existant :

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

Ajoutez également une nouvelle dépendance dans la section <dependencies> existante afin d'ajouter la bibliothèque cliente Java Cloud Spanner à l'application.

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

Remplacez ensuite la section <build> existante du fichier pom.xml par la section <build> ci-dessous :

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

Enregistrez les modifications apportées au fichier pom.xml en sélectionnant "Enregistrer" dans le menu "Fichier" de l'éditeur Cloud Shell ou en appuyant simultanément sur les touches "Ctrl" et "S".

Ensuite, ouvrez le fichier App.java situé dans le dossier src/main/java/com/google/codelabs/ avec l'éditeur Cloud Shell. Remplacez le code existant du fichier par le code requis pour créer la base de données leaderboard et les tables Players et Scores en collant le code Java suivant dans le fichier 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");
  }
}

Enregistrez les modifications apportées au fichier App.java en sélectionnant "Enregistrer" dans le menu "Fichier " de l'éditeur Cloud Shell.

Vous pouvez utiliser le fichier App.java situé dans le répertoire java-docs-samples/spanner/leaderboard/step4/src comme référence de ce à quoi doit ressembler votre fichier App.java après l'ajout du code pour activer la commande create.

Pour créer votre application, exécutez le package mvn à partir du répertoire où se trouve votre fichier pom.xml :

mvn package

Une fois votre fichier JAR Java créé, exécutez l'application obtenue dans Cloud Shell à l'aide de la commande suivante :

java -jar target/leaderboard.jar

Vous devriez voir une sortie semblable à ce qui suit.

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.

À partir de cette réponse, nous pouvons voir qu'il s'agit bien de l'application Leaderboard qui possède actuellement une seule commande possible : create. Nous constatons que les arguments attendus de la commande create sont les suivants : ID d'instance et ID de base de données.

Exécutez maintenant la commande suivante :

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

Après quelques secondes, une réponse de ce type doit s'afficher :

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

Dans la section Cloud Spanner de Cloud Console, vous devriez voir votre nouvelle base de données et vos tables dans le menu de gauche.

ba9008bb84cb90b0.png

À l'étape suivante, nous mettrons à jour notre application afin de charger des données dans votre nouvelle base de données.

Nous disposons à présent d'une base de données appelée leaderboard et contenant deux tables, Players et Scores. Utilisons maintenant la bibliothèque cliente Java pour remplir la table Players avec des joueurs et la table Scores avec des scores aléatoires pour chaque joueur.

S'il n'est pas déjà ouvert, ouvrez l'éditeur Cloud Shell en cliquant sur l'icône en surbrillance ci-dessous :

ef49fcbaaed19024.png

Ensuite, modifiez le fichier App.java dans l'éditeur Cloud Shell afin d'ajouter une commande insert pouvant être utilisée pour insérer 100 joueurs dans la table Players, ou pour insérer quatre scores aléatoires dans le fichier Scores pour chaque joueur dans la table Players.

Commencez par mettre à jour la section imports en haut du fichier en la remplaçant de sorte à obtenir le résultat ci-dessous :

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;

Ajoutez ensuite les méthodes "insert", "insertPlayers" et "insertScores" ci-dessous sous la méthode create() existante et au-dessus de la méthode printUsageAndExit() existante :

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

Pour que la commande insert fonctionne, ajoutez le code suivant à la méthode "main" de votre application dans l'instruction switch (command) :

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

Une fois l'opération terminée, l'instruction switch (command) doit ressembler à ceci :

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

Pour finaliser l'ajout de la fonctionnalité "insert" à votre application, la dernière étape consiste à ajouter un texte d'aide pour la commande "insert" dans la méthode printUsageAndExit(). Ajoutez le texte d'aide suivant à la méthode printUsageAndExit() afin d'inclure le texte d'aide pour les commandes d'insertion :

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

Enregistrez les modifications apportées au fichier App.java en sélectionnant "Enregistrer" dans le menu "Fichier " de l'éditeur Cloud Shell.

Vous pouvez utiliser le fichier App.java du répertoire java-docs-samples/spanner/leaderboard/step5/src comme référence de ce à quoi doit ressembler votre fichier App.java après l'ajout du code pour activer la commande insert.

Recompilons et exécutez l'application pour vérifier que la nouvelle commande insert est bien incluse dans la liste des commandes possibles.

Pour compiler votre application, exécutez mvn package à partir du répertoire où se trouve le fichier pom.xml :

mvn package

Une fois votre fichier JAR Java créé, exécutez la commande suivante :

java -jar target/leaderboard.jar

La commande insert devrait maintenant être incluse dans la sortie par défaut de l'application :

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.

Vous pouvez voir dans la réponse qu'en plus de l'ID d'instance et de l'ID de base de données, un autre argument dont la valeur peut être "players" ou "scores" est apparu.

Nous allons maintenant exécuter la commande insert avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande create en ajoutant "players" comme argument "type of insert" supplémentaire.

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

Après quelques secondes, une réponse de ce type doit s'afficher :

Done inserting player records...

Utilisons maintenant la bibliothèque cliente Java pour remplir notre table Scores avec quatre scores et horodatages aléatoires pour chaque joueur dans la table Players.

La colonne Timestamp de la table Scores a été définie comme colonne d'horodatage de commit ("commit timestamp") via l'instruction SQL suivante, exécutée lors de l'exécution de la commande 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

Notez l'attribut OPTIONS(allow_commit_timestamp=true). Cela fait de Timestamp une colonne d'horodatage de commit qu'il est possible de renseigner avec l'horodatage de transaction exact des opérations INSERT et UPDATE sur une ligne de table donnée.

Vous pouvez également insérer vos propres valeurs d'horodatage dans une colonne "commit timestamp" à condition d'utiliser un horodatage situé dans le passé, ce qui est exactement ce que nous avons fait dans le présent atelier de programmation.

Nous allons maintenant exécuter la commande insert avec les mêmes valeurs d'argument que celles utilisées lors de l'appel de la commande create en ajoutant "scores" comme argument "type of insert" supplémentaire.

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

Après quelques secondes, une réponse de ce type doit s'afficher :

Done inserting score records...

L'exécution de insert avec scores spécifié comme argument "type of insert" supplémentaire appelle la méthode insertScores. Celle-ci utilise les extraits de code ci-dessous pour insérer un horodatage généré de manière aléatoire avec une date et une heure dans le passé :

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

Pour automatiquement remplir la colonne Timestamp avec l'horodatage correspondant à l'heure exacte de la transaction "Insert", vous pouvez insérer la constante Java Value.COMMIT_TIMESTAMP comme dans l'extrait de code suivant :

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

Maintenant que le chargement des données est terminé, vérifions les valeurs que nous venons d'écrire dans les nouvelles tables. Sélectionnez d'abord la base de données leaderboard, puis la table Players. Cliquez sur l'onglet Data. Vous devriez voir des données dans les colonnes PlayerId et PlayerName de la table.

7bc2c96293c31c49.png

Vérifions maintenant le tableau des scores contient bien des données en cliquant sur le tableau Scores et en sélectionnant l'onglet Data. Vous devriez voir des données dans les colonnes PlayerId, Timestamp et Score de la table.

d8a4ee4f13244c19.png

Bravo ! Mettons à jour notre application pour exécuter des requêtes qui nous permettront de créer un classement de jeu.

Maintenant que nous avons configuré notre base de données et chargé des informations dans nos tables, nous allons créer un classement à l'aide de ces données. Pour ce faire, nous devons répondre aux quatre questions suivantes :

  1. Quels joueurs sont les "Top 10" joueurs de tous les temps ?
  2. Quels joueurs sont les "Top 10" joueurs de l'année ?
  3. Quels joueurs sont les "Top 10" joueurs du mois ?
  4. Quels joueurs sont les "Top 10" joueurs de la semaine ?

Mettons à jour notre application pour exécuter les requêtes SQL qui répondent à ces questions.

Nous allons ajouter une commande query afin d'exécuter les requêtes permettant de répondre à ces questions en générant les informations requises pour notre classement.

Modifiez le fichier App.java dans l'éditeur Cloud Shell afin de mettre à jour l'application et d'ajouter une commande query. La commande query est composée de deux méthodes query, l'une n'utilisant qu'un argument DatabaseClient et l'autre acceptant un argument timespan supplémentaire pour faciliter le filtrage des résultats en fonction d'une période spécifiée en nombre d'heures.

Ajoutez les deux méthodes query suivantes sous la méthode insertScores() existante et au-dessus de la méthode printUsageAndExit() existante :

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

Pour que la commande query fonctionne, ajoutez le code suivant à l'instruction switch(command) dans la méthode "main" de votre application :

        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;

La dernière étape pour finaliser l'ajout de la fonctionnalité "query" à votre application consiste à ajouter du texte d'aide pour la commande "query" dans la méthode printUsageAndExit(). Ajoutez les lignes de code suivantes à la méthode printUsageAndExit() afin d'inclure du texte d'aide pour la commande "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");

Enregistrez les modifications apportées au fichier App.java en sélectionnant "Enregistrer" dans le menu "Fichier " de l'éditeur Cloud Shell.

Vous pouvez utiliser le fichier App.java du répertoire dotnet-docs-samples/applications/leaderboard/step6/src comme référence de ce à quoi doit ressembler votre fichier App.java après l'ajout du code pour activer la commande query.

Pour compiler votre application, exécutez mvn package à partir du répertoire où se trouve le fichier pom.xml :

mvn package

Nous allons maintenant exécuter l'application pour confirmer que la nouvelle commande query est bien incluse dans la liste des commandes possibles de l'application. Exécutez la commande suivante :

java -jar target/leaderboard.jar

La commande query devrait maintenant être incluse dans la sortie par défaut de l'application en tant que nouvelle option :

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.

Vous pouvez voir dans la réponse qu'en plus des arguments d'ID d'instance et d'ID de base de données, la commande query accepte un argument facultatif "timespan" (exprimé en heures) pour filtrer les enregistrements en fonction de leur valeur dans la colonne Timestamp du tableau Scores. L'argument "timespan" étant facultatif, aucun filtrage par horodatage ne sera appliqué si l'argument n'est pas inclus. Nous pouvons donc utiliser la commande query sans argument "timespan" pour obtenir la liste des joueurs du "Top 10" sur l'intégralité des données disponibles.

Exécutons la commande query sans spécifier de valeur "timespan", en utilisant les mêmes valeurs d'argument que celles utilisées lors de l'exécution de la commande create.

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

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" :

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

Exécutez maintenant la commande query avec les arguments nécessaires pour obtenir les joueurs du "Top 10" de l'année en spécifiant une valeur "timespan" égale au nombre d'heures dans une année (8760).

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

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de l'année :

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

À présent, vous allez exécuter la commande query pour obtenir les joueurs du "Top 10 " du mois en spécifiant une valeur "timespan" égale au nombre d'heures dans un mois (730).

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

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" du mois :

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

À présent, exécutez la commande query pour obtenir les joueurs du "Top 10 " de la semaine en spécifiant une valeur "timespan" égale au nombre d'heures dans une semaine (168).

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

Vous devriez voir une réponse similaire à celle ci-dessous et incluant les joueurs du "Top 10" de la semaine :

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

Beau travail !

À mesure que vous ajoutez des enregistrements, Cloud Spanner fait évoluer votre base de données en fonction de vos besoins. Quel que soit le volume de votre base de données, le classement de votre jeu peut continuer d'évoluer avec précision grâce à Cloud Spanner et sa technologie Truetime.

Après s'être amusé avec Spanner, il faut maintenant nettoyer notre aire de jeux pour ne gaspiller de préciseuses ressources et donc d'argent. Heureusement, il s'agit d'une étape très facile puisqu'il vous suffit d'accéder à la section Cloud Spanner de Cloud Console et de supprimer l'instance que nous avons créée à l'étape "Configurer une instance Cloud Spanner".

Points abordés :

  • Instances, bases de données et schéma de table Google Cloud Spanner pour un classement.
  • Comment créer une application de console Java.
  • Comment créer une base de données et des tables Spanner à l'aide de la bibliothèque cliente Java.
  • Comment charger des données dans une base de données Spanner à l'aide de la bibliothèque cliente Java.
  • Comment interroger les résultats du "Top 10" à partir de vos données à l'aide des horodatages de commit Spanner et de la bibliothèque cliente Java.

Prochaines étapes :

Votre avis nous intéresse !

  • Veuillez prendre quelques minutes pour répondre à notre courte enquête.