Criar um aplicativo de banco on-line com o Spanner

1. Visão geral

O Spanner é um serviço de banco de dados totalmente gerenciado, escalonável horizontalmente e distribuído globalmente, ótimo para cargas de trabalho operacionais relacionais e não relacionais. Além dos recursos principais, o Spanner oferece recursos avançados poderosos que permitem criar aplicativos inteligentes e orientados a dados.

Este codelab se baseia no entendimento básico do Spanner e explora o uso de integrações avançadas para melhorar os recursos de processamento e análise de dados, usando um aplicativo bancário on-line como base.

Vamos nos concentrar em três recursos avançados principais:

  • Integração com a Vertex AI:saiba como integrar o Spanner à plataforma de IA do Google Cloud, a Vertex AI. Você vai aprender a invocar modelos da Vertex AI diretamente nas consultas SQL do Spanner, ativando transformações e previsões poderosas no banco de dados, permitindo que nosso aplicativo bancário categorize transações automaticamente para casos de uso como acompanhamento de orçamento e detecção de anomalias.
  • Pesquisa de texto completo:saiba como implementar a funcionalidade de pesquisa de texto completo no Spanner. Você vai aprender a indexar dados de texto e escrever consultas eficientes para realizar pesquisas baseadas em palavras-chave nos seus dados operacionais, permitindo a descoberta de dados eficaz, como encontrar clientes por endereço de e-mail no sistema bancário.
  • Consultas federadas do BigQuery:saiba como aproveitar os recursos de consulta federada do Spanner para consultar diretamente dados que residem no BigQuery. Isso permite combinar os dados operacionais em tempo real do Spanner com os conjuntos de dados analíticos do BigQuery para insights e relatórios abrangentes sem duplicação de dados ou processos ETL complexos, impulsionando vários casos de uso no nosso aplicativo bancário, como campanhas de marketing segmentadas, combinando dados de clientes em tempo real com tendências históricas mais amplas do BigQuery.

O que você vai aprender

  • Como configurar uma instância do Spanner.
  • Como criar um banco de dados e tabelas.
  • Como carregar dados nas tabelas do banco de dados do Spanner.
  • Como chamar modelos da Vertex AI usando o Spanner.
  • Como consultar o banco de dados do Spanner usando pesquisa aproximada e de texto completo.
  • Como executar consultas federadas no Spanner do BigQuery.
  • Como excluir sua instância do Spanner.

O que é necessário

  • Um projeto do Google Cloud conectado a uma conta de faturamento.
  • Um navegador da Web, como o Chrome ou o Firefox.

2. Configuração e requisitos

Criar um projeto

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

Projeto atual

Com um projeto selecionado, pule para Ativar as APIs necessárias.

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.

Clique no botão "NEW PROJECT" na caixa de diálogo resultante para criar um novo projeto:

Novo projeto

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

Caixa de diálogo do projeto

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

Lembre-se do ID do projeto, um nome exclusivo em todos os projetos do Google Cloud. Faremos referência a ele mais adiante neste codelab como PROJECT_ID.

Detalhes do projeto

Em seguida, se ainda não tiver feito isso, ative o faturamento no console do desenvolvedor para usar os recursos do Google Cloud e ativar a API Spanner, a API Vertex AI, a API BigQuery e a API BigQuery Connection.

Faturamento do projeto

Os preços do Spanner estão documentados aqui. Outros custos associados a outros recursos serão documentados nas páginas de preços específicas.

Novos usuários do Google Cloud Platform estão qualificados para um teste sem custo financeiro de US$300.

Configuração do Google Cloud Shell

Neste codelab, vamos usar 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 e é 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.

Para ativar o Cloud Shell no Console do Cloud, basta clicar em Ativar o Cloud Shell Ícone do Cloud Shell. O provisionamento e a conexão ao ambiente levam apenas alguns instantes.

Cloud Shell

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

gcloud auth list

Saída esperada:

Credentialed Accounts

ACTIVE: *
ACCOUNT: <myaccount>@<mydomain>.com
gcloud config list project

Saída esperada:

[core]
project = <PROJECT_ID>

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

gcloud config set project <PROJECT_ID>

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

ID do projeto

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

Saída esperada:

<PROJECT_ID>

Ative as APIs necessárias

Ative as APIs Spanner, Vertex AI e BigQuery no projeto:

gcloud services enable spanner.googleapis.com
gcloud services enable aiplatform.googleapis.com
gcloud services enable bigquery.googleapis.com
gcloud services enable bigqueryconnection.googleapis.com

Resumo

Nesta etapa, você configurou seu projeto, caso ainda não tivesse um, ativou o Cloud Shell e as APIs necessárias.

A seguir

Em seguida, você vai configurar a instância do Spanner.

3. Configurar uma instância do Spanner

Criar a instância do Spanner

Nesta etapa, você vai configurar uma instância do Spanner para o codelab. Para fazer isso, abra o Cloud Shell e execute este comando:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
gcloud spanner instances create $SPANNER_INSTANCE \
  --config=regional-us-central1 \
  --description="Spanner Online Banking" \
  --nodes=1 \
  --edition=ENTERPRISE \
  --default-backup-schedule-type=NONE

Saída esperada:

Creating instance...done.

Resumo

Nesta etapa, você criou a instância do Spanner.

A seguir

Em seguida, você vai preparar o aplicativo inicial e criar o banco de dados e o esquema.

4. Criar um banco de dados e um esquema

Preparar a inscrição inicial

Nesta etapa, você vai criar o banco de dados e o esquema usando o código.

Primeiro, crie um aplicativo Java chamado onlinebanking usando o Maven:

mvn -B archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DgroupId=com.google.codelabs \
  -DartifactId=onlinebanking \
  -DjavaCompilerVersion=1.8 \
  -DjunitVersion=4.13.2 \
  -DarchetypeVersion=1.5

Confira e copie os arquivos de dados que vamos adicionar ao banco de dados (confira o repositório de código aqui):

git clone https://github.com/GoogleCloudPlatform/cloud-spanner-samples.git
cp -r ./cloud-spanner-samples/banking/data ./onlinebanking

Navegue até a pasta do aplicativo:

cd onlinebanking

Abra o arquivo pom.xml do Maven. Adicione a seção de gerenciamento de dependências para usar a BOM do Maven e gerenciar a versão das bibliotecas do Google Cloud:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>libraries-bom</artifactId>
      <version>26.56.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

O editor e o arquivo vão ficar assim: Cloud Shell

Verifique se a seção dependencies inclui as bibliotecas que o aplicativo vai usar:

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>2.0.9</version>
  </dependency>
  <dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.10</version>
  </dependency>
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner</artifactId>
  </dependency>
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-bigquery</artifactId>
  </dependency>
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-bigqueryconnection</artifactId>
  </dependency>
</dependencies>

Por fim, substitua os plug-ins de build para que o aplicativo seja empacotado em um JAR executável:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-resources-plugin</artifactId>
      <version>3.3.1</version>
      <executions>
        <execution>
          <id>copy-resources</id>
          <phase>process-resources</phase>
          <goals>
            <goal>copy-resources</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.directory}/${project.artifactId}-resources</outputDirectory>
            <resources>
              <resource>
                <directory>resources</directory>
                <filtering>true</filtering>
              </resource>
            </resources>
          </configuration>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <version>3.8.1</version>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>prepare-package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <outputDirectory>${project.build.directory}/${project.artifactId}-resources/lib</outputDirectory>
            <overWriteReleases>false</overWriteReleases>
            <overWriteSnapshots>false</overWriteSnapshots>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.4.2</version>
      <configuration>
        <finalName>${project.artifactId}</finalName>
        <outputDirectory>${project.build.directory}</outputDirectory>
        <archive>
          <index>false</index>
          <manifest>
            <mainClass>com.google.codelabs.App</mainClass>
            <addClasspath>true</addClasspath>
            <classpathPrefix>${project.artifactId}-resources/lib/</classpathPrefix>
          </manifest>
        </archive>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>3.2.5</version>
      <executions>
        <execution>
          <goals>
            <goal>integration-test</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.2.5</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 Ctrl+S.

Agora que as dependências estão prontas, você vai adicionar código ao app para criar um esquema, alguns índices (incluindo a pesquisa) e um modelo de IA conectado a um endpoint remoto. Você vai criar esses artefatos e adicionar mais métodos a essa classe ao longo deste codelab.

Abra App.java em onlinebanking/src/main/java/com/google/codelabs e substitua o conteúdo pelo seguinte código:

package com.google.codelabs;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
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;

public class App {

  // Create the Spanner database and schema
  public static void create(DatabaseAdminClient dbAdminClient, DatabaseId db,
      String location, String model) {
    System.out.println("Creating Spanner database...");
    List<String> statements = Arrays.asList(
      "CREATE TABLE Customers (\n"
          + "  CustomerId INT64 NOT NULL,\n"
          + "  FirstName STRING(256) NOT NULL,\n"
          + "  LastName STRING(256) NOT NULL,\n"
          + "  FullName STRING(512) AS (FirstName || ' ' || LastName) STORED,\n"
          + "  Email STRING(512) NOT NULL,\n"
          + "  EmailTokens TOKENLIST AS\n"
          + "    (TOKENIZE_SUBSTRING(Email, ngram_size_min=>2, ngram_size_max=>3,\n"
          + "      relative_search_types=>[\"all\"])) HIDDEN,\n"
          + "  Address STRING(MAX)\n"
          + ") PRIMARY KEY (CustomerId)",

      "CREATE INDEX CustomersByEmail\n"
          + "ON Customers(Email)",

      "CREATE SEARCH INDEX CustomersFuzzyEmail\n"
          + "ON Customers(EmailTokens)",

      "CREATE TABLE Accounts (\n"
          + "  AccountId INT64 NOT NULL,\n"
          + "  CustomerId INT64 NOT NULL,\n"
          + "  AccountType STRING(256) NOT NULL,\n"
          + "  Balance NUMERIC NOT NULL,\n"
          + "  OpenDate TIMESTAMP NOT NULL\n"
          + ") PRIMARY KEY (AccountId)",

      "CREATE INDEX AccountsByCustomer\n"
          + "ON Accounts (CustomerId)",

      "CREATE TABLE TransactionLedger (\n"
          + "  TransactionId INT64 NOT NULL,\n"
          + "  AccountId INT64 NOT NULL,\n"
          + "  TransactionType STRING(256) NOT NULL,\n"
          + "  Amount NUMERIC NOT NULL,\n"
          + "  Timestamp TIMESTAMP NOT NULL"
          + "  OPTIONS(allow_commit_timestamp=true),\n"
          + "  Category STRING(256),\n"
          + "  Description STRING(MAX),\n"
          + "  CategoryTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Category)) HIDDEN,\n"
          + "  DescriptionTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Description)) HIDDEN\n"
          + ") PRIMARY KEY (AccountId, TransactionId),\n"
          + "INTERLEAVE IN PARENT Accounts ON DELETE CASCADE",

      "CREATE INDEX TransactionLedgerByAccountType\n"
          + "ON TransactionLedger(AccountId, TransactionType)",

      "CREATE INDEX TransactionLedgerByCategory\n"
          + "ON TransactionLedger(AccountId, Category)",

      "CREATE SEARCH INDEX TransactionLedgerTextSearch\n"
          + "ON TransactionLedger(CategoryTokens, DescriptionTokens)",

      "CREATE MODEL TransactionCategoryModel\n"
          + "INPUT (prompt STRING(MAX))\n"
          + "OUTPUT (content STRING(MAX))\n"
          + "REMOTE OPTIONS (\n"
          + "  endpoint = '//aiplatform.googleapis.com/projects/" + db.getInstanceId().getProject()
              + "/locations/" + location + "/publishers/google/models/" + model + "',\n"
          + "  default_batch_size = 1\n"
          + ")");
    OperationFuture<Database, CreateDatabaseMetadata> op = dbAdminClient.createDatabase(
        db.getInstanceId().getInstance(),
        db.getDatabase(),
        statements);
    try {
      Database dbOperation = op.get();
      System.out.println("Created Spanner database [" + dbOperation.getId() + "]");
    } catch (ExecutionException e) {
      throw (SpannerException) e.getCause();
    } catch (InterruptedException e) {
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }

  static void printUsageAndExit() {
    System.out.println("Online Online Banking Application 1.0.0");
    System.out.println("Usage:");
    System.out.println("  java -jar target/onlinebanking.jar <command> [command_option(s)]");
    System.out.println("");
    System.out.println("Examples:");
    System.out.println("  java -jar target/onlinebanking.jar create");
    System.out.println("      - Create a sample Spanner database and schema in your "
        + "project.\n");
    System.exit(1);
  }

  public static void main(String[] args) {
    if (args.length < 1) {
      printUsageAndExit();
    }

    String instanceId = System.getProperty("SPANNER_INSTANCE", System.getenv("SPANNER_INSTANCE"));
    String databaseId = System.getProperty("SPANNER_DATABASE", System.getenv("SPANNER_DATABASE"));
    String location = System.getenv().getOrDefault("SPANNER_LOCATION", "us-central1");
    String model = System.getenv().getOrDefault("SPANNER_MODEL", "gemini-2.0-flash-lite");
    if (instanceId == null || databaseId == null) {
      System.err.println("Missing one or more required environment variables: SPANNER_INSTANCE or "
          + "SPANNER_DATABASE");
      System.exit(1);
    }

    BigQueryOptions bigqueryOptions = BigQueryOptions.newBuilder().build();
    BigQuery bigquery = bigqueryOptions.getService();

    SpannerOptions spannerOptions = SpannerOptions.newBuilder().build();
    try (Spanner spanner = spannerOptions.getService()) {
      String command = args[0];
      DatabaseId db = DatabaseId.of(spannerOptions.getProjectId(), instanceId, databaseId);
      DatabaseClient dbClient = spanner.getDatabaseClient(db);
      DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient();

      switch (command) {
        case "create":
          create(dbAdminClient, db, location, model);
          break;
        default:
          printUsageAndExit();
      }
    }
  }
}

Salve as alterações em App.java.

Confira as diferentes entidades que o código está criando e crie o JAR do aplicativo:

mvn package

Saída esperada:

[INFO] Building jar: /home/your_user/onlinebanking/target/onlinebanking.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Execute o aplicativo para conferir as informações de uso:

java -jar target/onlinebanking.jar

Saída esperada:

Online Banking Application 1.0.0
Usage:
  java -jar target/onlinebanking.jar <command> [command_option(s)]

Examples:
  java -jar target/onlinebanking.jar create
      - Create a sample Spanner database and schema in your project.

Criar o banco de dados e o esquema

Defina as variáveis de ambiente do aplicativo necessárias:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking

Crie o banco de dados e o esquema executando o comando create:

java -jar target/onlinebanking.jar create

Saída esperada:

Creating Spanner database...
Created Spanner database [<DATABASE_RESOURCE_NAME>]

Verificar o esquema no Spanner

No console do Spanner, navegue até a instância e o banco de dados que acabou de ser criado.

Você vai encontrar as três tabelas: Accounts, Customers e TransactionLedger.

Mostrar esquema

Essa ação cria o esquema do banco de dados, incluindo as tabelas Accounts, Customers e TransactionLedger, além de índices secundários para recuperação de dados otimizada e uma referência de modelo da Vertex AI.

Diagrama de relacionamento de entidades

A tabela TransactionLedger é intercalada nas contas para melhorar o desempenho da consulta de transações específicas da conta com uma melhor localidade de dados.

Os índices secundários (CustomersByEmail, CustomersFuzzyEmail, AccountsByCustomer, TransactionLedgerByAccountType, TransactionLedgerByCategory, TransactionLedgerTextSearch) foram implementados para otimizar padrões comuns de acesso a dados usados neste codelab, como a pesquisa de clientes por e-mail exato e aproximado, a recuperação de contas por cliente e a consulta e pesquisa eficientes de dados de transações.

O TransactionCategoryModel usa a Vertex AI para permitir chamadas SQL diretas para um LLM, que é usado para categorização dinâmica de transações neste codelab.

Resumo

Nesta etapa, você criou o banco de dados e o esquema do Spanner.

A seguir

Em seguida, você vai carregar os dados de amostra do aplicativo.

5. Carregar dados

Agora, você vai adicionar a funcionalidade para carregar dados de exemplo de arquivos CSV no banco de dados.

Abra App.java e comece substituindo as importações:

package com.google.codelabs;

import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
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.Key;
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.Struct;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;

Em seguida, adicione os métodos de inserção à classe App:

  // Insert customers from CSV
  public static void insertCustomers(DatabaseClient dbClient) {
    System.out.println("Inserting customers...");
    dbClient
        .readWriteTransaction()
        .run(transaction -> {
          int count = 0;
          List<Statement> statements = new ArrayList<>();
          try (CSVReader reader = new CSVReader(new FileReader("data/customers.csv"))) {
            reader.skip(1);
            String[] line;
            while ((line = reader.readNext()) != null) {
              Statement statement = Statement.newBuilder(
                  "INSERT INTO Customers (CustomerId, FirstName, LastName, Email, Address) "
                      + "VALUES (@customerId, @firstName, @lastName, @email, @address)")
                  .bind("customerId").to(Long.parseLong(line[0]))
                  .bind("firstName").to(line[1])
                  .bind("lastName").to(line[2])
                  .bind("email").to(line[3])
                  .bind("address").to(line[4])
                  .build();
              statements.add(statement);
              count++;
            }
            transaction.batchUpdate(statements);
            System.out.println("Inserted " + count + " customers");
            return null;
          }
        });
  }

  // Insert accounts from CSV
  public static void insertAccounts(DatabaseClient dbClient) {
    System.out.println("Inserting accounts...");
    dbClient
        .readWriteTransaction()
        .run(transaction -> {
          int count = 0;
          List<Statement> statements = new ArrayList<>();
          try (CSVReader reader = new CSVReader(new FileReader("data/accounts.csv"))) {
            reader.skip(1);
            String[] line;
            while ((line = reader.readNext()) != null) {
              Statement statement = Statement.newBuilder(
                "INSERT INTO Accounts (AccountId, CustomerId, AccountType, Balance, OpenDate) "
                    + "VALUES (@accountId, @customerId, @accountType, @balance, @openDate)")
                .bind("accountId").to(Long.parseLong(line[0]))
                .bind("customerId").to(Long.parseLong(line[1]))
                .bind("accountType").to(line[2])
                .bind("balance").to(new BigDecimal(line[3]))
                .bind("openDate").to(line[4])
                .build();
              statements.add(statement);
              count++;
            }
            transaction.batchUpdate(statements);
            System.out.println("Inserted " + count + " accounts");
            return null;
          }
        });
  }

  // Insert transactions from CSV
  public static void insertTransactions(DatabaseClient dbClient) {
    System.out.println("Inserting transactions...");
    dbClient
        .readWriteTransaction()
        .run(transaction -> {
          int count = 0;
          List<Statement> statements = new ArrayList<>();
          try (CSVReader reader = new CSVReader(new FileReader("data/transactions.csv"))) {
            reader.skip(1);
            String[] line;

            // Specify timestamps that are within last 30 days
            Random random = new Random();
            Instant startTime = Instant.now().minus(15, ChronoUnit.DAYS);
            Instant currentTimestamp = startTime;

            Map<Long, BigDecimal> balanceChanges = new HashMap<>();
            while ((line = reader.readNext()) != null) {
              long accountId = Long.parseLong(line[1]);
              String transactionType = line[2];
              BigDecimal amount = new BigDecimal(line[3]);
              int randomMinutes = random.nextInt(60) + 1;
              currentTimestamp = currentTimestamp.plus(Duration.ofMinutes(randomMinutes));
              Timestamp timestamp = Timestamp.ofTimeSecondsAndNanos(
                  currentTimestamp.getEpochSecond(), currentTimestamp.getNano());
              Statement statement = Statement.newBuilder(
                "INSERT INTO TransactionLedger (TransactionId, AccountId, TransactionType, Amount,"
                    + "Timestamp, Category, Description) "
                    + "VALUES (@transactionId, @accountId, @transactionType, @amount, @timestamp,"
                    + "@category, @description)")
                .bind("transactionId").to(Long.parseLong(line[0]))
                .bind("accountId").to(accountId)
                .bind("transactionType").to(transactionType)
                .bind("amount").to(amount)
                .bind("timestamp").to(timestamp)
                .bind("category").to(line[5])
                .bind("description").to(line[6])
                .build();
              statements.add(statement);

              // Track balance changes per account
              BigDecimal balanceChange = balanceChanges.getOrDefault(accountId,
                  BigDecimal.ZERO);
              if ("Credit".equalsIgnoreCase(transactionType)) {
                balanceChanges.put(accountId, balanceChange.add(amount));
              } else if ("Debit".equalsIgnoreCase(transactionType)) {
                balanceChanges.put(accountId, balanceChange.subtract(amount));
              } else {
                System.err.println("Unsupported transaction type: " + transactionType);
                continue;
              }

              count++;
            }

            // Apply final balance updates
            for (Map.Entry<Long, BigDecimal> entry : balanceChanges.entrySet()) {
              long accountId = entry.getKey();
              BigDecimal balanceChange = entry.getValue();

              Struct row = transaction.readRow(
                  "Accounts",
                  Key.of(accountId),
                  List.of("Balance"));
              if (row != null) {
                BigDecimal currentBalance = row.getBigDecimal("Balance");
                BigDecimal updatedBalance = currentBalance.add(balanceChange);
                Statement statement = Statement.newBuilder(
                  "UPDATE Accounts SET Balance = @balance WHERE AccountId = @accountId")
                  .bind("accountId").to(accountId)
                  .bind("balance").to(updatedBalance)
                  .build();
                statements.add(statement);
              }
            }

            transaction.batchUpdate(statements);
            System.out.println("Inserted " + count + " transactions");
          }
          return null;
        });
  }

Adicione outra instrução de caso no método main para inserção no switch (command):

        case "insert":
          String insertType = (args.length >= 2) ? args[1] : "";
          if (insertType.equals("customers")) {
            insertCustomers(dbClient);
          } else if (insertType.equals("accounts")) {
            insertAccounts(dbClient);
          } else if (insertType.equals("transactions")) {
            insertTransactions(dbClient);
          } else {
            insertCustomers(dbClient);
            insertAccounts(dbClient);
            insertTransactions(dbClient);
          }
          break;

Por fim, adicione como usar a inserção ao método printUsageAndExit:

    System.out.println("  java -jar target/onlinebanking.jar insert");
    System.out.println("      - Insert sample Customers, Accounts, and Transactions into the "
        + "database.\n");

Salve as alterações que você fez em App.java.

Recrie o aplicativo:

mvn package

Insira os dados de amostra executando o comando insert:

java -jar target/onlinebanking.jar insert

Saída esperada:

Inserting customers...
Inserted 100 customers
Inserting accounts...
Inserted 125 accounts
Inserting transactions...
Inserted 200 transactions

No console do Spanner, volte ao Spanner Studio para sua instância e seu banco de dados. Em seguida, selecione a tabela TransactionLedger e clique em "Dados" na barra lateral para verificar se os dados foram carregados. Deve haver 200 linhas na tabela.

Ver dados

Resumo

Nesta etapa, você inseriu os dados de amostra no banco de dados.

A seguir

Em seguida, você vai aproveitar a integração da Vertex AI para categorizar transações bancárias automaticamente no Spanner SQL.

6. Categorizar dados com a Vertex AI

Nesta etapa, você vai aproveitar o poder da Vertex AI para categorizar automaticamente suas transações financeiras diretamente no Spanner SQL. Com a Vertex AI, você pode escolher um modelo pré-treinado ou treinar e implantar o seu. Confira os modelos disponíveis no Model Garden da Vertex AI.

Neste codelab, vamos usar um dos modelos Gemini, Gemini Flash Lite. Essa versão do Gemini é econômica e ainda pode processar a maioria das cargas de trabalho diárias.

No momento, temos várias transações financeiras que gostaríamos de categorizar (groceries, transportation etc.) dependendo da descrição. Para fazer isso, registre um modelo no Spanner e use ML.PREDICT para chamar o modelo de IA.

No nosso aplicativo bancário, podemos querer categorizar transações para ter insights mais aprofundados sobre o comportamento do cliente, personalizar serviços, detectar anomalias com mais eficiência ou permitir que o cliente acompanhe o orçamento mensal.

A primeira etapa já foi realizada quando criamos o banco de dados e o esquema, que criaram um modelo assim:

instrução &quot;create model&quot;

Em seguida, vamos adicionar um método ao aplicativo para chamar ML.PREDICT.

Abra App.java e adicione o método categorize:

  // Use Vertex AI to set the category of transactions
  public static void categorize(DatabaseClient dbClient) {
    System.out.println("Categorizing transactions...");
    try {
      // Create a prompt to instruct the LLM how to categorize the transactions
      String categories = String.join(", ", Arrays.asList("Entertainment", "Gifts", "Groceries",
          "Investment", "Medical", "Movies", "Online Shopping", "Other", "Purchases", "Refund",
          "Restaurants", "Salary", "Transfer", "Transportation", "Utilities"));
      String prompt = "Categorize the following financial activity into one of these "
          + "categories: " +  categories + ". Return Other if the description cannot be mapped to "
          + "one of these categories.  Only return the exact category string, no other text or "
          + "punctuation or reasoning. Description: ";
      String sql = "UPDATE TransactionLedger SET Category = (\n"
          + "  SELECT content FROM ML.PREDICT(MODEL `TransactionCategoryModel`, (\n"
          + "    SELECT CONCAT('" + prompt + "', CASE WHEN TRIM(Description) = ''\n"
          + "    THEN 'Other' ELSE Description END) AS prompt\n"
          + "  ))\n"
          + ") WHERE TRUE";

      // Use partitioned update to batch update a large number of rows
      dbClient.executePartitionedUpdate(Statement.of(sql));
      System.out.println("Completed categorizing transactions");
    } catch (SpannerException e) {
      throw e;
    }
  }

Adicione outra instrução de caso no método main para categorizar:

        case "categorize":
          categorize(dbClient);
          break;

Por fim, adicione como usar a categorização ao método printUsageAndExit:

    System.out.println("  java -jar target/onlinebanking.jar categorize");
    System.out.println("      - Use AI to categorize transactions in the database.\n");

Salve as alterações que você fez em App.java.

Recrie o aplicativo:

mvn package

Categorize as transações no banco de dados executando o comando categorize:

java -jar target/onlinebanking.jar categorize

Saída esperada:

Categorizing transactions...
Completed categorizing transactions

No Spanner Studio, execute a instrução Preview Data para a tabela TransactionLedger. A coluna Category agora vai ser preenchida para todas as linhas.

Conferir dados categorizados

Agora que categorizamos as transações, podemos usar essas informações para consultas internas ou voltadas ao cliente. Na próxima etapa, vamos descobrir quanto um determinado cliente gasta em uma categoria durante o mês.

Resumo

Nesta etapa, você usou um modelo pré-treinado para realizar a categorização dos seus dados com tecnologia de IA.

A seguir

Em seguida, você vai usar a tokenização para realizar pesquisas imprecisas e de texto completo.

7. Consultar usando a pesquisa de texto completo

Adicionar o código da consulta

O Spanner oferece muitas consultas de pesquisa de texto completo. Nesta etapa, você vai realizar uma pesquisa de correspondência exata, depois uma pesquisa aproximada e uma de texto completo.

Abra App.java e comece substituindo as importações:

package com.google.codelabs;

import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
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.Key;
import com.google.cloud.spanner.ReadOnlyTransaction;
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.Struct;
import com.google.cloud.spanner.TimestampBound;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;

Em seguida, adicione os métodos de consulta:

  // Get current account balance(s) by customer
  public static void getBalance(DatabaseClient dbClient, long customerId) {
    String query = "SELECT AccountId, Balance\n"
        + "FROM Accounts\n"
        + "WHERE CustomerId = @customerId";
    Statement statement = Statement.newBuilder(query)
        .bind("customerId").to(customerId)
        .build();

    // Ignore ongoing transactions, use stale reads as seconds-old data is sufficient
    TimestampBound stalenessBound = TimestampBound.ofMaxStaleness(5, TimeUnit.SECONDS);
    try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction(stalenessBound);
        ResultSet resultSet = transaction.executeQuery(statement);) {
      System.out.println("Account balances for customer " + customerId + ":");
      while (resultSet.next()) {
        System.out.println("  Account " + resultSet.getLong("AccountId") + ": "
            + resultSet.getBigDecimal("Balance"));
      }
    }
  }

  // Find customers by email
  public static void findCustomers(DatabaseClient dbClient, String email) {
    // Query using fuzzy search (ngrams) to allow for spelling mistakes
    String query = "SELECT CustomerId, Email\n"
        + "FROM Customers\n"
        + "WHERE SEARCH_NGRAMS(EmailTokens, @email)\n"
        + "ORDER BY SCORE_NGRAMS(EmailTokens, @email) DESC\n"
        + "LIMIT 10";
    Statement statement = Statement.newBuilder(query)
        .bind("email").to(email)
        .build();

    try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction();
        ResultSet resultSet = transaction.executeQuery(statement)) {
      System.out.println("Customer emails matching " + email + " (top 10 matches):");
      while (resultSet.next()) {
        System.out.println("  Customer " + resultSet.getLong("CustomerId") + ": "
            + resultSet.getString("Email"));
      }
    }
  }

  // Get total monthly spending for a customer by category
  public static void getSpending(DatabaseClient dbClient, long customerId, String category) {
    // Query category using full-text search
    String query = "SELECT SUM(Amount) as TotalSpending\n"
        + "FROM TransactionLedger t\n"
        + "JOIN Accounts a\n"
        + "  ON t.AccountId = a.AccountId\n"
        + "WHERE t.TransactionType = 'Debit'\n"
        + "  AND a.CustomerId = @customerId\n"
        + "  AND t.Timestamp >= TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL -30 DAY)\n"
        + "  AND (SEARCH(t.CategoryTokens, @category) OR SEARCH(t.DescriptionTokens, @category))";
    Statement statement = Statement.newBuilder(query)
        .bind("customerId").to(customerId)
        .bind("category").to(category)
        .build();

    try (ReadOnlyTransaction transaction = dbClient.singleUseReadOnlyTransaction();
        ResultSet resultSet = transaction.executeQuery(statement);) {
      System.out.println("Total spending for customer " + customerId + " under category "
          + category + ":");
      while (resultSet.next()) {
        BigDecimal totalSpending = BigDecimal.ZERO;
        if (!resultSet.isNull("TotalSpending")) {
          totalSpending = resultSet.getBigDecimal("TotalSpending");
        }
        System.out.println("  " + totalSpending);
      }
    }
  }

Adicione outra instrução case no método main para a consulta:

        case "query":
          String queryType = (args.length >= 2) ? args[1] : "";
          if (queryType.equals("balance")) {
            long customerId = (args.length >= 3) ? Long.parseLong(args[2]) : 1L;
            getBalance(dbClient, customerId);
          } else if (queryType.equals("email")) {
            String email = (args.length >= 3) ? args[2] : "";
            findCustomers(dbClient, email);
          } else if (queryType.equals("spending")) {
            long customerId = (args.length >= 3) ? Long.parseLong(args[2]) : 1L;
            String category = (args.length >= 4) ? args[3] : "";
            getSpending(dbClient, customerId, category);
          } else {
            printUsageAndExit();
          }
          break;

Por fim, adicione como usar os comandos de consulta ao método printUsageAndExit:

    System.out.println("  java -jar target/onlinebanking.jar query balance 1");
    System.out.println("      - Query customer account balance(s) by customer id.\n");
    System.out.println("  java -jar target/onlinebanking.jar query email madi");
    System.out.println("      - Find customers by email using fuzzy search.\n");
    System.out.println("  java -jar target/onlinebanking.jar query spending 1 groceries");
    System.out.println("      - Query customer spending by customer id and category using "
        + "full-text search.\n");

Salve as alterações que você fez em App.java.

Recrie o aplicativo:

mvn package

Realizar uma pesquisa de correspondência exata para saldos de contas de clientes

Uma consulta de correspondência exata procura linhas correspondentes que correspondam exatamente a um termo.

Para melhorar o desempenho, um índice já foi adicionado quando você criou o banco de dados e o esquema:

  "CREATE INDEX AccountsByCustomer\n"
          + "ON Accounts (CustomerId)",

O método getBalance usa implicitamente esse índice para encontrar clientes que correspondem ao customerId fornecido e também faz junções em contas pertencentes a esse cliente.

Confira como a consulta fica quando é executada diretamente no Spanner Studio: consultar o saldo manualmente

Para listar os saldos da conta do cliente 1, execute o comando:

java -jar target/onlinebanking.jar query balance 1

Saída esperada:

Account balances for customer 1:
  Account 1: 9875.25
  Account 7: 9900
  Account 110: 38200

Há 100 clientes, então você também pode consultar qualquer um dos outros saldos de conta de cliente especificando um ID de cliente diferente:

java -jar target/onlinebanking.jar query balance 5
java -jar target/onlinebanking.jar query balance 10
java -jar target/onlinebanking.jar query balance 99

Realizar uma pesquisa aproximada nos e-mails dos clientes

As pesquisas aproximadas permitem encontrar correspondências aproximadas para termos de pesquisa, incluindo variações ortográficas e erros de digitação.

Um índice de n-gramas já foi adicionado quando você criou o banco de dados e o esquema:

CREATE TABLE Customers (
  ...
  EmailTokens TOKENLIST AS (TOKENIZE_SUBSTRING(Email,
    ngram_size_min=>2,
    ngram_size_max=>3,
    relative_search_types=>["all"])) HIDDEN,
) PRIMARY KEY(CustomerId);

CREATE SEARCH INDEX CustomersFuzzyEmail ON Customers(EmailTokens);

O método findCustomers usa SEARCH_NGRAMS e SCORE_NGRAMS para consultar esse índice e encontrar clientes por e-mail. Como a coluna de e-mail foi tokenizada em n-gramas, essa consulta pode conter erros de ortografia e ainda retornar uma resposta correta. Os resultados são ordenados com base na melhor correspondência.

Encontre endereços de e-mail de clientes correspondentes que contêm madi executando o comando:

java -jar target/onlinebanking.jar query email madi

Saída esperada:

Customer emails matching madi (top 10 matches):
  Customer 39: madison.perez@example.com
  Customer 64: mason.gray@example.com
  Customer 91: mabel.alexander@example.com

Essa resposta mostra as correspondências mais próximas que incluem madi ou uma string semelhante, em ordem de classificação.

Confira como a consulta fica se for executada diretamente no Spanner Studio: pesquisando manualmente por madi

A pesquisa imprecisa também pode ajudar com erros de ortografia, como erros de emily:

java -jar target/onlinebanking.jar query email emily
java -jar target/onlinebanking.jar query email emliy
java -jar target/onlinebanking.jar query email emilee

Saída esperada:

Customer emails matching emliy (top 10 matches):
  Customer 31: emily.lopez@example.com

Em cada caso, o e-mail do cliente esperado é retornado como a correspondência principal.

O recurso de pesquisa de texto completo do Spanner é usado para recuperar registros com base em palavras-chave ou frases. Ele pode corrigir erros de ortografia ou pesquisar sinônimos.

Um índice de pesquisa de texto completo já foi adicionado quando você criou o banco de dados e o esquema:

CREATE TABLE TransactionLedger (
  ...
  CategoryTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Category)) HIDDEN,
  DescriptionTokens TOKENLIST AS (TOKENIZE_FULLTEXT(Description)) HIDDEN,
) PRIMARY KEY(AccountId, TransactionId),
  INTERLEAVE IN PARENT Accounts ON DELETE CASCADE;

CREATE SEARCH INDEX TransactionLedgerTextSearch ON TransactionLedger(CategoryTokens, DescriptionTokens);

O método getSpending usa a função de pesquisa de texto completo SEARCH para corresponder a esse índice. Ele procura todos os gastos (débitos) nos últimos 30 dias para o ID do cliente.

Para conferir o gasto total do último mês do cliente 1 na categoria groceries, execute o comando:

java -jar target/onlinebanking.jar query spending 1 groceries

Saída esperada:

Total spending for customer 1 under category groceries:
  50

Você também pode encontrar os gastos em outras categorias (que foram categorizadas em uma etapa anterior) ou usar um ID de cliente diferente:

java -jar target/onlinebanking.jar query spending 1 transportation
java -jar target/onlinebanking.jar query spending 1 restaurants
java -jar target/onlinebanking.jar query spending 12 entertainment

Resumo

Nesta etapa, você realizou consultas de correspondência exata, além de pesquisas de texto completo e aproximadas.

A seguir

Em seguida, você vai integrar o Spanner ao Google BigQuery para realizar consultas federadas, permitindo combinar os dados do Spanner em tempo real com os dados do BigQuery.

8. Executar consultas federadas com o BigQuery

Crie o conjunto de dados do BigQuery

Nesta etapa, você vai reunir dados do BigQuery e do Spanner usando consultas federadas.

Para fazer isso, na linha de comando do Cloud Shell, primeiro crie um conjunto de dados MarketingCampaigns:

bq mk --location=us-central1 MarketingCampaigns

Saída esperada:

Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.

E uma tabela CustomerSegments no conjunto de dados:

bq mk --table MarketingCampaigns.CustomerSegments CampaignId:STRING,CampaignName:STRING,CustomerId:INT64

Saída esperada:

Table '<PROJECT_ID>:MarketingCampaigns.CustomerSegments' successfully created.

Em seguida, faça uma conexão do BigQuery com o Spanner:

bq mk --connection \
  --connection_type=CLOUD_SPANNER \
  --properties="{\"database\": \"projects/$GOOGLE_CLOUD_PROJECT/instances/cloudspanner-onlinebanking/databases/onlinebanking\", \"useParallelism\": true, \"useDataBoost\": true}" \
  --location=us-central1 \
  spanner-connection

Saída esperada:

Connection <PROJECT_NUMBER>.us-central1.spanner-connection successfully created

Por fim, adicione alguns clientes à tabela do BigQuery que podem ser agrupados com nossos dados do Spanner:

bq query --use_legacy_sql=false '
INSERT INTO MarketingCampaigns.CustomerSegments (CampaignId, CampaignName, CustomerId)
VALUES
  ("campaign1", "Spring Promotion", 1),
  ("campaign1", "Spring Promotion", 3),
  ("campaign1", "Spring Promotion", 5),
  ("campaign1", "Spring Promotion", 7),
  ("campaign1", "Spring Promotion", 9),
  ("campaign1", "Spring Promotion", 11)'

Saída esperada:

Waiting on bqjob_r76a7ce76c5ec948f_0000019644bda052_1 ... (0s) Current status: DONE
Number of affected rows: 6

Para verificar se os dados estão disponíveis, consulte o BigQuery:

bq query --use_legacy_sql=false "SELECT * FROM MarketingCampaigns.CustomerSegments"

Saída esperada:

+------------+------------------+------------+
| CampaignId |   CampaignName   | CustomerId |
+------------+------------------+------------+
| campaign1  | Spring Promotion |          1 |
| campaign1  | Spring Promotion |          5 |
| campaign1  | Spring Promotion |          7 |
| campaign1  | Spring Promotion |          9 |
| campaign1  | Spring Promotion |         11 |
| campaign1  | Spring Promotion |          3 |
+------------+------------------+------------+

Esses dados no BigQuery representam dados que foram adicionados em vários fluxos de trabalho do banco. Por exemplo, essa pode ser a lista de clientes que abriram contas recentemente ou se inscreveram em uma promoção de marketing. Para determinar a lista de clientes que queremos segmentar na nossa campanha de marketing, precisamos consultar esses dados no BigQuery e também os dados em tempo real no Spanner. Uma consulta federada permite fazer isso em uma única consulta.

Executar uma consulta federada com o BigQuery

Em seguida, vamos adicionar um método ao aplicativo para chamar EXTERNAL_QUERY e realizar a consulta federada. Isso permite mesclar e analisar dados de clientes no BigQuery e no Spanner, como identificar quais clientes atendem aos critérios da nossa campanha de marketing com base nos gastos recentes.

Abra App.java e comece substituindo as importações:

package com.google.codelabs;

import java.io.FileReader;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.connection.v1.ConnectionName;
import com.google.cloud.bigquery.JobException;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableResult;
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.Key;
import com.google.cloud.spanner.ReadOnlyTransaction;
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.Struct;
import com.google.cloud.spanner.TimestampBound;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.opencsv.CSVReader;

Em seguida, adicione o método campaign:

  // Get customers for quarterly marketing campaign in BigQuery using Spanner data
  public static void campaign(BigQuery bq, DatabaseId db, String location, String campaignId,
      int threshold) {
    // The BigQuery dataset, table, and Spanner connection must already exist for this to succeed
    ConnectionName connection = ConnectionName.of(db.getInstanceId().getProject(), location,
        "spanner-connection");

    // Use a federated query to bring Spanner data into BigQuery
    String bqQuery = "SELECT cs.CampaignName, c.CustomerId, c.FullName, t.TotalSpending\n"
        + "FROM MarketingCampaigns.CustomerSegments cs\n"
        + "JOIN EXTERNAL_QUERY('" + connection.toString() + "',\n"
        + "  \"SELECT t.AccountId, SUM(t.Amount) AS TotalSpending"
        + "   FROM TransactionLedger t"
        + "   WHERE t.Timestamp >= TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL -90 DAY)"
        + "   GROUP BY t.AccountId"
        + "   HAVING SUM(t.Amount) > " + threshold + "\"\n"
        + ") t ON cs.CustomerId = t.AccountId\n"
        + "JOIN EXTERNAL_QUERY('" + connection.toString() + "',\n"
        + "  \"SELECT CustomerId, FullName"
        + "   FROM Customers\"\n"
        + ") c ON c.CustomerId = cs.CustomerId\n"
        + "WHERE cs.CampaignId = '" + campaignId + "'";
    try {
      QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(bqQuery).build();
      TableResult results = bq.query(queryConfig);

      System.out.println("Customers for campaign (" + campaignId + "):");
      results.iterateAll().forEach(row -> {
        System.out.println("  " + row.get("FullName").getStringValue()
            + " (" + row.get("CustomerId").getStringValue() + ")");
      });
    } catch (JobException e) {
      throw (BigQueryException) e.getCause();
    } catch (InterruptedException e) {
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }

Adicione outra instrução de caso no método main para a campanha:

        case "campaign":
          String campaignId = (args.length >= 2) ? args[1] : "";
          int threshold = (args.length >= 3) ? Integer.parseInt(args[2]) : 5000;
          campaign(bigquery, db, location, campaignId, threshold);
          break;

Por fim, adicione como usar a campanha ao método printUsageAndExit:

    System.out.println("  java -jar target/onlinebanking.jar campaign campaign1 5000");
    System.out.println("      - Use Federated Queries (BigQuery) to find customers that match a "
        + "marketing campaign by name based on a recent spending threshold.\n");

Salve as alterações que você fez em App.java.

Recrie o aplicativo:

mvn package

Execute uma consulta federada para determinar os clientes que devem ser incluídos na campanha de marketing (campaign1) se tiverem gasto pelo menos $5000 nos últimos três meses, executando o comando campaign:

java -jar target/onlinebanking.jar campaign campaign1 5000

Saída esperada:

Customers for campaign (campaign1):
  Alice Smith (1)
  Eve Davis (5)
  Kelly Thomas (11)

Agora podemos segmentar esses clientes com ofertas ou recompensas exclusivas.

Ou podemos procurar um número maior de clientes que alcançaram um limite de gastos menor nos últimos três meses:

java -jar target/onlinebanking.jar campaign campaign1 2500

Saída esperada:

Customers for campaign (campaign1):
  Alice Smith (1)
  Charlie Williams (3)
  Eve Davis (5)
  Ivy Taylor (9)
  Kelly Thomas (11)

Resumo

Nesta etapa, você executou consultas federadas do BigQuery que trouxeram dados do Spanner em tempo real.

A seguir

Em seguida, você pode limpar os recursos criados para este codelab para evitar cobranças.

9. Limpeza (opcional)

Esta etapa é opcional. Se você quiser continuar testando sua instância do Spanner, não é necessário limpá-la no momento. No entanto, o projeto que você está usando vai continuar sendo cobrado pela instância. Se você não precisar mais dessa instância, exclua-a para evitar cobranças. Além da instância do Spanner, este codelab também criou um conjunto de dados e uma conexão do BigQuery, que precisam ser limpos quando não forem mais necessários.

Exclua a instância do Spanner:

gcloud spanner instances delete cloudspanner-onlinebanking

Confirme que você quer continuar (digite Y):

Delete instance [cloudspanner-onlinebanking]. Are you sure?

Do you want to continue (Y/n)?

Exclua a conexão e o conjunto de dados do BigQuery:

bq rm --connection --location=us-central1 spanner-connection
bq rm -r MarketingCampaigns

Confirme a exclusão do conjunto de dados do BigQuery (digite Y):

rm: remove dataset '<PROJECT_ID>:MarketingCampaigns'? (y/N)

10. Parabéns

🚀 Você criou uma nova instância do Cloud Spanner, criou um banco de dados vazio, carregou dados de amostra, realizou operações e consultas avançadas e excluiu a instância do Cloud Spanner (opcional).

O que aprendemos

  • Como configurar uma instância do Spanner.
  • Como criar um banco de dados e tabelas.
  • Como carregar dados nas tabelas do banco de dados do Spanner.
  • Como chamar modelos da Vertex AI usando o Spanner.
  • Como consultar o banco de dados do Spanner usando pesquisa aproximada e de texto completo.
  • Como executar consultas federadas no Spanner do BigQuery.
  • Como excluir sua instância do Spanner.

A seguir