Cloud Spanner: Crea una tabla de clasificación de videojuegos con Java

Google Cloud Spanner es un servicio de base de datos relacional, distribuido de forma global y escalable de forma vertical completamente administrado que proporciona transacciones ACID y semántica de SQL sin renunciar al rendimiento y a la alta disponibilidad.

En este lab, aprenderás a configurar una instancia de Cloud Spanner. Completarás los pasos para crear una base de datos y un esquema que se puedan usar en una tabla de clasificación de videojuegos. Comenzarás por crear una tabla de jugadores a fin de almacenar su información y una tabla de puntuaciones para almacenar sus puntuaciones.

A continuación, propagarás las tablas con datos de muestra. Por último, finalizarás el lab mediante la ejecución de las diez consultas de muestra principales y borrarás la instancia para liberar recursos.

Qué aprenderá

  • Cómo configurar una instancia de Cloud Spanner.
  • Cómo crear una base de datos y tablas.
  • Cómo usar una columna de marca de tiempo de confirmación
  • Cómo cargar datos en tu tabla de base de datos de Cloud Spanner con marcas de tiempo
  • Cómo consultar tu base de datos de Cloud Spanner.
  • Cómo borrar tu instancia de Cloud Spanner.

Qué necesitarás

¿Cómo usarás este instructivo?

Leer Leer y completar los ejercicios

¿Cómo calificarías tu experiencia con Google Cloud Platform?

Principiante Intermedio Avanzado

Configuración del entorno a su propio ritmo

Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una. Accede a Google Cloud Platform Console (console.cloud.google.com) y crea un proyecto nuevo.

Si ya tienes un proyecto, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la Console:

6c9406d9b014760.png

y haz clic en el botón “PROYECTO NUEVO” en el diálogo resultante para crear un proyecto nuevo:

f708315ae07353d0.png

Si aún no tienes un proyecto, deberías ver un cuadro de diálogo como este para crear el primero:

870a3cbd6541ee86.png

El cuadro de diálogo de creación posterior del proyecto te permite ingresar los detalles de tu proyecto nuevo:

6a92c57d3250a4b3.png

Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

A continuación, si aún no lo has hecho, deberás habilitar la facturación en Developers Console para usar los recursos de Google Cloud y habilitar la API de Cloud Spanner.

15d0ef27a8fbab27.png

Ejecutar este codelab debería costar solo unos pocos dólares, pero su costo podría aumentar si decides usar más recursos o si los dejas en ejecución (consulta la sección “Limpiar” al final de este documento). Los precios de Google Cloud Spanner se documentan aquí.

Los usuarios nuevos de Google Cloud Platform están aptas para obtener una prueba gratuita de $300, por lo que este codelab es completamente gratuito.

Configuración de Google Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Esta máquina virtual basada en Debian está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Esto significa que todo lo que necesitarás para este Codelab es un navegador (sí, funciona en una Chromebook).

  1. Para activar Cloud Shell desde Cloud Console, solo haz clic en Activar Cloud ShellgcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A (el aprovisionamiento y la conexión al entorno debería llevar solo unos minutos).

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

Captura de pantalla del 14 de junio de 2017 a las 10.13.43 p.m. .png

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu PROJECT_ID:

gcloud auth list

Resultado del comando

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

Resultado del comando

[core]
project = <PROJECT_ID>

Si, por algún motivo, el proyecto no está configurado, solo emite el siguiente comando:

gcloud config set project <PROJECT_ID>

Si no conoce su PROJECT_ID, Observa el ID que usaste en los pasos de configuración o búscalo en el panel de Cloud Console:

158fNPfwSxsFqz9YbtJVZes8viTS3d1bV4CVhij3XPxuzVFOtTObnwsphlm6lYGmgdMFwBJtc-FaLrZU7XHAg_ZYoCrgombMRR3h-eolLPcvO351c5iBv506B3ZwghZoiRg6cz23Qw

Cloud Shell también configura algunas variables de entorno de forma predeterminada, lo que puede resultar útil cuando ejecutas comandos futuros.

echo $GOOGLE_CLOUD_PROJECT

Resultado del comando

<PROJECT_ID>
  1. Establece la zona predeterminada y la configuración del proyecto.
gcloud config set compute/zone us-central1-f

Puedes elegir una variedad de zonas diferentes. Para obtener más información, consulta Regiones y zonas.

Resumen

En este paso, configurarás tu entorno.

A continuación

A continuación, configurarás una instancia de Cloud Spanner.

En este paso, configuraremos nuestra instancia de Cloud Spanner para este codelab. Busca la entrada de Spanner 1a6580bd3d3e6783.png en el menú de opciones superior izquierdo 3129589f7bc9e5ce.png o ingresa “/” y escribe “Spanner”

36e52f8df8e13b99.png

A continuación, haz clic en 95269e75bc8c3e4d.png y completa el formulario. Para ello, ingresa el nombre de la instancia cloudspanner-leaderboard de la instancia, elige una configuración (selecciona una instancia regional) y establece la cantidad de nodos. Para este codelab solo necesitaremos 1 nodo. Para las instancias de producción y a fin de cumplir con los requisitos del ANS de Cloud Spanner, debes ejecutar 3 o más nodos en tu instancia de Cloud Spanner.

Por último, pero no menos importante, haz clic en “Crear” y, en segundos, selecciona una instancia de Cloud Spanner a su disposición.

dceb68e9ed3801e8.png

En el siguiente paso, usaremos la biblioteca cliente de Java para crear una base de datos y un esquema en la instancia nueva.

En este paso, crearemos nuestra base de datos de muestra y un esquema.

Usemos la biblioteca cliente de Java para crear dos tablas, una tabla de jugadores con información sobre ellos y una tabla de puntuaciones a fin de almacenar las puntuaciones Para ello, analizaremos los pasos a fin de crear una aplicación de consola de Java en Cloud Shell.

Primero, clona el código de muestra para este codelab desde GitHub. Para ello, escribe el siguiente comando en Cloud Shell:

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

Luego, cambia el directorio al directorio “aplicaciones” en el que crearás tu aplicación.

cd java-docs-samples/spanner/leaderboard

Todo el código necesario para este codelab se encuentra en el directorio java-docs-samples/spanner/leaderboard/complete existente como una aplicación ejecutable C# llamada Leaderboard que sirve como referencia a medida que avanzas a través del codelab. Crearemos un directorio nuevo y compilaremos una copia de la aplicación Tablas de clasificación en etapas.

Crea un nuevo directorio llamado “codelab” para la aplicación y usa el comando de cambio de directorio a este con el siguiente comando:

mkdir codelab && cd $_

Crea una nueva aplicación básica de Java llamada “Tabla de clasificación” con el siguiente comando de Maven (mvn):

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

Este comando crea una aplicación simple de consola que consta de dos archivos principales: el archivo de configuración de la aplicación Maven pom.xml y el archivo de aplicación de Java App.java.

A continuación, usa el comando de cambio de directorio al directorio de la tabla de clasificación que acabas de crear y haz una lista de su contenido:

cd leaderboard && ls

Deberías ver los archivos pom.xml y src en la lista:

pom.xml  src

Ahora, actualicemos esta app de Console mediante la edición de App.java para usar la biblioteca cliente de Java Spanner a fin de crear una tabla de clasificación que contenga dos tabla, jugadores y puntuación. Puedes hacerlo directamente en el editor de Cloud Shell:

Haz clic en el siguiente ícono destacado para abrir el editor de Cloud Shell:

73cf70e05f653ca.png

Abre pom.xml en la carpeta de la tabla de clasificación. Abre el archivo pom.xml ubicado en la carpeta java-docs-samples\ spanner\leaderboard\codelab\leaderboard. En este archivo, se configura el sistema de compilación de Maven para compilar nuestra aplicación en un archivo jar, incluidas todas nuestras dependencias.

Agrega la siguiente 1 sección de administración de dependencias nueva debajo del elemento </properties> existente:

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

Además, agrega 1 dependencia nueva en la sección <dependencies> existente, que agregará la biblioteca cliente de Java de Cloud Spanner a la aplicación.

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

Luego, reemplaza la sección <build> existente del archivo pom.xml por la siguiente sección <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>

Para guardar los cambios realizados en el archivo pom.xml, selecciona “Guardar” en el menú “Archivo” del editor de Cloud Shell o presiona las teclas “Ctrl” y “S” juntas.

A continuación, abre el archivo App.java en el editor de Cloud Shell ubicado en la carpeta src/main/java/com/google/codelabs/. Para reemplazar el código existente del archivo con el código necesario a fin de crear la base de datos leaderboard y las tablas Players y Scores, pega el siguiente código Java en el archivo 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");
  }
}

Para guardar los cambios realizados en el archivo App.java, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.

Puedes usar el archivo App.java en el directorio java-docs-samples/spanner/leaderboard/step4/src si deseas ver un ejemplo de cómo debe verse tu archivo App.java después de agregar el código para habilitar el comando create.

Para compilar tu app, ejecuta el paquete mvn desde el directorio en el que se encuentra el pom.xml:

mvn package

Una vez que su archivo jar de Java se haya compilado correctamente, ejecuta el siguiente comando para ejecutar la aplicación resultante en Cloud Shell:

java -jar target/leaderboard.jar

Deberías ver un resultado como el siguiente:

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.

En esta respuesta, podemos ver que esta es la aplicación Leaderboard, que en la actualidad tiene un comando posible: create. Podemos ver que los argumentos esperados del comando create son el ID de instancia y el ID de la base de datos.

Ahora, ejecuta el siguiente comando:

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

Después de unos segundos, deberías ver una respuesta como la siguiente:

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

En la sección de Cloud Spanner de Cloud Console, deberías ver su nueva base de datos y tablas en el menú del lado izquierdo.

ba9008bb84cb90b0.png

En el próximo paso, actualizaremos nuestra aplicación para cargar algunos datos en tu base de datos nueva.

Ahora tenemos una base de datos llamada leaderboard que contiene dos tablas, Players y Scores. Ahora usemos la biblioteca cliente de Java a fin de propagar la tabla Players con los jugadores y nuestra tabla Scores con puntuaciones aleatorias para cada jugador.

Si aún no se abrió, haz clic en el ícono que se destaca a continuación para abrir el editor de Cloud Shell:

ef49fcbaaed19024.png

A continuación, edita el archivo App.java en el editor de Cloud Shell a fin de agregar un comando insert que se pueda usar para insertar 100 jugadores a la tabla Players o se puede usar a fin de insertar 4 puntuaciones aleatorias en el Scores de cada jugador en la tabla Players.

Primero, actualiza la sección imports en la parte superior del archivo de la app y reemplaza el contenido actual de modo que, una vez que termines, se vea de la siguiente manera:

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;

A continuación, agrega la siguiente inserción, métodos insertPlayers e insertScores debajo del método create() existente y encima del 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...");
    }
  }

Luego, para que el comando insert funcione, agrega el siguiente código al método “principal” de tu app dentro de la declaración switch (command):

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

Cuando termines la declaración switch (command), el código se verá de la siguiente manera:

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

El último paso a fin de que termines de agregar la funcionalidad “insert” a tu app es agregar texto de ayuda para el comando “insert” al método printUsageAndExit(). Agrega las siguientes líneas de código al método printUsageAndExit() a fin de incluir texto de ayuda para el comando de inserción:

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

Para guardar los cambios realizados en el archivo App.java, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.

Puedes usar el archivo App.java en el directorio java-docs-samples/spanner/leaderboard/step5/src si deseas ver un ejemplo de cómo debe verse tu archivo App.java después de agregar el código para habilitar el comando insert.

Ahora volveremos a compilar y ejecutar la app para confirmar que el nuevo comando insert se incluye en la lista de comandos de la app.

Para compilar tu app, ejecuta mvn package desde el directorio en el que se encuentra el archivo pom.xml:

mvn package

Una vez que tu archivo jar de Java se haya compilado correctamente, ejecuta el siguiente comando:

java -jar target/leaderboard.jar

Deberías ver el comando insert ahora incluido en el resultado predeterminado de la aplicación:

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.

Se puede ver de la respuesta que, además del ID de instancia y el ID de la base de datos, hay otro argumento que puede tener un valor “jugadores” o “puntuaciones”.

Ahora ejecutemos el comando insert con los mismos valores de argumento que usamos cuando llamamos al comando create, y agreguemos “jugadores” como el argumento adicional “tipo de inserción”.

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

Después de unos segundos, deberías ver una respuesta como la siguiente:

Done inserting player records...

Ahora usemos la biblioteca cliente de Java a fin de propagar nuestra tabla Scores con cuatro puntuaciones aleatorias junto con marcas de tiempo para cada jugador en la tabla Players.

La columna Timestamp de la tabla Scores se definió como una columna de “marca de tiempo de confirmación” a través de la siguiente instrucción de SQL que se ejecutaba cuando ejecutamos el comando 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

Observa el atributo OPTIONS(allow_commit_timestamp=true). Esto hace que Timestamp sea una columna de “marca de tiempo de confirmación” y permite que se propague automáticamente con la marca de tiempo exacta de la transacción para operaciones INSERTAR y ACTUALIZAR en una fila de tabla determinada.

También puedes insertar tus propios valores de marca de tiempo en una columna “marca de tiempo de confirmación”, siempre y cuando insertes una marca de tiempo con un valor anterior, lo que haremos para este codelab.

Ahora, ejecutemos el comando insert con los mismos valores de argumento que usamos cuando llamamos al comando create y agregamos “puntuaciones” como el argumento adicional “tipo de inserción”.

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

Después de unos segundos, deberías ver una respuesta como la siguiente:

Done inserting score records...

La ejecución de insert con el “tipo de inserción” especificado como scores llama al método insertScores, que usa los siguientes fragmentos de código para insertar una marca de tiempo generada de forma aleatoria con una fecha y hora en el pasado:

          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 propagar automáticamente la columna Timestamp con la marca de tiempo exacta cuando la transacción “Insertar” se instale, puedes insertar la constante Value.COMMIT_TIMESTAMP de Java, como en el siguiente fragmento de código:

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

Ahora que se completó la carga de datos, verificaremos los valores que acabamos de escribir en nuestras tablas nuevas. Primero, selecciona la base de datos leaderboard y, luego, la tabla Players. Haz clic en la pestaña Data. Debería ver que tiene datos en las columnas PlayerId y PlayerName de la tabla.

7bc2c96293c31c49.png

A continuación, verifiquemos la tabla de puntuaciones. Para ello, haz clic en la tabla Scores y selecciona la pestaña Data. Deberías ver que tiene datos en las columnas PlayerId, Timestamp y Score de la tabla.

d8a4ee4f13244c19.png

¡Bien hecho! Actualicemos nuestra app para ejecutar algunas consultas que podemos usar a fin de crear una tabla de clasificación de videojuegos.

Ahora que configuramos nuestra base de datos y cargamos información en nuestras tablas, vamos a crear una tabla de clasificación con estos datos. Para ello, debemos responder las siguientes cuatro preguntas:

  1. ¿Cuáles son los diez principales jugadores de todos los tiempos?
  2. ¿Cuáles son los diez jugadores principales de este año?
  3. ¿Cuáles son los diez jugadores principales del mes?
  4. ¿Cuáles son los diez jugadores principales de la semana?

Actualicemos nuestra aplicación para ejecutar las consultas de SQL que responderán estas preguntas.

Agregaremos un comando query que proporcionará una manera de ejecutar las consultas a fin de responder las preguntas que producirán la información requerida en nuestra tabla de clasificación.

Edita el archivo App.java en el editor de Cloud Shell a fin de actualizar la app para agregar un comando query. El comando query consta de dos métodos query, uno que solo requiere un argumento DatabaseClient y otro que requiere un argumento timespan adicional para facilitar el filtrado de resultados por un período especificado en horas.

Agrega los dos métodos query siguientes debajo del método insertScores() existente y encima del método printUsageAndExit() existente:

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

Luego, para que el comando query funcione, agrega el siguiente código a la declaración switch(command) en el método “principal” de tu 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;

El último paso a fin de que termines de agregar la funcionalidad “consulta” a tu app es agregar texto de ayuda para el comando “query” al método printUsageAndExit(). Agrega las siguientes líneas de código al método printUsageAndExit() a fin de incluir el texto de ayuda para el 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");

Para guardar los cambios realizados en el archivo App.java, selecciona “Guardar” en el menú “Archivo” de Cloud Shell.

Puedes usar el archivo App.java en el directorio dotnet-docs-samples/applications/leaderboard/step6/src si deseas ver un ejemplo de cómo debe verse tu archivo App.java después de agregar el código para habilitar el comando query.

Para compilar tu app, ejecuta mvn package desde el directorio en el que se encuentra el archivo pom.xml:

mvn package

Ahora ejecutemos la app para confirmar que el nuevo comando query se incluye en la lista de comandos posibles de la app. Ejecuta el siguiente comando:

java -jar target/leaderboard.jar

Ahora, deberías ver el comando query incluido en el resultado predeterminado de la aplicación como una nueva opción 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.

Como puedes ver en la respuesta, además del ID de instancia y los argumentos de ID de la base de datos, el comando query nos permite especificar un período opcional en cantidad de horas para usar en el filtrado de registros según su valor en la columna Scores de la tabla Timestamp. Dado que el argumento de intervalo es opcional, significa que no se incluye un argumento de intervalo, por lo que las marcas de tiempo no se filtran los registros. Por lo tanto, podemos usar el comando query sin un valor de “intervalo” para obtener una lista de los “diez mejores” jugadores de todos los tiempos.

Ejecutamos el comando query sin especificar un “período” con los mismos valores de argumento que usamos cuando ejecutamos el comando create.

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

Deberías ver una respuesta que incluya los “diez mejores” jugadores de todos los tiempos, como se indica a continuación:

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

Ahora, ejecutemos el comando query con los argumentos necesarios a fin de consultar los “diez mejores” jugadores del año. Para ello, especifica un “intervalo” igual a la cantidad de horas en un año, que es 8,760.

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

Deberías ver una respuesta que incluya a los “diez mejores” jugadores del año, como se muestra a continuación:

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

Ahora, ejecutemos el comando query a fin de consultar a los “diez mejores” jugadores del mes. Para ello, especifica un “intervalo” igual a la cantidad de horas de un mes, que es 730.

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

Deberías ver una respuesta que incluya a los “diez mejores” jugadores del mes, como se muestra a continuación:

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

Ahora, ejecutemos el comando query a fin de consultar a los “diez mejores” jugadores de la semana. Para ello, especifica un “intervalo” igual a la cantidad de horas de una semana, que es 168.

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

Deberías ver una respuesta que incluya a los “diez mejores” jugadores de la semana, como se muestra a continuación:

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

¡Excelente trabajo!

Ahora a medida que agregues los registros, Cloud Spanner escalará tu base de datos del tamaño que necesites que sea. No importa cuánto crezca tu base de datos, la tabla de clasificación de tu juego puede escalar con exactitud con Cloud Spanner y su tecnología Truetime.

Después de la diversión cuando se juega con Spanner, debemos limpiar nuestro zona de prueba para ahorrar dinero y recursos valiosos. Por suerte, este paso es sencillo. Ve a la sección de Cloud Spanner de Cloud Console y borra la instancia que creamos en el paso de codelab denominado “Configurar una instancia de Cloud Spanner”.

Temas abordados:

  • Instancias, bases de datos y esquemas de tablas de Google Cloud Spanner para una tabla de clasificación
  • Cómo crear una aplicación de consola de Java
  • Cómo crear una base de datos y tablas de Spanner con la biblioteca cliente de Java
  • Cómo cargar datos en una base de datos de Spanner con la biblioteca cliente de Java
  • Cómo consultar los resultados de los “Diez mejores” de tus datos mediante las marcas de tiempo de confirmación de Spanner y la biblioteca cliente de Java

Próximos pasos:

Envíanos tus comentarios

  • Tómate un momento para completar nuestra breve encuesta