Cloud Spanner: crie um placar de jogos com Java

O Google Cloud Spanner é um serviço de banco de dados relacional horizontalmente escalonável e distribuído globalmente que fornece transações ACID e semântica SQL sem deixar de oferecer desempenho e alta disponibilidade.

Neste laboratório, você aprenderá a configurar uma instância do Cloud Spanner. Você conhecerá as etapas para criar um banco de dados e um esquema que possam ser usados em um placar de jogos. Primeiro, crie uma tabela de jogadores para armazenar informações sobre o jogador e uma tabela de pontuações para armazenar as pontuações dos jogadores.

Em seguida, preencha as tabelas com dados de amostra. Para concluir o laboratório, execute algumas consultas de exemplo do Top 10 e, por fim, exclua a instância para liberar recursos.

O que você aprenderá

  • Como configurar uma instância do Cloud Spanner.
  • Como criar um banco de dados e tabelas.
  • Como usar uma coluna de carimbo de data/hora de confirmação.
  • Como carregar dados em uma tabela de banco de dados do Cloud Spanner com carimbos de data/hora.
  • Como consultar o banco de dados do Cloud Spanner.
  • Como excluir uma instância do Cloud Spanner.

Pré-requisitos

Como você usará este tutorial?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com o Google Cloud Platform?

Iniciante Intermediário Proficiente

Configuração de ambiente personalizada

Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma. Faça login no Console do Google Cloud Platform ( console.cloud.google.com) e crie um novo projeto.

Se você já tiver um projeto, clique no menu suspenso de seleção no canto superior esquerdo do console:

6c9406d9b014760.png

e clique no botão "NEW PROJECT" na caixa de diálogo exibida para criar um novo projeto:

f708315ae07353d0.png

Se você ainda não tiver um projeto, uma caixa de diálogo como esta será exibida para criar seu primeiro:

870a3cbd6541ee86.png

A caixa de diálogo de criação de projeto subsequente permite que você insira os detalhes do novo projeto:

6a92c57d3250a4b3.png

Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID.

Em seguida, será preciso ativar o faturamento no Developers Console para usar os recursos do Google Cloud e ativar a API Cloud Spanner, caso ainda não tenha feito isso.

15d0ef27a8fbab27.png

A execução por meio deste codelab terá um custo baixo, mas poderá ser mais se você decidir usar mais recursos ou se deixá-los em execução. Consulte a seção "limpeza" no final deste documento. Os preços do Google Cloud Spanner estão documentados neste link.

Novos usuários do Google Cloud Platform estão qualificados para uma avaliação gratuita de US$ 300, o que torna este codelab totalmente gratuito.

Configuração do Google Cloud Shell

Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

O Cloud Shell é uma máquina virtual com base em Debian que contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Isso significa que tudo que você precisa para este codelab é um navegador (sim, funciona em um Chromebook).

  1. Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A. Leva apenas alguns instantes para provisionar e se conectar ao ambiente.

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

Screen Shot 2017-06-14 às 10.13.43 PM.png

Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto estará configurado com seu PROJECT_ID.

gcloud auth list

Resposta ao comando

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

Resposta ao comando

[core]
project = <PROJECT_ID>

Se, por algum motivo, o projeto não estiver definido, basta emitir o seguinte comando:

gcloud config set project <PROJECT_ID>

Quer encontrar seu PROJECT_ID? Veja qual ID você usou nas etapas de configuração ou procure-o no painel do Console do Cloud:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

O Cloud Shell também define algumas variáveis de ambiente por padrão, o que pode ser útil ao executar comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resposta ao comando

<PROJECT_ID>
  1. Defina a zona padrão e a configuração do projeto:
gcloud config set compute/zone us-central1-f

É possível escolher uma variedade de zonas diferentes. Para mais informações, consulte Regiões e zonas.

Resumo

Nesta etapa, você configurará seu ambiente.

A seguir

Agora configure uma instância do Cloud Spanner.

Nesta etapa, configuramos a instância do Cloud Spanner para este codelab. Pesquise a entrada do Spanner 1a6580bd3d3e6783.png no menu de navegação superior esquerdo 3129589f7bc9e5ce.png ou procure o Spanner pressionando "/" e digite "Spanner".

36e52f8df8e13b99.png

Depois, clique em 95269e75bc8c3e4d.png e preencha o formulário inserindo o nome da instância cloudspanner-leaderboard da sua instância, escolhendo uma configuração (selecione uma instância regional) e defina o número de nós para este codelab só precisaremos de um nó. Para instâncias de produção e se qualificar para o SLA do Cloud Spanner, você precisará executar três ou mais nós na sua instância do Cloud Spanner.

Por fim, mas não menos importante, clique em "Criar" e, em segundos, uma instância do Cloud Spanner está à sua disposição.

dceb68e9ed3801e8.png

Na próxima etapa, usaremos a biblioteca do cliente Java para criar um banco de dados e esquema em nossa nova instância.

Nesta etapa, criaremos nosso banco de dados e esquema de amostra.

Vamos usar a biblioteca de cliente Java para criar duas tabelas, uma tabela de jogadores para informações do jogador e uma tabela de pontuações para armazenar pontuações de jogadores. Para isso, mostraremos as etapas para criar um aplicativo de console Java no Cloud Shell.

Primeiro, clone o código de amostra do codelab digitando o seguinte comando no Cloud Shell:

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

Em seguida, mude para o diretório "applications" onde você criará o aplicativo.

cd java-docs-samples/spanner/leaderboard

Todo o código necessário para este codelab está localizado no diretório java-docs-samples/spanner/leaderboard/complete existente como um aplicativo C# executável como Leaderboard para servir como referência enquanto você avança no codelab. Criaremos um novo diretório e criaremos uma cópia do aplicativo Leaderboard em etapas.

Crie um novo diretório chamado "codelab" para o aplicativo e altere o diretório nele para ele usando o seguinte comando:

mkdir codelab && cd $_

Crie um novo aplicativo Java básico chamado "Leaderboard" usando o seguinte comando do Maven (mvn):

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

Esse comando cria um aplicativo de console simples com dois arquivos principais, o arquivo de configuração do aplicativo Maven pom.xml e o arquivo de aplicativo Java App.java.

Depois, altere o diretório para o diretório do placar que foi criado e liste o conteúdo:

cd leaderboard && ls

Você verá o arquivo pom.xml e o diretório src listados:

pom.xml  src

Agora, vamos atualizar este aplicativo de console editando o App.java para usar a biblioteca de cliente Java Spanner para criar um placar de formação de duas tabelas; Jogadores e pontuações. É possível fazer isso diretamente no editor do Cloud Shell:

Abra o editor do Cloud Shell clicando no ícone destacado abaixo:

73cf70e05f653ca.png

Abra o pom.xml na pasta do placar. Abra o arquivo pom.xml localizado na pasta java-docs-samples\ spanner\leaderboard\codelab\leaderboard. Esse arquivo configura o sistema de compilação do Maven para criar nosso aplicativo em um jar, incluindo todas as nossas dependências.

Adicione a nova seção de gerenciamento de dependências a seguir logo abaixo do elemento </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>

Adicione também uma nova dependência na seção <dependencies> existente, que adicionará a biblioteca de cliente Java do Cloud Spanner ao aplicativo.

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

Em seguida, substitua a seção <build> do arquivo pom.xml pela seção <build> a seguir:

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

Salve as alterações feitas no arquivo pom.xml selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell ou pressionando as teclas de atalho "Ctrl" e "S" juntas.

Em seguida, abra o arquivo App.java no editor do Cloud Shell localizado na pasta src/main/java/com/google/codelabs/. Substitua o código existente do arquivo pelo código necessário para criar o banco de dados leaderboard e as tabelas Players e Scores colando o seguinte código Java no arquivo 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");
  }
}

Salve as alterações feitas no arquivo App.java selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.

Use o arquivo App.java no diretório java-docs-samples/spanner/leaderboard/step4/src para ver um exemplo de como seu arquivo App.java deve ficar depois de você adicionar o código para ativar o comando create.

Para criar seu app, execute o pacote mvn no diretório em que seu pom.xml está localizado:

mvn package

Após criar o arquivo jar Java, execute o aplicativo resultante no Cloud Shell inserindo o seguinte comando:

java -jar target/leaderboard.jar

O resultado será assim:

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.

Nesta resposta, vemos que este é o aplicativo Leaderboard que atualmente tem um comando possível: create. Os argumentos esperados do comando create são o código da instância e do banco de dados.

Agora, execute o seguinte comando.

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

Após alguns segundos, será exibida uma resposta semelhante a esta:

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

Na seção Cloud Spanner do Console do Cloud, você verá o novo banco de dados e as tabelas que aparecem no menu do lado esquerdo.

ba9008bb84cb90b0.png

Na próxima etapa, atualizaremos o nosso aplicativo para carregar alguns dados no novo banco de dados.

Agora temos um banco de dados chamado leaderboard contendo duas tabelas; Players e Scores. Agora, usaremos a biblioteca de cliente Java para preencher a tabela Players com os jogadores e nossa tabela Scores com pontuações aleatórias para cada jogador.

Se ele ainda não estiver aberto, clique no ícone destacado abaixo para abrir o editor do Cloud Shell:

ef49fcbaaed19024.png

Em seguida, edite o arquivo App.java no editor do Cloud Shell para adicionar um comando insert que possa ser usado para inserir 100 jogadores na tabela Players ou para inserir quatro pontuações aleatórias em Scores tabela para cada jogador na tabela Players.

Primeiro, atualize a seção imports na parte superior do arquivo do app, substituindo o que está lá, de modo que, quando você terminar, o resultado será semelhante a este:

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;

Em seguida, adicione os seguintes métodos insert, insertPlayers e insertScores abaixo do método create() existente e acima do método printUsageAndExit() existente:

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

Em seguida, para fazer o comando insert funcionar, adicione o seguinte código ao método "main" do app na instrução switch (command) :

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

Depois de terminar, a instrução switch (command) será semelhante a esta:

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

A etapa final para concluir a adição da funcionalidade "inserir" ao seu aplicativo é adicionar o texto de ajuda ao comando "inserir" ao método printUsageAndExit(). Adicione as seguintes linhas de código ao método printUsageAndExit() para incluir o texto de ajuda para o comando "insert":

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

Salve as alterações feitas no arquivo App.java selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.

Use o arquivo App.java no diretório java-docs-samples/spanner/leaderboard/step5/src para ver um exemplo de como seu arquivo App.java deve ficar depois de você adicionar o código para ativar o comando insert.

Agora, vamos recriar e executar o app para confirmar que o novo comando insert está incluído na lista de comandos possíveis do app.

Para criar seu aplicativo, execute mvn package no diretório em que seu pom.xml está localizado:

mvn package

Depois de criar o arquivo jar Java, execute o seguinte comando:

java -jar target/leaderboard.jar

Você verá o comando insert agora incluído na saída padrão do app:

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.

É possível ver da resposta que, além do ID da instância e do ID do banco de dados, há outro argumento que pode ter um valor "players" ou "scores".

Agora, vamos executar o comando insert com os mesmos valores de argumento que usamos quando chamamos o comando create, adicionando "players" como o argumento adicional "tipo de inserção".

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

Após alguns segundos, será exibida uma resposta semelhante a esta:

Done inserting player records...

Agora, vamos usar a biblioteca de cliente Java para preencher nossa tabela Scores com quatro pontuações aleatórias com carimbos de data/hora de cada jogador na tabela Players.

A coluna Timestamp da tabela Scores foi definida como uma coluna de "carimbo de data/hora de confirmação" por meio da seguinte instrução SQL que foi executada quando executamos o comando create anteriormente:

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

Observe o atributo OPTIONS(allow_commit_timestamp=true). Isso torna Timestamp uma coluna de "timestamp de confirmação" e permite que ela seja preenchida automaticamente com o carimbo de data/hora exato da transação para operações INSERT e UPDATE em uma determinada linha da tabela.

Também é possível inserir seus próprios valores de carimbo de data/hora em uma coluna de "carimbo de data/hora de confirmação", desde que você insira um carimbo de data/hora com um valor que esteja no passado, que é o que vamos fazer para este fim de codelab.

Agora, vamos executar o comando insert com os mesmos valores de argumento que usamos quando chamamos o comando create adicionando "scores" como o argumento adicional "type of insert".

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

Após alguns segundos, será exibida uma resposta semelhante a esta:

Done inserting score records...

Executar insert com o "tipo de inserção" especificado como scores chama o método insertScores, que usa os seguintes snippets de código para inserir um carimbo de data/hora gerado aleatoriamente com uma data e hora no passado:

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

Para preencher automaticamente a coluna Timestamp com o carimbo de data/hora do momento exato em que a transação "Inserir" ocorre, insira a constante Java Value.COMMIT_TIMESTAMP como no snippet de código a seguir:

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

Agora que concluímos o carregamento de dados, vamos verificar os valores que acabamos de gravar nas nossas novas tabelas. Primeiro, selecione o banco de dados leaderboard e depois a tabela Players. Clique na guia Data. Você verá dados nas colunas PlayerId e PlayerName da tabela.

7bc2c96293c31c49.png

Em seguida, para verificar se a tabela "Scores" também tem dados, clique na tabela Scores e selecione a guia Data. Você deve ter dados nas colunas PlayerId, Timestamp e Score da tabela.

d8a4ee4f13244c19.png

Parabéns! Vamos atualizar nosso app para executar algumas consultas que podemos usar para criar um placar de jogos.

Agora que configuramos o banco de dados e carregamos as informações nas tabelas, vamos criar um placar usando esses dados. Para fazer isso, precisamos responder a estas quatro perguntas:

  1. Quais jogadores são o "Top 10" de todos os tempos?
  2. Quais jogadores são os "Top 10" do ano?
  3. Quais jogadores são o "Top 10" do mês?
  4. Quais jogadores são os "Top 10" da semana?

Vamos atualizar nosso aplicativo para executar as consultas SQL que responderão a essas perguntas.

Adicionaremos um comando query que fornecerá uma forma de executar as consultas de forma a responder às perguntas que produzirão as informações necessárias para nosso placar.

Edite o arquivo App.java no editor do Cloud Shell para atualizar o app e adicionar um comando query. O comando query é composto por dois métodos query, um que usa apenas um argumento DatabaseClient e outro que usa um argumento timespan extra para facilitar a filtragem de resultados por um período especificado em horas.

Adicione os dois métodos query a seguir abaixo do método insertScores() existente e acima do método printUsageAndExit() atual:

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

Em seguida, para fazer o comando query funcionar, adicione o seguinte código à instrução switch(command) no método "main" do seu app:

        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;

A etapa final para concluir a adição da funcionalidade "query" ao seu aplicativo é adicionar o texto de ajuda ao comando "query" ao método printUsageAndExit(). Adicione as seguintes linhas de código ao método printUsageAndExit() para incluir o texto de ajuda no comando "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");

Salve as alterações feitas no arquivo App.java selecionando "Salvar" no menu "Arquivo" do editor do Cloud Shell.

Use o arquivo App.java no diretório dotnet-docs-samples/applications/leaderboard/step6/src para ver um exemplo de como seu arquivo App.java deve ficar depois de você adicionar o código para ativar o comando query.

Para criar seu aplicativo, execute mvn package no diretório em que seu pom.xml está localizado:

mvn package

Agora, vamos executar o aplicativo para confirmar se o novo comando query está incluído na lista de comandos possíveis do aplicativo. Execute este comando:

java -jar target/leaderboard.jar

Você verá o comando query agora incluído na saída padrão do app como uma nova opção de comando:

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.

É possível ver na resposta que, além dos argumentos do código da instância e do banco de dados, o comando query nos permite especificar um período opcional em número de horas a ser usado para filtrar registros com base no valor deles na tabela Scores Timestamp. Como o argumento de tempo é opcional, significa que, se um argumento de tempo não for incluído, nenhum registro será filtrado por carimbos de data/hora. Assim, podemos usar o comando query sem um valor de "período" para acessar uma lista dos nossos "Top 10" todos os tempos.

Execute o comando query sem especificar um "timespan", usando os mesmos valores de argumento que usamos quando executamos o comando create.

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

Você deverá ver uma resposta que inclui os players "Top 10" de todos os tempos, como a seguinte:

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

Agora, vamos executar o comando query com os argumentos necessários para consultar os "Top 10" do ano especificando um "timespan" igual ao número de horas em um ano que é 8760.

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

Você deverá ver uma resposta que inclui os "Top 10" do ano, como a seguir:

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

Agora, vamos executar o comando query para consultar os players "Top 10" do mês, especificando um "timespan" igual ao número de horas em um mês que é 730.

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

Você verá uma resposta que inclui os "Top 10" do mês, como a seguinte:

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

Agora execute o comando query para consultar os "Top 10" da semana, especificando um "timespan" igual ao número de horas em uma semana de 168.

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

Você verá uma resposta que inclui os jogadores "Top 10" da semana, como a seguinte:

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

Ótimo trabalho!

Agora, conforme você adicionar registros, o Cloud Spanner fará o escalonamento do seu banco de dados para o tamanho que for necessário. Não importa o quanto seu banco de dados aumente, o placar do jogo pode continuar a ser dimensionado com a precisão do Cloud Spanner e da tecnologia Truetime.

Depois de se divertir com o Spanner, precisamos limpar nosso playground, economizando recursos preciosos e dinheiro. Felizmente, esta é uma etapa fácil, basta acessar a seção Cloud Spanner do Console do Cloud e excluir a instância que criamos na etapa do codelab chamada "Setup a Cloud Spanner Instance".

O que vimos:

  • Instâncias, bancos de dados e esquema de tabelas do Google Cloud Spanner para um placar
  • Como criar um aplicativo do console Java
  • Como criar um banco de dados do Spanner e tabelas usando a biblioteca de cliente do Java
  • Como carregar dados em um banco de dados do Spanner usando a biblioteca de cliente Java
  • Como consultar os "Top 10" dos seus dados usando carimbos de data/hora de confirmação do Spanner e a biblioteca de cliente Java

Próximas etapas:

Envie um feedback

  • Reserve um momento para completar nossa pesquisa curta