Crea una aplicación de banca en línea con Spanner

Crea una aplicación de banca en línea con Spanner

Acerca de este codelab

subjectÚltima actualización: may 30, 2025
account_circleEscrito por Lucia Subatin, Jeremy Parr-Pearson

1. Descripción general

Spanner es un servicio de base de datos distribuido de forma global, escalable de forma horizontal y completamente administrado que es ideal para cargas de trabajo operativas relacionales y no relacionales. Además de sus funciones principales, Spanner ofrece funciones avanzadas potentes que permiten compilar aplicaciones inteligentes y basadas en datos.

En este codelab, se parte de los conceptos básicos de Spanner y se profundiza en el uso de sus integraciones avanzadas para mejorar tus capacidades de procesamiento y análisis de datos, con una aplicación de banca en línea como base.

Nos enfocaremos en tres funciones avanzadas clave:

  • Integración en Vertex AI: Descubre cómo integrar Spanner sin problemas con Vertex AI, la plataforma de IA de Google Cloud. Aprenderás a invocar modelos de Vertex AI directamente desde las consultas de SQL de Spanner, lo que habilitará poderosas transformaciones y predicciones en la base de datos, lo que permitirá que nuestra aplicación bancaria categorice automáticamente las transacciones para casos de uso como el seguimiento del presupuesto y la detección de anomalías.
  • Búsqueda en el texto completo: Obtén información para implementar la funcionalidad de búsqueda en el texto completo en Spanner. Explorarás cómo indexar datos de texto y escribir consultas eficientes para realizar búsquedas basadas en palabras clave en tus datos operativos, lo que permitirá un descubrimiento de datos potente, como encontrar clientes de forma eficiente por dirección de correo electrónico en nuestro sistema bancario.
  • Consultas federadas de BigQuery: Explora cómo aprovechar las capacidades de consulta federada de Spanner para consultar directamente los datos que se encuentran en BigQuery. Esto te permite combinar los datos operativos en tiempo real de Spanner con los conjuntos de datos analíticos de BigQuery para obtener estadísticas y informes integrales sin duplicar los datos ni realizar procesos ETL complejos, lo que potencia varios casos de uso en nuestra aplicación bancaria, como campañas de marketing segmentadas, ya que combina datos de clientes en tiempo real con tendencias históricas más amplias de BigQuery.

Qué aprenderás

  • Cómo configurar una instancia de Spanner
  • Cómo crear una base de datos y tablas.
  • Cómo cargar datos en tus tablas de base de datos de Spanner
  • Cómo llamar a modelos de Vertex AI desde Spanner
  • Cómo consultar tu base de datos de Spanner con la búsqueda parcial y la búsqueda en el texto completo
  • Cómo realizar consultas federadas en Spanner desde BigQuery
  • Cómo borrar tu instancia de Spanner.

Requisitos

  • Un proyecto de Google Cloud conectado a una cuenta de facturación
  • Un navegador web, como Chrome o Firefox.

2. Configuración y requisitos

Crea un proyecto

Si ya tienes un proyecto de Google Cloud con la facturación habilitada, haz clic en el menú desplegable de selección de proyectos en la parte superior izquierda de la consola:

Proyecto existente

Con un proyecto seleccionado, ve a Habilita las APIs requeridas.

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

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

Proyecto nuevo

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

Diálogo del proyecto

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

Recuerda el ID del proyecto, que es un nombre único en todos los proyectos de Google Cloud. Se mencionará más adelante en este codelab como PROJECT_ID.

Detalles del proyecto

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

Facturación del proyecto

Los precios de Spanner se documentan aquí. Los demás costos asociados con otros recursos se documentarán en sus páginas de precios específicas.

Los usuarios nuevos de Google Cloud Platform son aptos para obtener una prueba gratuita de USD 300.

Configuración de Google Cloud Shell

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.

Para activar Cloud Shell desde la consola de Cloud, solo haz clic en Activar Cloud Shell Ícono de Cloud Shell (el aprovisionamiento y la conexión al entorno deberían llevar solo unos minutos).

Cloud Shell

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

Credentialed Accounts

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

Resultado esperado:

[core]
project = <PROJECT_ID>

Si, por algún motivo, el proyecto no está configurado, 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 la consola de Cloud:

ID del proyecto

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

<PROJECT_ID>

Habilite las API necesarias

Habilita las APIs de Spanner, Vertex AI y BigQuery para tu proyecto:

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

Resumen

En este paso, configuraste tu proyecto si aún no tenías uno, activaste Cloud Shell y habilitaste las APIs requeridas.

Cuál es el próximo paso

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

3. Configura una instancia de Spanner

Crea la instancia de Spanner

En este paso, configurarás una instancia de Spanner para el codelab. Para ello, abre Cloud Shell y ejecuta el siguiente 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

Resultado esperado:

Creating instance...done.

Resumen

En este paso, creaste la instancia de Spanner.

Cuál es el próximo paso

A continuación, prepararás la aplicación inicial y crearás la base de datos y el esquema.

4. Crea una base de datos y un esquema

Prepara la solicitud inicial

En este paso, crearás la base de datos y el esquema a través del código.

Primero, crea una aplicación de Java llamada onlinebanking con 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

Realiza la confirmación y copia los archivos de datos que agregaremos a la base de datos (consulta aquí para ver el repositorio de código):

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

Navega a la carpeta de la aplicación:

cd onlinebanking

Abre el archivo pom.xml de Maven. Agrega la sección de administración de dependencias para usar la BOM de Maven y administrar la versión de las bibliotecas de 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>

Así se verán el editor y el archivo: Cloud Shell

Asegúrate de que la sección dependencies incluya las bibliotecas que usará la aplicación:

<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 último, reemplaza los complementos de compilación para que la aplicación se empaquete en un archivo JAR ejecutable:

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

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

Ahora que las dependencias están listas, agregarás código a la app para crear un esquema, algunos índices (incluida la búsqueda) y un modelo de IA conectado a un extremo remoto. En este codelab, aprovecharás estos artefactos y agregarás más métodos a esta clase.

Abre App.java en onlinebanking/src/main/java/com/google/codelabs y reemplaza el contenido por el siguiente 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();
     
}
   
}
 
}
}

Guarda los cambios en App.java.

Observa las diferentes entidades que crea tu código y compila el archivo JAR de la aplicación:

mvn package

Resultado esperado:

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

Ejecuta la aplicación para ver la información de uso:

java -jar target/onlinebanking.jar

Resultado esperado:

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.

Crea la base de datos y el esquema

Configura las variables de entorno de la aplicación requeridas:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking

Ejecuta el comando create para crear la base de datos y el esquema:

java -jar target/onlinebanking.jar create

Resultado esperado:

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

Verifica el esquema en Spanner

En la consola de Spanner, navega a la instancia y la base de datos que acabas de crear.

Deberías ver las 3 tablas: Accounts, Customers y TransactionLedger.

Ver esquema

Esta acción crea el esquema de la base de datos, incluidas las tablas Accounts, Customers y TransactionLedger, junto con índices secundarios para la recuperación de datos optimizada y una referencia de modelo de Vertex AI.

Diagrama de relaciones de entidades

La tabla TransactionLedger se intercala en Cuentas para mejorar el rendimiento de las consultas de transacciones específicas de la cuenta a través de una mejor localidad de los datos.

Se implementaron índices secundarios (CustomersByEmail, CustomersFuzzyEmail, AccountsByCustomer, TransactionLedgerByAccountType, TransactionLedgerByCategory, TransactionLedgerTextSearch) para optimizar los patrones de acceso a datos comunes que se usan en este codelab, como las búsquedas de clientes por correo electrónico exacto y difuso, la recuperación de cuentas por cliente y la consulta y búsqueda eficientes de datos de transacciones.

TransactionCategoryModel aprovecha Vertex AI para habilitar llamadas SQL directas a un LLM, que se usa para la categorización dinámica de transacciones en este codelab.

Resumen

En este paso, creaste la base de datos y el esquema de Spanner.

Cuál es el próximo paso

A continuación, cargarás los datos de muestra de la aplicación.

5. Cargar datos

Ahora, agregarás la funcionalidad para cargar datos de muestra de archivos CSV en la base de datos.

Abre App.java y comienza por reemplazar las importaciones:

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;

Luego, agrega los métodos de inserción a la clase 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;
        });
  }

Agrega otra sentencia case en el método main para insertar dentro de 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 último, agrega cómo usar insert al 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");

Guarda los cambios que realizaste en App.java.

Vuelve a compilar la aplicación:

mvn package

Ejecuta el comando insert para insertar los datos de muestra:

java -jar target/onlinebanking.jar insert

Resultado esperado:

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

En la consola de Spanner, vuelve a Spanner Studio para ver tu instancia y base de datos. Luego, selecciona la tabla TransactionLedger y haz clic en “Datos” en la barra lateral para verificar que se hayan cargado los datos. Debería haber 200 filas en la tabla.

Ver datos

Resumen

En este paso, insertaste los datos de muestra en la base de datos.

Cuál es el próximo paso

A continuación, aprovecharás la integración de Vertex AI para categorizar automáticamente las transacciones bancarias directamente en Spanner SQL.

6. Categoriza datos con Vertex AI

En este paso, aprovecharás la potencia de Vertex AI para categorizar automáticamente tus transacciones financieras directamente en Spanner SQL. Con Vertex AI, puedes elegir un modelo previamente entrenado existente o entrenar y, luego, implementar el tuyo. Consulta los modelos disponibles en Vertex AI Model Garden.

En este codelab, usaremos uno de los modelos de Gemini, Gemini Flash Lite. Esta versión de Gemini es rentable y, aun así, puede manejar la mayoría de las cargas de trabajo diarias.

Actualmente, tenemos varias transacciones financieras que nos gustaría categorizar (groceries, transportation, etc.) según la descripción. Para ello, podemos registrar un modelo en Spanner y, luego, usar ML.PREDICT para llamar al modelo de IA.

En nuestra aplicación bancaria, es posible que deseemos categorizar las transacciones para obtener estadísticas más detalladas sobre el comportamiento de los clientes, de modo que podamos personalizar los servicios, detectar anomalías de manera más eficaz o brindarle al cliente la capacidad de hacer un seguimiento de su presupuesto mes a mes.

El primer paso ya se realizó cuando creamos la base de datos y el esquema, que crearon un modelo como este:

sentencia CREATE MODEL

A continuación, agregaremos un método a la aplicación para llamar a ML.PREDICT.

Abre App.java y agrega el 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;
    }
  }

Agrega otra sentencia case en el método main para categorizar:

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

Por último, agrega cómo usar categorize al método printUsageAndExit:

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

Guarda los cambios que realizaste en App.java.

Vuelve a compilar la aplicación:

mvn package

Ejecuta el comando categorize para categorizar las transacciones en la base de datos:

java -jar target/onlinebanking.jar categorize

Resultado esperado:

Categorizing transactions...
Completed categorizing transactions

En Spanner Studio, ejecuta la sentencia Preview Data para la tabla TransactionLedger. La columna Category ahora debería propagarse a todas las filas.

Cómo ver datos categorizados

Ahora que clasificamos las transacciones, podemos usar esta información para consultas internas o para los clientes. En un paso posterior, veremos cómo saber cuánto gasta un cliente determinado en una categoría durante el mes.

Resumen

En este paso, usaste un modelo previamente entrenado para realizar la categorización de tus datos con tecnología de IA.

Cuál es el próximo paso

A continuación, usarás la tokenización para realizar búsquedas difusas y de texto completo.

7. Cómo realizar consultas con la búsqueda en el texto completo

Agrega el código de consulta

Spanner proporciona muchas consultas de búsqueda de texto completo. En este paso, realizarás una búsqueda de concordancia exacta, una búsqueda aproximada y una búsqueda de texto completo.

Abre App.java y comienza por reemplazar las importaciones:

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;

Luego, agrega los 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);
      }
    }
  }

Agrega otra sentencia case al método main para la 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 último, agrega cómo usar los comandos de consulta al 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");

Guarda los cambios que realizaste en App.java.

Vuelve a compilar la aplicación:

mvn package

Realiza una búsqueda de concordancia exacta para los saldos de las cuentas de los clientes

Una búsqueda de concordancia exacta busca filas que coincidan exactamente con un término.

Para mejorar el rendimiento, ya se agregó un índice cuando creaste la base de datos y el esquema:

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

El método getBalance usa implícitamente este índice para encontrar clientes que coincidan con el customerId proporcionado y también se une a las cuentas que pertenecen a ese cliente.

Así se ve la consulta cuando se ejecuta directamente en Spanner Studio: Cómo consultar el saldo de forma manual

Ejecuta el siguiente comando para obtener una lista de los saldos de la cuenta del cliente 1:

java -jar target/onlinebanking.jar query balance 1

Resultado esperado:

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

Hay 100 clientes, por lo que también puedes consultar cualquiera de los otros saldos de las cuentas de cliente si especificas un 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

Realiza una búsqueda aproximada en los correos electrónicos de los clientes

Las búsquedas aproximadas permiten encontrar coincidencias aproximadas para los términos de búsqueda, incluidas las variaciones ortográficas y los errores tipográficos.

Ya se agregó un índice de n-gramas cuando creaste la base de datos y el 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);

El método findCustomers usa SEARCH_NGRAMS y SCORE_NGRAMS para consultar este índice y encontrar clientes por correo electrónico. Debido a que la columna de correo electrónico se tokenizó con n-gramas, esta consulta puede contener errores ortográficos y, aun así, mostrar una respuesta correcta. Los resultados se ordenan según la mejor coincidencia.

Ejecuta el siguiente comando para encontrar las direcciones de correo electrónico de los clientes que coincidan y contengan madi:

java -jar target/onlinebanking.jar query email madi

Resultado esperado:

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

Esta respuesta muestra las coincidencias más cercanas que incluyen madi, o una cadena similar, en orden de clasificación.

Esta es la apariencia de la consulta si se ejecuta directamente en Spanner Studio: búsqueda manual de madi

La búsqueda parcial también puede ayudar con errores ortográficos, como los errores 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

Resultado esperado:

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

En cada caso, el correo electrónico del cliente esperado se muestra como el hit principal.

La función de búsqueda de texto completo de Spanner se usa para recuperar registros en función de palabras clave o frases. Tiene la capacidad de corregir errores ortográficos o buscar sinónimos.

Ya se agregó un índice de búsqueda de texto completo cuando creaste la base de datos y el 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);

El método getSpending usa la función de búsqueda de texto completo SEARCH para hacer coincidir ese índice. Busca todos los gastos (débitos) de los últimos 30 días para el ID de cliente determinado.

Ejecuta el siguiente comando para obtener la inversión total del último mes del cliente 1 en la categoría groceries:

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

Resultado esperado:

Total spending for customer 1 under category groceries:
  50

También puedes encontrar la inversión en otras categorías (que clasificamos en un paso anterior) o usar un 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

Resumen

En este paso, realizaste consultas de concordancia exacta, así como búsquedas aproximadas y de texto completo.

Cuál es el próximo paso

A continuación, integrarás Spanner con Google BigQuery para realizar consultas federadas, lo que te permitirá combinar tus datos de Spanner en tiempo real con los datos de BigQuery.

8. Ejecuta consultas federadas con BigQuery

Crea el conjunto de datos de BigQuery

En este paso, reunirás datos de BigQuery y Spanner mediante el uso de consultas federadas.

Para ello, en la línea de comandos de Cloud Shell, primero crea un conjunto de datos MarketingCampaigns:

bq mk --location=us-central1 MarketingCampaigns

Resultado esperado:

Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.

Y una tabla CustomerSegments en el conjunto de datos:

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

Resultado esperado:

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

A continuación, establece una conexión de BigQuery a 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

Resultado esperado:

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

Por último, agrega algunos clientes a la tabla de BigQuery que se puedan unir con nuestros datos de 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)'

Resultado esperado:

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

Para verificar que los datos estén disponibles, consulta BigQuery:

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

Resultado esperado:

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

Estos datos en BigQuery representan datos que se agregaron a través de varios flujos de trabajo del banco. Por ejemplo, esta puede ser la lista de clientes que abrieron cuentas recientemente o se registraron para una promoción de marketing. Para determinar la lista de clientes a los que queremos segmentar nuestra campaña de marketing, debemos consultar estos datos en BigQuery y los datos en tiempo real en Spanner. Una consulta federada nos permite hacer esto en una sola consulta.

Ejecuta una consulta federada con BigQuery

A continuación, agregaremos un método a la aplicación para llamar a EXTERNAL_QUERY y realizar la consulta federada. Esto permitirá unir y analizar los datos de los clientes en BigQuery y Spanner, como identificar qué clientes cumplen con los criterios de nuestra campaña de marketing en función de sus gastos recientes.

Abre App.java y comienza por reemplazar las importaciones:

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;

Luego, agrega el 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);
    }
  }

Agrega otra sentencia case en el método main de la campaña:

        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 último, agrega cómo usar la campaña al 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");

Guarda los cambios que realizaste en App.java.

Vuelve a compilar la aplicación:

mvn package

Ejecuta una consulta federada para determinar los clientes que se deben incluir en la campaña de marketing (campaign1) si invirtieron al menos $5000 en los últimos 3 meses. Para ello, ejecuta el comando campaign:

java -jar target/onlinebanking.jar campaign campaign1 5000

Resultado esperado:

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

Ahora podemos segmentar nuestros anuncios para estos clientes con ofertas o recompensas exclusivas.

También podemos buscar una cantidad más amplia de clientes que hayan alcanzado un umbral de inversión menor en los últimos 3 meses:

java -jar target/onlinebanking.jar campaign campaign1 2500

Resultado esperado:

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

Resumen

En este paso, ejecutaste correctamente consultas federadas desde BigQuery que extrajeron datos de Spanner en tiempo real.

Cuál es el próximo paso

A continuación, puedes limpiar los recursos creados para este codelab para evitar cargos.

9. Limpieza (opcional)

Este paso es opcional. Si quieres seguir experimentando con tu instancia de Spanner, no es necesario que la limpies en este momento. Sin embargo, se seguirá cobrando por la instancia al proyecto que uses. Si ya no necesitas esta instancia, debes borrarla en este momento para evitar estos cargos. Además de la instancia de Spanner, este codelab también creó un conjunto de datos y una conexión de BigQuery que se deben limpiar cuando ya no sean necesarios.

Borra la instancia de Spanner:

gcloud spanner instances delete cloudspanner-onlinebanking

Confirma que quieres continuar (escribe Y):

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

Do you want to continue (Y/n)?

Borra la conexión y el conjunto de datos de BigQuery:

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

Confirma la eliminación del conjunto de datos de BigQuery (ingresa Y):

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

10. Felicitaciones

🚀 Creaste una instancia nueva de Cloud Spanner, una base de datos vacía, cargaste datos de muestra, realizaste operaciones y consultas avanzadas, y (de manera opcional) borraste la instancia de Cloud Spanner.

Temas abordados

  • Cómo configurar una instancia de Spanner
  • Cómo crear una base de datos y tablas.
  • Cómo cargar datos en tus tablas de base de datos de Spanner
  • Cómo llamar a modelos de Vertex AI desde Spanner
  • Cómo consultar tu base de datos de Spanner con la búsqueda parcial y la búsqueda en el texto completo
  • Cómo realizar consultas federadas en Spanner desde BigQuery
  • Cómo borrar tu instancia de Spanner.

Próximos pasos