Tworzenie aplikacji bankowości internetowej za pomocą usługi Spanner

1. Omówienie

Spanner to w pełni zarządzana, globalnie rozproszona usługa bazy danych, którą można skalować w poziomie. Świetnie sprawdza się w przypadku zarówno relacyjnych, jak i nierelacyjnych zadań operacyjnych. Oprócz podstawowych funkcji Spanner oferuje zaawansowane funkcje, które umożliwiają tworzenie inteligentnych aplikacji opartych na danych.

Ten warsztat programowania opiera się na podstawowej znajomości usługi Spanner i zajmuje się wykorzystaniem jej zaawansowanych integracji w celu zwiększenia możliwości przetwarzania danych i analizy na przykładzie aplikacji bankowej online.

Skupimy się na 3 głównych zaawansowanych funkcjach:

  • Integracja z Vertex AI: dowiedz się, jak bezproblemowo zintegrować Spannera z platformą AI Google Cloud – Vertex AI. Dowiedz się, jak wywoływać modele Vertex AI bezpośrednio z zapytań Spanner SQL, aby umożliwić zaawansowane przekształcenia i prognozy w bazie danych, co pozwoli aplikacji bankowej automatycznie kategoryzować transakcje na potrzeby takich zastosowań, jak śledzenie budżetu i wykrywanie anomalii.
  • Wyszukiwanie pełnotekstowe: dowiedz się, jak zaimplementować w usłudze Spanner funkcję wyszukiwania pełnotekstowego. Poznasz indeksowanie danych tekstowych i tworzenie wydajnych zapytań, które umożliwiają wyszukiwanie według słów kluczowych w danych operacyjnych. Umożliwi to skuteczne odkrywanie danych, np. szybkie znajdowanie klientów według adresu e-mail w naszym systemie bankowym.
  • Sfederowane zapytania do BigQuery:dowiedz się, jak korzystać z funkcji sfederowanych zapytań w Spanner do bezpośredniego wysyłania zapytań do danych znajdujących się w BigQuery. Dzięki temu możesz łączyć dane operacyjne w czasie rzeczywistym z BigQuery ze zbiorami danych analitycznych w BigQuery, aby uzyskiwać kompleksowe statystyki i raporty bez dublowania danych ani skomplikowanych procesów ETL. Możesz też wykorzystywać różne przypadki użycia w naszej aplikacji bankowej, np. ukierunkowane kampanie marketingowe, łącząc dane o klientach w czasie rzeczywistym z szerszymi trendami historycznymi z BigQuery.

Czego się nauczysz

  • Jak skonfigurować instancję usługi Spanner
  • Jak utworzyć bazę danych i tabele.
  • Jak wczytywać dane do tabel bazy danych Spanner.
  • Jak wywoływać modele Vertex AI z Spannera.
  • Jak wysyłać zapytania do bazy danych Spanner przy użyciu wyszukiwania nieprecyzyjnego i wyszukiwania pełnotekstowego.
  • Jak wykonywać zapytania sfederowane do usługi Spanner z BigQuery.
  • Jak usunąć instancję usługi Spanner

Czego potrzebujesz

  • projekt Google Cloud połączony z kontem rozliczeniowym.
  • przeglądarkę internetową, taką jak Chrome czy Firefox;

2. Konfiguracja i wymagania

Utwórz projekt

Jeśli masz już projekt Google Cloud z włączonymi płatnościami, w lewym górnym rogu konsoli kliknij menu wyboru projektu:

Istniejący projekt

Po wybraniu projektu przejdź do sekcji Włączanie wymaganych interfejsów API.

Jeśli nie masz jeszcze konta Google (Gmail lub Google Apps), utwórz je. Zaloguj się w konsoli Google Cloud Platform (console.cloud.google.com) i utwórz nowy projekt.

Aby utworzyć nowy projekt, kliknij w wyświetlonym oknie przycisk „NOWY PROJEKT”:

Nowy projekt

Jeśli nie masz jeszcze projektu, wyświetli się okno podobne do tego:

Okno projektu

W kolejnych oknach tworzenia projektu możesz podać szczegóły nowego projektu.

Zapamiętaj identyfikator projektu, który jest unikalną nazwą wśród wszystkich projektów Google Cloud. W dalszej części tego ćwiczenia będzie on nazywany PROJECT_ID.

Szczegóły projektu

Następnie, jeśli jeszcze tego nie zrobiono, musisz włączyć płatności w Konsoli Google dla deweloperów, aby korzystać z zasobów Google Cloud i włączyć interfejsy Spanner API, Vertex AI API, BigQuery APIBigQuery Connection API.

Rozliczenia projektu

Informacje o cenach usługi Spanner znajdziesz tutaj. Inne koszty związane z innymi zasobami będą dokumentowane na odpowiednich stronach cen.

Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Konfiguracja Google Cloud Shell

W tym laboratorium kodu będziemy używać Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.

Ta maszyna wirtualna oparta na Debianie zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie poprawia wydajność sieci i uwierzytelnianie. Oznacza to, że do tego ćwiczenia będziesz potrzebować tylko przeglądarki.

Aby aktywować Cloud Shell z Konsoli Cloud, kliknij Aktywuj Cloud Shell Ikona Cloud Shell (udostępnienie środowiska i połączenie z nim powinno zająć tylko kilka chwil).

Cloud Shell

Po połączeniu z Cloud Shell powinieneś zobaczyć, że jesteś już uwierzytelniony i że projekt jest już ustawiony na PROJECT_ID.

gcloud auth list

Oczekiwany wynik:

Credentialed Accounts

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

Oczekiwany wynik:

[core]
project = <PROJECT_ID>

Jeśli z jakiegoś powodu projekt nie jest ustawiony, uruchom to polecenie:

gcloud config set project <PROJECT_ID>

Szukasz urządzenia PROJECT_ID? Sprawdź, jakiego identyfikatora użyto w procesie konfiguracji, lub odszukaj go w panelu Cloud Console:

Identyfikator projektu

Cloud Shell domyślnie ustawia też niektóre zmienne środowiskowe, co może być przydatne podczas wykonywania kolejnych poleceń.

echo $GOOGLE_CLOUD_PROJECT

Oczekiwany wynik:

<PROJECT_ID>

Włączanie wymaganych interfejsów API

Włącz w projekcie interfejsy Spanner, Vertex AI i BigQuery API:

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

Podsumowanie

Na tym etapie skonfigurowałeś projekt (jeśli go jeszcze nie miałeś), aktywowałeś Cloud Shell i włączyłeś wymagane interfejsy API.

Następny krok

Następnie skonfiguruj instancję usługi Spanner.

3. Konfigurowanie instancji usługi Spanner

Tworzenie instancji usługi Spanner

Na tym etapie skonfigurujesz instancję Spanner do wykorzystania w tym ćwiczeniu. Aby to zrobić, otwórz Cloud Shell i uruchom to polecenie:

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

Oczekiwany wynik:

Creating instance...done.

Podsumowanie

W tym kroku utworzysz instancję usługi Spanner.

Następny krok

Następnie przygotujesz początkową aplikację i utworzysz bazę danych oraz schemat.

4. Tworzenie bazy danych i schematu

Przygotowywanie początkowej aplikacji

W tym kroku utworzysz bazę danych i schemat za pomocą kodu.

Najpierw utwórz aplikację Java o nazwie onlinebanking za pomocą 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

Sprawdź i skopiuj pliki danych, które dodamy do bazy danych (repozytorium kodu znajdziesz tutaj):

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

Otwórz folder aplikacji:

cd onlinebanking

Otwórz plik Maven pom.xml. Dodaj sekcję zarządzania zależnościami, aby używać pliku BOM Maven do zarządzania wersjami bibliotek 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>

Edytor i plik będą wyglądać tak: Cloud Shell

Upewnij się, że sekcja dependencies zawiera biblioteki, których będzie używać aplikacja:

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

Na koniec zastąp wtyczki kompilacji, aby aplikacja została spakowana do uruchomialnego pliku JAR:

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

Zapisz zmiany wprowadzone w pliku pom.xml, wybierając „Zapisz” w menu „Plik” w edytorze Cloud Shell lub naciskając Ctrl+S.

Teraz, gdy zależności są gotowe, dodaj do aplikacji kod, który utworzy schemat, indeksy (w tym wyszukiwanie) oraz model AI połączony z odległym punktem końcowym. W ramach tego ćwiczenia będziesz tworzyć te artefakty i dodawać do tej klasy kolejne metody.

Otwórz plik App.java w folderze onlinebanking/src/main/java/com/google/codelabs i zastąp jego zawartość tym kodem:

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

Zapisz zmiany w pliku App.java.

Zapoznaj się z różnymi elementami tworzonymi przez kod i utwórz plik JAR aplikacji:

mvn package

Oczekiwany wynik:

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

Uruchom aplikację, aby wyświetlić informacje o korzystaniu:

java -jar target/onlinebanking.jar

Oczekiwany wynik:

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.

Tworzenie bazy danych i schematu

Ustaw wymagane zmienne środowiskowe aplikacji:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking

Utwórz bazę danych i schemat, wykonując polecenie create:

java -jar target/onlinebanking.jar create

Oczekiwany wynik:

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

Sprawdzanie schematu w usłudze Spanner

W konsoli Spanner przejdź do utworzonej właśnie instancji i bazy danych.

Powinny się wyświetlić wszystkie 3 tabele: Accounts, CustomersTransactionLedger.

Wyświetl schemat

To działanie tworzy schemat bazy danych, w tym tabele Accounts, CustomersTransactionLedger, a także indeksy pomocnicze do optymalizacji pobierania danych i odniesienie do modelu Vertex AI.

Diagram relacji między elementami

Tabela TransactionLedger jest przeplatana w ramach kont, aby zwiększyć wydajność zapytań dotyczących transakcji dotyczących poszczególnych kont dzięki ulepszonym lokalnym danym.

Indeksy drugorzędne (CustomersByEmail, CustomersFuzzyEmail, AccountsByCustomer, TransactionLedgerByAccountType, TransactionLedgerByCategory, TransactionLedgerTextSearch) zostały zaimplementowane w celu optymalizacji typowych wzorców dostępu do danych używanych w tym laboratorium programistycznym, takich jak wyszukiwanie klientów według dokładnego lub nieprecyzyjnego adresu e-mail, pobieranie kont według klienta oraz wydajne wyszukiwanie i przeszukiwanie danych transakcji.

TransactionCategoryModel korzysta z Vertex AI, aby umożliwić bezpośrednie wywołania SQL do LLM, które w tym ćwiczeniu służy do dynamicznej kategoryzacji transakcji.

Podsumowanie

W tym kroku utworzysz bazę danych i schemat usługi Spanner.

Następny krok

Następnie wczytaj przykładowe dane aplikacji.

5. Wczytaj dane

Teraz dodasz funkcję wczytywania do bazy danych przykładowych danych z plików CSV.

Otwórz plik App.java i zacznij od zastąpienia importów:

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;

Następnie dodaj do klasy App metody wstawiania:

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

Dodaj do metody main kolejne instrukcje case, aby wstawić element w metodie 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;

Na koniec dodaj instrukcje korzystania z metody printUsageAndExit:

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

Zapisz zmiany wprowadzone w pliku App.java.

Utwórz aplikację ponownie:

mvn package

Aby wstawić przykładowe dane, uruchom polecenie insert:

java -jar target/onlinebanking.jar insert

Oczekiwany wynik:

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

W konsoli Spanner wróć do instancji i bazy danych w Spanner Studio. Następnie wybierz tabelę TransactionLedger i na pasku bocznym kliknij „Dane”, aby sprawdzić, czy dane zostały załadowane. Tabela powinna zawierać 200 wierszy.

Wyświetlanie danych

Podsumowanie

W tym kroku wstawiliśmy do bazy danych przykładowe dane.

Następny krok

Następnie użyjesz integracji Vertex AI, aby automatycznie kategoryzować transakcje bankowe bezpośrednio w Spanner SQL.

6. Kategoryzowanie danych za pomocą Vertex AI

W tym kroku wykorzystasz możliwości Vertex AI, aby automatycznie kategoryzować transakcje finansowe bezpośrednio w Spanner SQL. Korzystając z Vertex AI, możesz wybrać istniejący wytrenowany model lub wytrenować i wdrażać własny. Sprawdź dostępne modele w bazie modeli Vertex AI.

W tym ćwiczeniu użyjemy jednego z modeli Gemini, Gemini Flash Lite. Ta wersja Gemini jest opłacalna, a jednak nadal może obsługiwać większość codziennych zadań.

Obecnie mamy wiele transakcji finansowych, które chcemy pogrupować (groceries, transportation itd.) w zależności od opisu. Aby to zrobić, należy zarejestrować model w usłudze Spanner, a potem wywołać model AI za pomocą funkcji ML.PREDICT.

W naszej aplikacji bankowej możemy chcieć pogrupować transakcje, aby uzyskać bardziej szczegółowe informacje o zachowaniu klientów. Dzięki temu będziemy mogli personalizować usługi, skuteczniej wykrywać anomalie lub umożliwić klientom śledzenie ich budżetu w poszczególnych miesiącach.

Pierwszy krok został już wykonany, gdy utworzyliśmy bazę danych i schemat, które pozwoliły stworzyć taki model:

Instrukcja create model

Następnie dodamy do aplikacji metodę wywołania funkcji ML.PREDICT.

Otwórz App.java i dodaj metodę 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;
    }
  }

Dodaj kolejne instrukcje case w metodie main, aby pogrupować dane:

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

Na koniec dodaj do metody printUsageAndExit instrukcje korzystania z funkcji kategoryzowania:

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

Zapisz zmiany wprowadzone w pliku App.java.

Utwórz aplikację ponownie:

mvn package

Przypisz transakcje do kategorii w bazie danych, uruchamiając polecenie categorize:

java -jar target/onlinebanking.jar categorize

Oczekiwany wynik:

Categorizing transactions...
Completed categorizing transactions

W Spanner Studio wykonaj instrukcję Podgląd danych dla tabeli TransactionLedger. Kolumna Category powinna być teraz wypełniona we wszystkich wierszach.

Wyświetlanie pogrupowanych danych

Teraz, gdy transakcje zostały pogrupowane, możemy używać tych informacji do zapytań wewnętrznych lub dotyczących klientów. W następnym kroku zobaczysz, jak sprawdzić, ile dany klient wydał w danej kategorii w ciągu miesiąca.

Podsumowanie

W tym kroku wykorzystasz wytrenowany wcześniej model do kategoryzowania danych za pomocą AI.

Następny krok

Następnie użyjesz tokenizacji do przeprowadzenia wyszukiwania z użyciem zapytań nieprecyzyjnych i wyszukiwania pełnego tekstu.

7. Zapytanie z wykorzystaniem wyszukiwania pełnotekstowego

Dodawanie kodu zapytania

Spanner udostępnia wiele zapytań do wyszukiwania pełnotekstowego. W tym kroku przeprowadzisz wyszukiwanie z dopasowaniem ścisłym, a potem wyszukiwanie z dopasowaniem przybliżonym i wyszukiwanie pełnotekstowe.

Otwórz plik App.java i zacznij od zastąpienia importów:

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;

Następnie dodaj metody zapytań:

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

Dodaj kolejne instrukcje case w metodie main dla zapytania:

        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;

Na koniec dodaj do metody printUsageAndExit instrukcje korzystania z komend zapytań:

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

Zapisz zmiany wprowadzone w pliku App.java.

Utwórz aplikację ponownie:

mvn package

Przeprowadź wyszukiwanie ścisłego dopasowania salda na koncie klienta

Zapytanie z dopasowaniem ścisłym wyszukuje wiersze, które dokładnie pasują do danego hasła.

Aby zwiększyć wydajność, indeks został już dodany podczas tworzenia bazy danych i schematu:

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

Metoda getBalance używa tego indeksu do wyszukiwania klientów pasujących do podanego identyfikatora customerId, a także do złączania kont należących do tego klienta.

Oto jak wygląda zapytanie, gdy jest wykonywane bezpośrednio w Spanner Studio: ręcznie sprawdzać saldo

Aby wyświetlić salda na kontach klienta 1, uruchom to polecenie:

java -jar target/onlinebanking.jar query balance 1

Oczekiwany wynik:

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

Jest 100 klientów, więc możesz też zapytać o saldo dowolnego innego konta klienta, podając inny identyfikator klienta:

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

Przeprowadzanie wyszukiwania przybliżonego w e-mailach od klientów

Wyszukiwanie zbliżone umożliwia znajdowanie przybliżonych dopasowań haseł wyszukiwania, w tym ich wariantów z błędami pisowni.

Indeks n-gramów został już dodany podczas tworzenia bazy danych i schematu:

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

Metoda findCustomers używa argumentów SEARCH_NGRAMS i SCORE_NGRAMS do wysyłania zapytań do tego indeksu w celu znajdowania klientów według adresu e-mail. Kolumna e-maila została podzielona na tokeny n-gramów, więc to zapytanie może zawierać błędy ortograficzne, a także zwracać prawidłową odpowiedź. Wyniki są uporządkowane według najlepszego dopasowania.

Aby znaleźć pasujące adresy e-mail klientów zawierające madi, uruchom to polecenie:

java -jar target/onlinebanking.jar query email madi

Oczekiwany wynik:

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

Ta odpowiedź zawiera najbliższe dopasowania zawierające madi lub podobny ciąg znaków, posortowane według stopnia dopasowania.

Oto jak wygląda zapytanie, gdy jest wykonywane bezpośrednio w Spanner Studio: ręczne wyszukiwanie madi

Wyszukiwanie zbliżone może też pomóc w przypadku błędów ortograficznych, takich jak błędy w słowie 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

Oczekiwany wynik:

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

W każdym przypadku zwracany jest adres e-mail oczekiwanego klienta.

Funkcja wyszukiwania pełnotekstowego w Spanner służy do pobierania rekordów na podstawie słów kluczowych lub wyrażeń. Umożliwia poprawianie błędów ortograficznych i wyszukiwanie synonimów.

Indeks wyszukiwania pełnotekstowego został już dodany podczas tworzenia bazy danych i schematu:

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

Metoda getSpending używa funkcji wyszukiwania pełnotekstowego SEARCH do dopasowywania do tego indeksu. Szuka wszystkich wydatków (debetów) z ostatnich 30 dni dla danego identyfikatora klienta.

Aby uzyskać łączne wydatki za ostatni miesiąc w przypadku klienta 1 w kategorii groceries, uruchom to polecenie:

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

Oczekiwany wynik:

Total spending for customer 1 under category groceries:
  50

Możesz też sprawdzić wydatki w innych kategoriach (które zostały sklasyfikowane w poprzednim kroku) lub użyć innego identyfikatora klienta:

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

Podsumowanie

W tym kroku wykonałeś/wykonałaś zapytania z dopasowaniem ścisłym, a także wyszukiwania z dopasowaniem nieścisłym i pełnym tekstem.

Następny krok

Następnie zintegrujesz usługę Spanner z Google BigQuery, aby wykonywać zapytania sfederowane, co pozwoli Ci łączyć dane w czasie rzeczywistym z usługi Spanner z danymi z BigQuery.

8. Wykonywanie zapytań sfederowanych za pomocą BigQuery

Tworzenie zbioru danych BigQuery

W tym kroku połączysz dane z BigQuery i Spanner za pomocą zapytań sfederowanych.

W tym celu w wierszu poleceń Cloud Shell utwórz najpierw zbiór danych MarketingCampaigns:

bq mk --location=us-central1 MarketingCampaigns

Oczekiwany wynik:

Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.

I tabelę CustomerSegments w zbiorze danych:

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

Oczekiwany wynik:

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

Następnie utwórz połączenie między 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

Oczekiwany wynik:

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

Na koniec dodaj do tabeli BigQuery kilku klientów, których można złączyć z naszych danymi w 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)'

Oczekiwany wynik:

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

Aby sprawdzić, czy dane są dostępne, wykonaj zapytanie do BigQuery:

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

Oczekiwany wynik:

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

Te dane w BigQuery to dane, które zostały dodane w ramach różnych przepływów pracy banku. Może to być na przykład lista klientów, którzy niedawno założyli konto lub zapisali się do promocji marketingowej. Aby określić listę klientów, na których chcemy się skupić w ramach kampanii marketingowej, musimy wysłać zapytanie do tych danych w BigQuery, a także do danych w czasie rzeczywistym w Spannerze. Zapytanie federacyjne pozwala nam to zrobić w pojedynczym zapytaniu.

Wykonywanie zapytania sfederowanego w BigQuery

Następnie dodamy do aplikacji metodę, która wywoła funkcję EXTERNAL_QUERY, aby wykonać zapytanie sfederowane. Umożliwi to złączanie i analizowanie danych o klientach w BigQuery i Spanner, np. identyfikowanie klientów, którzy spełniają kryteria kampanii marketingowej na podstawie ich ostatnich wydatków.

Otwórz plik App.java i zacznij od zastąpienia importów:

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;

Następnie dodaj metodę 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);
    }
  }

Dodaj w metodzie main kolejne wyrażenie case, aby sprawdzać kampanie:

        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;

Na koniec dodaj do metody printUsageAndExit instrukcje korzystania z kampanii:

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

Zapisz zmiany wprowadzone w pliku App.java.

Utwórz aplikację ponownie:

mvn package

Aby określić klientów, których należy uwzględnić w kampanii marketingowej (campaign1), jeśli w ciągu ostatnich 3 miesięcy wydali oni co najmniej $5000, uruchom zapytanie z federacją:campaign

java -jar target/onlinebanking.jar campaign campaign1 5000

Oczekiwany wynik:

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

Teraz możemy kierować do tych klientów oferty specjalne lub nagrody.

Możemy też poszukać większej liczby klientów, którzy w ciągu ostatnich 3 miesięcy osiągnęli mniejszy próg wydatków:

java -jar target/onlinebanking.jar campaign campaign1 2500

Oczekiwany wynik:

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

Podsumowanie

W tym kroku udało Ci się wykonać sfederowane zapytania z BigQuery, które pobierały dane z Spanner w czasie rzeczywistym.

Następny krok

Następnie możesz wyczyścić zasoby utworzone w ramach tego ćwiczenia z programowania, aby uniknąć opłat.

9. Czyszczenie (opcjonalne)

Ten krok jest opcjonalny. Jeśli chcesz nadal eksperymentować z instancją usługi Spanner, nie musisz jej na razie oczyszczać. Jednak projekt, którego używasz, będzie nadal obciążany opłatami za instancję. Jeśli nie potrzebujesz już tej instancji, usuń ją, aby uniknąć tych opłat. Oprócz instancji usługi Spanner w ramach tego ćwiczenia kodowego utworzono też zbiór danych i połączenie z BigQuery, które należy usunąć, gdy nie będą już potrzebne.

Usuń instancję usługi Spanner:

gcloud spanner instances delete cloudspanner-onlinebanking

Potwierdź, że chcesz kontynuować (wpisz Y):

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

Do you want to continue (Y/n)?

Usuń połączenie i zbiór danych BigQuery:

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

Potwierdź usunięcie zbioru danych BigQuery (napisz Y):

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

10. Gratulacje

🚀 Utworzyłeś/utworzyłaś nową instancję Cloud Spanner, utworzyłeś/utworzyłaś pustą bazę danych, załadowałeś/załadowałaś przykładowe dane, wykonałeś/wykonałaś zaawansowane operacje i zapytania oraz (opcjonalnie) usunąłeś/usunęłaś instancję Cloud Spanner.

Omówione zagadnienia

  • Jak skonfigurować instancję usługi Spanner
  • Jak utworzyć bazę danych i tabele.
  • Jak wczytywać dane do tabel bazy danych Spanner.
  • Jak wywoływać modele Vertex AI z Spannera.
  • Jak wysyłać zapytania do bazy danych Spanner przy użyciu wyszukiwania nieprecyzyjnego i wyszukiwania pełnotekstowego.
  • Jak wykonywać zapytania sfederowane do usługi Spanner z BigQuery.
  • Jak usunąć instancję usługi Spanner

Co dalej?