Primeiros passos com o Cloud Spanner em Java

Objetivos

Neste tutorial, você verá as seguintes etapas usando a biblioteca de cliente do Cloud Spanner para Java:

  • Criar uma instância e um banco de dados do Cloud Spanner.
  • Gravar, ler e executar consultas SQL em dados contidos no banco de dados.
  • Atualizar o esquema do banco de dados.
  • Atualizar dados usando uma transação de leitura e gravação.
  • Adicionar um índice secundário ao banco de dados.
  • Usar o índice para ler e executar consultas SQL nos dados.
  • Recuperar dados usando uma transação somente leitura.

Custos

Neste tutorial, usamos o Cloud Spanner, que é um componente faturável do Google Cloud Platform. Para informações sobre o custo de utilização do Cloud Spanner, consulte Preços.

Antes de começar

  1. Conclua as etapas descritas em Configuração, que abordam a criação e configuração de um projeto padrão do Google Cloud Platform, a ativação do faturamento, a ativação da API Cloud Spanner e a configuração do OAuth 2.0 para receber credenciais de autenticação e usar a API Cloud Spanner.
    Em especial, execute gcloud auth application-default login para configurar o ambiente local de desenvolvimento com as credenciais de autenticação.

  2. Instale os seguintes itens na sua máquina de desenvolvimento se ainda não estiverem instalados:

  3. Clone o repositório do aplicativo de amostra na máquina local:

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

    Outra alternativa é fazer download do exemplo como um arquivo zip e extraí-lo.

  4. Altere para o diretório que contém o código de amostra do Cloud Spanner:

    cd java-docs-samples/spanner/cloud-client
    

Criar uma instância

Ao usar o Cloud Spanner pela primeira vez, é necessário criar uma instância, que é uma alocação de recursos usados pelos bancos de dados do Cloud Spanner. Ao criar uma instância, escolha uma configuração para ela que determine onde os dados serão armazenados e também o número de nós a serem usados. Com isso, é definida a quantidade de recursos de serviço e de armazenamento na instância.

Execute o seguinte comando para criar uma instância do Cloud Spanner na região us-central1 com um nó:

gcloud spanner instances create test-instance --config=regional-us-central1 \
  --description="Test Instance" --nodes=1

A instância criada tem as seguintes características:

  • O ID dela é test-instance.
  • O nome de exibição é Test Instance.
  • A configuração da instância é regional-us-central1. Com as configurações regionais, os dados são armazenados em uma região. Já com as configurações multirregionais, os dados são distribuídos em diversas regiões. Saiba mais em Instâncias.
  • A contagem de nós é 1. node_count corresponde à quantidade de recursos de serviço e de armazenamento disponíveis para os bancos de dados na instância. Saiba mais em Contagem de nós.

Você verá:

Creating instance...done.

Consultar os arquivos de amostra

O repositório de amostras contém uma amostra que apresenta como usar o Cloud Spanner com Java.

O pom.xml adiciona a biblioteca de cliente do Cloud Spanner para Java às dependências do projeto e configura o plug-in do conjunto para criar um arquivo JAR executável com a classe Java definida neste tutorial.

Crie a amostra do diretório spanner/cloud-client:

mvn package

Criar um banco de dados

Para criar um banco de dados chamado example-db na instância chamada test-instance, execute o seguinte código na linha de comando:

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
createdatabase test-instance example-db

Você verá:

Created database [example-db]

Você acabou de criar um banco de dados do Cloud Spanner. Veja a seguir o código que criou o banco de dados.

static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) {
  OperationFuture<Database, CreateDatabaseMetadata> op =
      dbAdminClient.createDatabase(
          id.getInstanceId().getInstance(),
          id.getDatabase(),
          Arrays.asList(
              "CREATE TABLE Singers (\n"
                  + "  SingerId   INT64 NOT NULL,\n"
                  + "  FirstName  STRING(1024),\n"
                  + "  LastName   STRING(1024),\n"
                  + "  SingerInfo BYTES(MAX)\n"
                  + ") PRIMARY KEY (SingerId)",
              "CREATE TABLE Albums (\n"
                  + "  SingerId     INT64 NOT NULL,\n"
                  + "  AlbumId      INT64 NOT NULL,\n"
                  + "  AlbumTitle   STRING(MAX)\n"
                  + ") PRIMARY KEY (SingerId, AlbumId),\n"
                  + "  INTERLEAVE IN PARENT Singers ON DELETE CASCADE"));
  try {
    // Initiate the request which returns an OperationFuture.
    Database db = op.get();
    System.out.println("Created database [" + db.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);
  }
}

O código também define duas tabelas, Singers e Albums, para um aplicativo básico de música. Essas tabelas são usadas em toda esta página. Caso ainda não tenha feito isso, confira o exemplo de esquema.

A próxima etapa é gravar dados no seu banco de dados.

Criar um cliente de banco de dados

Antes de fazer leituras ou gravações, é necessário criar um DatabaseClient. Você pode pensar em um DatabaseClient como uma conexão de banco de dados: todas as suas interações com o Cloud Spanner precisam passar por um DatabaseClient. Normalmente, você cria um DatabaseClient quando seu aplicativo é iniciado, e depois reutiliza esse DatabaseClient para ler, gravar e executar transações.

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();
  // Use client here...
} finally {
  spanner.close();
}

Como cada cliente usa recursos no Cloud Spanner, é recomendado fechar os clientes desnecessários chamando close().

Leia mais na referência do DatabaseClient do Javadoc.

Escrever dados com DML

É possível inserir dados usando a linguagem de manipulação de dados (DML, na sigla em inglês) em uma transação de leitura/gravação.

Use o método executeUpdate() para executar uma declaração DML.

static void writeUsingDml(DatabaseClient dbClient) {
  // Insert 4 singer records
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
              String sql =
                  "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
                      + "(12, 'Melissa', 'Garcia'), "
                      + "(13, 'Russell', 'Morales'), "
                      + "(14, 'Jacqueline', 'Long'), "
                      + "(15, 'Dylan', 'Shaw')";
              long rowCount = transaction.executeUpdate(Statement.of(sql));
              System.out.printf("%d records inserted.\n", rowCount);
              return null;
            }
          });
}

Execute a amostra usando o argumento writeusingdml.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
writeusingdml test-instance example-db

Você verá:

4 records inserted.

Gravar dados com mutações

Também é possível inserir dados usando mutações.

Grave os dados usando um objeto Mutation. Um objeto Mutation é um contêiner para operações de mutação. Um Mutation representa uma sequência de inserções, atualizações e exclusões que o Cloud Spanner aplica atomicamente a diferentes linhas e tabelas em um banco de dados do Cloud Spanner.

O método newInsertBuilder() na classe Mutation cria uma mutação INSERT, que insere uma nova linha em uma tabela. Se a linha já existir, a gravação falhará. Outra opção é usar o método newInsertOrUpdateBuilder para criar uma mutação INSERT_OR_UPDATE, que atualiza os valores da coluna se a linha já existir.

As mutações são gravadas pelo método write() na classe DatabaseClient. Todas as mutações em um único lote são aplicadas atomicamente.

Este código mostra como gravar os dados usando mutações:

static final List<Singer> SINGERS =
    Arrays.asList(
        new Singer(1, "Marc", "Richards"),
        new Singer(2, "Catalina", "Smith"),
        new Singer(3, "Alice", "Trentor"),
        new Singer(4, "Lea", "Martin"),
        new Singer(5, "David", "Lomond"));

static final List<Album> ALBUMS =
    Arrays.asList(
        new Album(1, 1, "Total Junk"),
        new Album(1, 2, "Go, Go, Go"),
        new Album(2, 1, "Green"),
        new Album(2, 2, "Forever Hold Your Peace"),
        new Album(2, 3, "Terrified"));
static void writeExampleData(DatabaseClient dbClient) {
  List<Mutation> mutations = new ArrayList<>();
  for (Singer singer : SINGERS) {
    mutations.add(
        Mutation.newInsertBuilder("Singers")
            .set("SingerId")
            .to(singer.singerId)
            .set("FirstName")
            .to(singer.firstName)
            .set("LastName")
            .to(singer.lastName)
            .build());
  }
  for (Album album : ALBUMS) {
    mutations.add(
        Mutation.newInsertBuilder("Albums")
            .set("SingerId")
            .to(album.singerId)
            .set("AlbumId")
            .to(album.albumId)
            .set("AlbumTitle")
            .to(album.albumTitle)
            .build());
  }
  dbClient.write(mutations);
}

Execute a amostra usando o argumento write.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
write test-instance example-db

Você verá o comando executado com sucesso.

Consultar dados usando SQL

O Cloud Spanner é compatível com uma interface SQL nativa para leitura de dados, que você pode acessar na linha de comando usando a ferramenta de linha de comando gcloud ou de maneira programática usando a biblioteca de cliente do Cloud Spanner para Java.

Na linha de comando

Execute a instrução SQL a seguir para ler os valores de todas as colunas da tabela Albums:

gcloud spanner databases execute-sql example-db --instance=test-instance --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

O resultado será:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Como usar a biblioteca de cliente do Cloud Spanner para Java

Além de executar uma instrução SQL na linha de comando, você pode emitir a mesma instrução SQL de maneira programática usando a biblioteca de cliente do Cloud Spanner para Java.

Os seguintes métodos e classes são usados para executar a consulta SQL:

  • O método singleUse() na classe DatabaseClient: use-o para ler o valor de uma ou mais colunas de uma ou mais linhas em uma tabela do Cloud Spanner. singleUse() retorna um objeto ReadContext, que é usado para executar uma instrução de leitura ou SQL.
  • O método executeQuery() da classe ReadContext: use-o para executar uma consulta em um banco de dados.
  • A classe Statement: use-a para criar uma string SQL.
  • A classe ResultSet: use-a para acessar os dados retornados por uma instrução SQL ou chamada de leitura.

Veja como emitir a consulta e acessar os dados:

static void query(DatabaseClient dbClient) {
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Execute a amostra usando o argumento query.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
query test-instance example-db

Você verá o seguinte resultado:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Consulta usando um parâmetro SQL

É possível usar parâmetros para incluir valores personalizados em instruções SQL. Aqui está um exemplo de como usar @lastName como parâmetro na cláusula WHERE para consultar registros que contêm um valor específico para LastName.

static void queryWithParameter(DatabaseClient dbClient) {
  Statement statement =
      Statement.newBuilder(
              "SELECT SingerId, FirstName, LastName\n"
                  + "FROM Singers\n"
                  + "WHERE LastName = @lastName")
          .bind("lastName")
          .to("Garcia")
          .build();
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getString("FirstName"),
          resultSet.getString("LastName"));
    }
  }
}

Execute a amostra usando o argumento queryWithParameter.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
querywithparameter test-instance example-db

Você verá o seguinte resultado:

12 Melissa Garcia

Ler dados usando a API de leitura

Além da interface SQL, o Cloud Spanner também é compatível com uma interface de leitura.

Use o método read() da classe ReadContext para ler linhas do banco de dados. Use um objeto KeySet para definir uma coleção de chaves e intervalos de chaves a serem lidos.

Veja como ler os dados:

static void read(DatabaseClient dbClient) {
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient
          .singleUse()
          .read(
              "Albums",
              KeySet.all(), // Read all rows in a table.
              Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Execute a amostra usando o argumento read.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
read test-instance example-db

Você verá uma resposta similar a:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Atualizar o esquema do banco de dados

Imagine que você precise adicionar uma nova coluna chamada MarketingBudget à tabela Albums. Para adicionar uma nova coluna a uma tabela, é necessário atualizar seu esquema de banco de dados. O Cloud Spanner é compatível com atualizações de esquema em um banco de dados enquanto esse banco continua a disponibilizar o tráfego. Para fazer atualizações no esquema, não é necessário desconectar o banco de dados, nem bloquear tabelas ou colunas inteiras. É possível continuar gravando dados no banco de dados durante a atualização do esquema. Leia mais sobre as atualizações de esquemas compatíveis e o desempenho das alterações de esquemas em Atualizações de esquema.

Adicionar uma coluna

Você pode adicionar uma coluna na linha de comando usando a ferramenta de linha de comando gcloud ou usando a biblioteca de cliente do Cloud Spanner para Java de maneira programática.

Na linha de comando

Use o comando ALTER TABLE a seguir para adicionar a nova coluna à tabela:

gcloud spanner databases ddl update example-db --instance=test-instance \
  --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

Você verá:

Schema updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para Java

Use o método updateDatabaseDdl() da classe DatabaseAdminClient para modificar o esquema:

static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseId dbId) {
  OperationFuture<Void, UpdateDatabaseDdlMetadata> op =
      adminClient.updateDatabaseDdl(
          dbId.getInstanceId().getInstance(),
          dbId.getDatabase(),
          Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"),
          null);
  try {
    // Initiate the request which returns an OperationFuture.
    op.get();
    System.out.println("Added MarketingBudget column");
  } 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);
  }
}

Execute a amostra usando o argumento addmarketingbudget.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
addmarketingbudget test-instance example-db

Você verá:

Added MarketingBudget column.

Gravar dados na nova coluna

O código a seguir grava dados na nova coluna. Ele define MarketingBudget como 100000 para a linha informada por Albums(1, 1) e 500000 para a linha informada por Albums(2, 2).

static void update(DatabaseClient dbClient) {
  // Mutation can be used to update/insert/delete a single row in a table. Here we use
  // newUpdateBuilder to create update mutations.
  List<Mutation> mutations =
      Arrays.asList(
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(1)
              .set("AlbumId")
              .to(1)
              .set("MarketingBudget")
              .to(100000)
              .build(),
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(2)
              .set("AlbumId")
              .to(2)
              .set("MarketingBudget")
              .to(500000)
              .build());
  // This writes all the mutations to Cloud Spanner atomically.
  dbClient.write(mutations);
}

Execute a amostra usando o argumento update.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
update test-instance example-db

Também é possível executar uma consulta SQL ou uma chamada de leitura para coletar os valores que você acabou de gravar.

Veja a seguir o código para executar a consulta:

static void queryMarketingBudget(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet = dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Para realizar esta consulta, execute a amostra usando o argumento querymarketingbudget.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
querymarketingbudget test-instance example-db

Você verá:

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

Atualizar dados

É possível atualizar dados usando DML em uma transação de leitura/gravação.

Use o método executeUpdate() para executar uma declaração DML.

static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
              // Transfer marketing budget from one album to another. We do it in a transaction to
              // ensure that the transfer is atomic.
              String sql1 =
                  "SELECT MarketingBudget from Albums WHERE SingerId = 2 and AlbumId = 2";
              ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
              long album2Budget = 0;
              while (resultSet.next()) {
                album2Budget = resultSet.getLong("MarketingBudget");
              }
              // Transaction will only be committed if this condition still holds at the time of
              // commit. Otherwise it will be aborted and the callable will be rerun by the
              // client library.
              long transfer = 200000;
              if (album2Budget >= transfer) {
                String sql2 =
                    "SELECT MarketingBudget from Albums WHERE SingerId = 1 and AlbumId = 1";
                ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
                long album1Budget = 0;
                while (resultSet2.next()) {
                  album1Budget = resultSet2.getLong("MarketingBudget");
                }
                album1Budget += transfer;
                album2Budget -= transfer;
                Statement updateStatement =
                    Statement.newBuilder(
                            "UPDATE Albums "
                                + "SET MarketingBudget = @AlbumBudget "
                                + "WHERE SingerId = 1 and AlbumId = 1")
                        .bind("AlbumBudget")
                        .to(album1Budget)
                        .build();
                transaction.executeUpdate(updateStatement);
                Statement updateStatement2 =
                    Statement.newBuilder(
                            "UPDATE Albums "
                                + "SET MarketingBudget = @AlbumBudget "
                                + "WHERE SingerId = 2 and AlbumId = 2")
                        .bind("AlbumBudget")
                        .to(album2Budget)
                        .build();
                transaction.executeUpdate(updateStatement2);
              }
              return null;
            }
          });
}

Execute a amostra usando o argumento writewithtransactionusingdml.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
writewithtransactionusingdml test-instance example-db

Usar um índice secundário

Suponha que você queira buscar todas as linhas de Albums que tenham valores de AlbumTitle em um determinado intervalo. É possível ler todos os valores da coluna AlbumTitle usando uma instrução SQL ou uma chamada de leitura e, em seguida, descartar as linhas que não atendam aos critérios. Porém, fazer essa varredura em uma tabela completa é um processo caro, especialmente em tabelas com muitas linhas. Em vez disso, acelere a recuperação de linhas ao pesquisar por colunas de chaves não primárias por meio da criação de um índice secundário na tabela.

Adicionar um índice secundário a uma tabela requer uma atualização de esquema. Como outras atualizações de esquema, o Cloud Spanner é compatível com a adição de um índice enquanto o banco de dados continua a disponibilizar o tráfego. O Cloud Spanner preenche o índice com dados (processo conhecido como "preenchimento") em segundo plano. Pode levar alguns minutos para que os preenchimentos sejam concluídos, mas não é necessário deixar o banco de dados off-line ou evitar a gravação em determinadas tabelas ou colunas durante esse processo. Para mais detalhes, consulte o preenchimento de índices.

Adicionar um índice secundário

Você pode adicionar um índice na linha de comando usando a ferramenta de linha de comando gcloud ou usando a biblioteca de cliente do Cloud Spanner para Java de maneira programática.

Na linha de comando

Use o comando CREATE INDEX a seguir para adicionar um índice ao banco de dados:

gcloud spanner databases ddl update example-db --instance=test-instance \
  --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

Você verá:

Schema updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para Java

Use o método updateDatabaseDdl() da classe DatabaseAdminClient para adicionar um índice:

static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) {
  OperationFuture<Void, UpdateDatabaseDdlMetadata> op =
      adminClient.updateDatabaseDdl(
          dbId.getInstanceId().getInstance(),
          dbId.getDatabase(),
          Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"),
          null);
  try {
    // Initiate the request which returns an OperationFuture.
    op.get();
    System.out.println("Added AlbumsByAlbumTitle index");
  } 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);
  }
}

Execute a amostra usando o argumento addindex.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
addindex test-instance example-db

Pode levar alguns minutos para adicionar um índice. Depois da adição, você verá:

Added the AlbumsByAlbumTitle index.

Consultar usando o índice

Faça uma consulta usando o novo índice na linha de comando ou na biblioteca de cliente.

Na linha de comando

Execute uma instrução SQL usando a ferramenta de linha de comando gcloud para buscar AlbumId, AlbumTitle e MarketingBudget em Albums usando o índice AlbumsByAlbumTitle para o intervalo de AlbumsTitle em ["Aardvark", "Goo").

gcloud spanner databases execute-sql example-db --instance=test-instance --sql='SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo"'

O resultado será:

AlbumId  AlbumTitle               MarketingBudget
2        Go, Go, Go
2        Forever Hold Your Peace  300000

Como usar a biblioteca de cliente do Cloud Spanner para Java

O código para usar o índice de maneira programática é semelhante ao código de consulta usado anteriormente.

static void queryUsingIndex(DatabaseClient dbClient) {
  Statement statement =
      Statement
          // We use FORCE_INDEX hint to specify which index to use. For more details see
          // https://cloud.google.com/spanner/docs/query-syntax#from-clause
          .newBuilder(
              "SELECT AlbumId, AlbumTitle, MarketingBudget\n"
                  + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}\n"
                  + "WHERE AlbumTitle >= @StartTitle AND AlbumTitle < @EndTitle")
          // We use @BoundParameters to help speed up frequently executed queries.
          //  For more details see https://cloud.google.com/spanner/docs/sql-best-practices
          .bind("StartTitle")
          .to("Aardvark")
          .bind("EndTitle")
          .to("Goo")
          .build();
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("AlbumId"),
          resultSet.getString("AlbumTitle"),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Execute a amostra usando o argumento queryindex.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
queryindex test-instance example-db

Você verá uma resposta similar a:

2 Go, Go, Go NULL
2 Forever Hold Your Peace 300000

Para mais detalhes, consulte a referência de:

Ler usando o índice

Para ler usando o índice, use o método readUsingIndex() da classe ReadContext.

O código a seguir busca todas as colunas AlbumId e AlbumTitle do índice AlbumsByAlbumTitle.

static void readUsingIndex(DatabaseClient dbClient) {
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
    }
  }
}

Execute a amostra usando o argumento readindex.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
readindex test-instance example-db

Você verá:

2 Forever Hold Your Peace
2 Go, Go, Go
1 Green
3 Terrified
1 Total Junk

Adicionar um índice com uma cláusula STORING

Talvez você tenha percebido que o exemplo de leitura acima não inclui a leitura da coluna MarketingBudget. Isso ocorre porque a interface de leitura do Cloud Spanner não é compatível com a capacidade de fazer a junção de um índice a uma tabela de dados para pesquisar valores que não estão armazenados no índice.

Crie uma definição alternativa de AlbumsByAlbumTitle que armazene uma cópia de MarketingBudget no índice.

Na linha de comando

gcloud spanner databases ddl update example-db --instance=test-instance \
  --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)'

A adição do índice pode levar alguns minutos. Depois da adição, você verá:

Schema updating...done.

Como usar a biblioteca de cliente do Cloud Spanner para Java

Use o método updateDatabaseDdl() da classe DatabaseAdminClient para adicionar um índice com uma cláusula STORING:

static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) {
  OperationFuture<Void, UpdateDatabaseDdlMetadata> op =
      adminClient.updateDatabaseDdl(
          dbId.getInstanceId().getInstance(),
          dbId.getDatabase(),
          Arrays.asList(
              "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
                  + "STORING (MarketingBudget)"),
          null);
  try {
    // Initiate the request which returns an OperationFuture.
    op.get();
    System.out.println("Added AlbumsByAlbumTitle2 index");
  } 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);
  }
}

Execute a amostra usando o argumento addstoringindex.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
addstoringindex test-instance example-db

Pode levar alguns minutos para adicionar um índice. Depois da adição, você verá:

Added the AlbumsByAlbumTitle2 index.

Agora você pode executar uma leitura que busque todas as colunas AlbumId, AlbumTitle e MarketingBudget do índice AlbumsByAlbumTitle2:

static void readStoringIndex(DatabaseClient dbClient) {
  // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
  // We use a try-with-resource block to automatically release resources held by ResultSet.
  try (ResultSet resultSet = dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle2",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong(0),
          resultSet.getString(1),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Execute a amostra usando o argumento readstoringindex.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
readstoringindex test-instance example-db

Você verá uma resposta similar a:

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 300000

Recuperar dados usando transações somente leitura

Suponha que você queira executar mais de uma leitura no mesmo carimbo de data/hora. As transações somente leitura observam um prefixo consistente do histórico de confirmações da transação. Portanto, o aplicativo sempre recebe dados consistentes. Use um objeto ReadOnlyTransaction para executar transações somente leitura. Use o método readOnlyTransaction() da classe DatabaseClient para conseguir um objeto ReadOnlyTransaction.

Veja a seguir como executar uma consulta e fazer uma leitura na mesma transação somente leitura.

static void readOnlyTransaction(DatabaseClient dbClient) {
  // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
  // We use a try-with-resource block to automatically do so.
  try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
    ResultSet queryResultSet =
        transaction.executeQuery(
            Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
    while (queryResultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2));
    }
    // We use a try-with-resource block to automatically release resources held by ResultSet.
    try (ResultSet readResultSet =
        transaction.read(
            "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
      while (readResultSet.next()) {
        System.out.printf(
            "%d %d %s\n",
            readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2));
      }
    }
  }
}

Execute a amostra usando o argumento readonlytransaction.

java -jar target/spanner-google-cloud-samples-jar-with-dependencies.jar \
readonlytransaction test-instance example-db

Você verá uma resposta similar a:

2 2 Forever Hold Your Peace
1 2 Go, Go, Go
2 1 Green
2 3 Terrified
1 1 Total Junk
1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Limpeza

Para evitar cobranças adicionais na sua conta do Google Cloud Platform pelos recursos utilizados neste tutorial, descarte o banco de dados e exclua a instância que você criou.

Excluir o banco de dados

Se você excluir uma instância, todos os bancos de dados nela serão excluídos automaticamente. Nesta etapa, mostramos como excluir um banco de dados sem remover a instância. Ainda pode haver cobrança em relação à instância.

Na linha de comando

gcloud spanner databases delete example-db --instance=test-instance

No Console do GCP

  1. Acesse a página Instâncias do Cloud Spanner no Console do Google Cloud Platform.
    Ir para a página "Instâncias" do Cloud Spanner
  2. Clique na instância.
  3. Clique no banco de dados que você quer excluir.
  4. Na página Detalhes do banco de dados, clique em Excluir.
  5. Confirme se quer excluir o banco de dados e clique em Excluir.

Excluir a instância

A exclusão de uma instância descarta automaticamente todos os bancos de dados criados nela.

Na linha de comando

gcloud spanner instances delete test-instance

No Console do GCP

  1. Acesse a página Instâncias do Cloud Spanner no Console do Google Cloud Platform.
    Ir para a página "Instâncias" do Cloud Spanner
  2. Clique na sua instância.
  3. Clique em Excluir.
  4. Confirme se quer excluir a instância e clique em Excluir.

A seguir

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner