Onlinebanking-Anwendung mit Spanner erstellen

1. Übersicht

Spanner ist ein vollständig verwalteter, horizontal skalierbarer, global verteilter Datenbankdienst, der sich sowohl für relationale als auch für nicht relationale operative Arbeitslasten eignet. Neben den Kernfunktionen bietet Spanner leistungsstarke erweiterte Funktionen, mit denen intelligente und datengesteuerte Anwendungen erstellt werden können.

Dieses Codelab baut auf dem grundlegenden Verständnis von Spanner auf und befasst sich mit der Nutzung der erweiterten Integrationen, um Ihre Datenverarbeitungs- und Analysefunktionen zu verbessern. Als Grundlage dient eine Onlinebanking-Anwendung.

Wir konzentrieren uns auf drei wichtige erweiterte Funktionen:

  • Vertex AI-Integration:Hier erfahren Sie, wie Sie Spanner nahtlos in die KI-Plattform von Google Cloud, Vertex AI, einbinden. Sie erfahren, wie Sie Vertex AI-Modelle direkt aus Spanner-SQL-Abfragen aufrufen können. So lassen sich leistungsstarke In-Database-Transformationen und ‑Vorhersagen durchführen, mit denen unsere Banking-Anwendung Transaktionen für Anwendungsfälle wie Budgetverfolgung und Anomalieerkennung automatisch kategorisieren kann.
  • Volltextsuche:Hier erfahren Sie, wie Sie die Volltextsuche in Spanner implementieren. Sie lernen, wie Sie Textdaten indexieren und effiziente Abfragen schreiben, um keywordbasierte Suchen in Ihren Betriebsdaten durchzuführen. So können Sie leistungsstarke Datenermittlungen durchführen, z. B. Kunden anhand ihrer E‑Mail-Adresse in unserem Bankensystem effizient finden.
  • Föderierte BigQuery-Abfragen:Hier erfahren Sie, wie Sie die föderierten Abfragefunktionen von Spanner nutzen können, um Daten, die sich in BigQuery befinden, direkt abzufragen. So können Sie die operativen Echtzeitdaten von Spanner mit den Analysedatensätzen von BigQuery kombinieren, um umfassende Statistiken und Berichte zu erstellen, ohne dass Daten dupliziert oder komplexe ETL-Prozesse erforderlich sind. Dies ermöglicht verschiedene Anwendungsfälle in unserer Banking-Anwendung, z. B. gezielte Marketingkampagnen, indem Echtzeit-Kundendaten mit umfassenderen historischen Trends aus BigQuery kombiniert werden.

Lerninhalte

  • So richten Sie eine Spanner-Instanz ein.
  • So erstellen Sie eine Datenbank und Tabellen.
  • Daten in Ihre Cloud Spanner-Datenbanktabellen laden
  • Vertex AI-Modelle aus Spanner aufrufen
  • So fragen Sie Ihre Spanner-Datenbank mit Fuzzy-Suche und Volltextsuche ab.
  • So führen Sie föderierte Abfragen für Spanner aus BigQuery aus.
  • So löschen Sie Ihre Spanner-Instanz.

Voraussetzungen

  • Ein Google Cloud-Projekt, das mit einem Rechnungskonto verknüpft ist.
  • Einen Webbrowser wie Chrome oder Firefox.

2. Einrichtung und Anforderungen

Projekt erstellen

Wenn Sie bereits ein Google Cloud-Projekt mit aktivierter Abrechnung haben, klicken Sie oben links in der Console auf das Drop-down-Menü zur Projektauswahl:

Vorhandenes Projekt

Wenn Sie ein Projekt ausgewählt haben, fahren Sie mit Erforderliche APIs aktivieren fort.

Wenn Sie noch kein Google-Konto (Gmail oder Google Apps) haben, müssen Sie eins erstellen. Melden Sie sich in der Google Cloud Console (console.cloud.google.com) an und erstellen Sie ein neues Projekt.

Klicken Sie im angezeigten Dialogfeld auf die Schaltfläche „NEW PROJECT“ (NEUES PROJEKT), um ein neues Projekt zu erstellen:

Neues Projekt

Wenn Sie noch kein Projekt haben, wird ein Dialogfeld wie dieses angezeigt, in dem Sie Ihr erstes Projekt erstellen können:

Dialogfeld „Projekt“

Im nachfolgenden Dialogfeld können Sie die Details Ihres neuen Projekts eingeben.

Merken Sie sich die Projekt-ID, die für alle Google Cloud-Projekte ein eindeutiger Name ist. Sie wird in diesem Codelab später als PROJECT_ID bezeichnet.

Projektdetails

Als Nächstes müssen Sie, falls noch nicht geschehen, die Abrechnung in der Entwicklerkonsole aktivieren, um Google Cloud-Ressourcen verwenden und die Spanner API, Vertex AI API, BigQuery API und BigQuery Connection API aktivieren zu können.

Projektabrechnung

Die Preise für Spanner sind hier dokumentiert. Andere Kosten, die mit anderen Ressourcen verbunden sind, werden auf den jeweiligen Preisseiten dokumentiert.

Neuen Nutzern der Google Cloud Platform steht eine kostenlose Testversion mit einem Guthaben von 300$ zur Verfügung.

Google Cloud Shell-Einrichtung

In diesem Codelab verwenden wir Google Cloud Shell, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.

Auf dieser Debian-basierten virtuellen Maschine sind alle erforderlichen Entwicklungstools installiert. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Für dieses Codelab benötigen Sie also nur einen Browser.

Klicken Sie zum Aktivieren von Cloud Shell in der Cloud Console einfach auf „Cloud Shell aktivieren“ Bild: Cloud Shell-Symbol. Es dauert nur wenige Augenblicke, bis die Umgebung bereitgestellt und eine Verbindung hergestellt wird.

Cloud Shell

Sobald Sie mit Cloud Shell verbunden sind, sollten Sie sehen, dass Sie bereits authentifiziert sind und das Projekt bereits auf Ihre PROJECT_ID eingestellt ist.

gcloud auth list

Erwartete Ausgabe:

Credentialed Accounts

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

Erwartete Ausgabe:

[core]
project = <PROJECT_ID>

Wenn das Projekt aus irgendeinem Grund nicht festgelegt ist, geben Sie den folgenden Befehl ein:

gcloud config set project <PROJECT_ID>

Suchst du nach deinem PROJECT_ID? Prüfen Sie, welche ID Sie in den Einrichtungsschritten verwendet haben, oder suchen Sie sie im Cloud Console-Dashboard:

Projekt-ID

In Cloud Shell werden auch einige Umgebungsvariablen standardmäßig festgelegt, die beim Ausführen zukünftiger Befehle nützlich sein können.

echo $GOOGLE_CLOUD_PROJECT

Erwartete Ausgabe:

<PROJECT_ID>

Erforderliche APIs aktivieren

Aktivieren Sie die Spanner-, Vertex AI- und BigQuery-APIs für Ihr Projekt:

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

Zusammenfassung

In diesem Schritt haben Sie Ihr Projekt eingerichtet, falls Sie noch keines hatten, Cloud Shell aktiviert und die erforderlichen APIs aktiviert.

Als Nächstes

Als Nächstes richten Sie die Cloud Spanner-Instanz ein.

3. Spanner-Instanz einrichten

Spanner-Instanz erstellen

In diesem Schritt richten Sie eine Spanner-Instanz für das Codelab ein. Öffnen Sie dazu Cloud Shell und führen Sie diesen Befehl aus:

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

Erwartete Ausgabe:

Creating instance...done.

Zusammenfassung

In diesem Schritt haben Sie die Spanner-Instanz erstellt.

Als Nächstes

Als Nächstes bereiten Sie die erste Anwendung vor und erstellen die Datenbank und das Schema.

4. Datenbank und Schema erstellen

Erstantrag vorbereiten

In diesem Schritt erstellen Sie die Datenbank und das Schema über den Code.

Erstellen Sie zuerst eine Java-Anwendung mit dem Namen onlinebanking mit 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

Checken Sie die Datendateien aus, die wir der Datenbank hinzufügen werden, und kopieren Sie sie (das Code-Repository finden Sie hier):

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

Rufen Sie den Anwendungsordner auf:

cd onlinebanking

Öffnen Sie die Maven-Datei pom.xml. Fügen Sie den Abschnitt zur Abhängigkeitsverwaltung hinzu, um die Maven-BOM zum Verwalten der Version von Google Cloud-Bibliotheken zu verwenden:

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

So sehen der Editor und die Datei aus: Cloud Shell

Der Abschnitt dependencies muss die Bibliotheken enthalten, die von der Anwendung verwendet werden:

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

Ersetzen Sie schließlich die Build-Plug-ins, damit die Anwendung in einer ausführbaren JAR-Datei verpackt wird:

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

Speichern Sie die Änderungen, die Sie an der Datei pom.xml vorgenommen haben, indem Sie im Cloud Shell-Editor im Menü „Datei“ die Option „Speichern“ auswählen oder Ctrl+S drücken.

Nachdem die Abhängigkeiten eingerichtet sind, fügen Sie der App Code hinzu, um ein Schema, einige Indexe (einschließlich der Suche) und ein KI-Modell zu erstellen, das mit einem Remote-Endpunkt verbunden ist. In diesem Codelab werden Sie auf diesen Artefakten aufbauen und dieser Klasse weitere Methoden hinzufügen.

Öffnen Sie App.java unter onlinebanking/src/main/java/com/google/codelabs und ersetzen Sie den Inhalt durch den folgenden Code:

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

Speichern Sie die Änderungen in App.java.

Sehen Sie sich die verschiedenen Entitäten an, die von Ihrem Code erstellt werden, und erstellen Sie das Anwendungs-JAR:

mvn package

Erwartete Ausgabe:

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

Führen Sie die Anwendung aus, um die Nutzungsinformationen aufzurufen:

java -jar target/onlinebanking.jar

Erwartete Ausgabe:

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.

Datenbank und Schema erstellen

Legen Sie die erforderlichen Umgebungsvariablen für die Anwendung fest:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking

Erstellen Sie die Datenbank und das Schema, indem Sie den Befehl create ausführen:

java -jar target/onlinebanking.jar create

Erwartete Ausgabe:

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

Schema in Spanner prüfen

Rufen Sie in der Spanner-Konsole die gerade erstellte Instanz und Datenbank auf.

Sie sollten alle drei Tabellen sehen: Accounts, Customers und TransactionLedger.

Schema ansehen

Bei dieser Aktion wird das Datenbankschema erstellt, einschließlich der Tabellen Accounts, Customers und TransactionLedger, sowie sekundärer Indexe für den optimierten Datenabruf und einer Vertex AI-Modellreferenz.

Entity-Relationship-Diagramm

Die Tabelle TransactionLedger ist in Konten verschachtelt, um die Abfrageleistung für kontospezifische Transaktionen durch eine verbesserte Datenlokalität zu steigern.

Sekundärindexe (CustomersByEmail, CustomersFuzzyEmail, AccountsByCustomer, TransactionLedgerByAccountType, TransactionLedgerByCategory, TransactionLedgerTextSearch) wurden implementiert, um gängige Datenzugriffsmuster zu optimieren, die in diesem Codelab verwendet werden, z. B. Kundensuchen nach genauer und unscharfer E-Mail-Adresse, Abrufen von Konten nach Kunde und effizientes Abfragen und Suchen von Transaktionsdaten.

Die TransactionCategoryModel nutzt Vertex AI, um direkte SQL-Aufrufe an ein LLM zu ermöglichen, das in diesem Codelab für die dynamische Transaktionskategorisierung verwendet wird.

Zusammenfassung

In diesem Schritt haben Sie die Spanner-Datenbank und das Schema erstellt.

Als Nächstes

Als Nächstes laden Sie die Beispielanwendungsdaten.

5. Daten laden

Als Nächstes fügen Sie Funktionen hinzu, um Beispieldaten aus CSV-Dateien in die Datenbank zu laden.

Öffnen Sie App.java und ersetzen Sie zuerst die Importe:

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;

Fügen Sie dann die Einfügemethoden in die Klasse App ein:

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

Fügen Sie in der Methode main eine weitere case-Anweisung für das Einfügen in switch (command) hinzu:

        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;

Fügen Sie schließlich hinzu, wie „insert“ für die printUsageAndExit-Methode verwendet wird:

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

Speichern Sie die Änderungen, die Sie an App.java vorgenommen haben.

Erstellen Sie die Anwendung neu:

mvn package

Fügen Sie die Beispieldaten ein, indem Sie den Befehl insert ausführen:

java -jar target/onlinebanking.jar insert

Erwartete Ausgabe:

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

Kehren Sie in der Spanner Console zu Spanner Studio für Ihre Instanz und Datenbank zurück. Wählen Sie dann die Tabelle TransactionLedger aus und klicken Sie in der Seitenleiste auf „Daten“, um zu prüfen, ob die Daten geladen wurden. Die Tabelle sollte 200 Zeilen enthalten.

Daten anzeigen

Zusammenfassung

In diesem Schritt haben Sie die Beispieldaten in die Datenbank eingefügt.

Als Nächstes

Als Nächstes nutzen Sie die Vertex AI-Integration, um Banktransaktionen automatisch direkt in Spanner SQL zu kategorisieren.

6. Daten mit Vertex AI kategorisieren

In diesem Schritt nutzen Sie die Leistungsfähigkeit von Vertex AI, um Ihre Finanztransaktionen direkt in Spanner SQL automatisch zu kategorisieren. Mit Vertex AI können Sie ein vorhandenes vortrainiertes Modell auswählen oder Ihr eigenes trainieren und bereitstellen. Die verfügbaren Modelle finden Sie im Vertex AI Model Garden.

In diesem Codelab verwenden wir eines der Gemini-Modelle, Gemini Flash Lite. Diese Version von Gemini ist kostengünstig und kann dennoch die meisten täglichen Arbeitslasten bewältigen.

Derzeit haben wir eine Reihe von Finanztransaktionen, die wir je nach Beschreibung kategorisieren möchten (groceries, transportation usw.). Dazu registrieren wir ein Modell in Spanner und rufen dann mit ML.PREDICT das KI-Modell auf.

In unserer Banking-Anwendung möchten wir möglicherweise Transaktionen kategorisieren, um mehr Einblicke in das Kundenverhalten zu erhalten. So können wir Dienste personalisieren, Anomalien effektiver erkennen oder dem Kunden die Möglichkeit geben, sein Budget monatlich zu verfolgen.

Der erste Schritt wurde bereits beim Erstellen der Datenbank und des Schemas ausgeführt. Dabei wurde ein Modell wie dieses erstellt:

CREATE MODEL-Anweisung

Als Nächstes fügen wir der Anwendung eine Methode hinzu, um ML.PREDICT aufzurufen.

Öffnen Sie App.java und fügen Sie die Methode categorize hinzu:

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

Fügen Sie der Methode main für „categorize“ eine weitere Case-Anweisung hinzu:

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

Fügen Sie schließlich die Verwendung von „categorize“ an die Methode printUsageAndExit an:

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

Speichern Sie die Änderungen, die Sie an App.java vorgenommen haben.

Erstellen Sie die Anwendung neu:

mvn package

Kategorisieren Sie die Transaktionen in der Datenbank, indem Sie den Befehl categorize ausführen:

java -jar target/onlinebanking.jar categorize

Erwartete Ausgabe:

Categorizing transactions...
Completed categorizing transactions

Führen Sie in Spanner Studio die Anweisung Preview Data für die Tabelle TransactionLedger aus. Die Spalte Category sollte jetzt für alle Zeilen ausgefüllt sein.

Kategorisierte Daten ansehen

Nachdem wir die Transaktionen kategorisiert haben, können wir diese Informationen für interne oder kundenbezogene Anfragen verwenden. Im nächsten Schritt sehen wir uns an, wie viel ein bestimmter Kunde im Laufe des Monats in einer Kategorie ausgibt.

Zusammenfassung

In diesem Schritt haben Sie ein vortrainiertes Modell verwendet, um Ihre Daten KI-basiert zu kategorisieren.

Als Nächstes

Als Nächstes verwenden Sie die Tokenisierung, um Fuzzy- und Volltextsuchen durchzuführen.

7. Mit der Volltextsuche suchen

Abfragecode hinzufügen

Spanner bietet viele Volltextsuchanfragen. In diesem Schritt führen Sie eine Suche nach exakten Treffern, eine unscharfe Suche und eine Volltextsuche durch.

Öffnen Sie App.java und ersetzen Sie zuerst die Importe:

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;

Fügen Sie dann die Abfragemethoden hinzu:

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

Fügen Sie eine weitere case-Anweisung in die main-Methode für die Abfrage ein:

        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;

Fügen Sie schließlich hinzu, wie die Abfragebefehle an die printUsageAndExit-Methode angehängt werden:

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

Speichern Sie die Änderungen, die Sie an App.java vorgenommen haben.

Erstellen Sie die Anwendung neu:

mvn package

Exakte Suche nach Kundenkontoständen durchführen

Bei einer Suche nach genau passenden Treffern wird nach übereinstimmenden Zeilen gesucht, die genau mit einem Begriff übereinstimmen.

Um die Leistung zu verbessern, wurde beim Erstellen der Datenbank und des Schemas bereits ein Index hinzugefügt:

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

Bei der Methode getBalance wird dieser Index implizit verwendet, um Kunden zu finden, die der angegebenen „customerId“ entsprechen. Außerdem werden Konten verknüpft, die zu diesem Kunden gehören.

So sieht die Abfrage aus, wenn sie direkt in Spanner Studio ausgeführt wird: Guthaben manuell abfragen

Führen Sie den folgenden Befehl aus, um den Kontostand des Kunden 1 aufzulisten:

java -jar target/onlinebanking.jar query balance 1

Erwartete Ausgabe:

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

Es gibt 100 Kunden. Sie können also auch die Kontostände anderer Kunden abfragen, indem Sie eine andere Kunden-ID angeben:

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

Ungefähre Suche nach Kunden-E-Mail-Adressen durchführen

Mit der ungenauen Suche lassen sich ungefähre Übereinstimmungen für Suchbegriffe finden, einschließlich Rechtschreibvarianten und Tippfehlern.

Beim Erstellen der Datenbank und des Schemas wurde bereits ein N-Gramm-Index hinzugefügt:

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

Bei der findCustomers-Methode werden SEARCH_NGRAMS und SCORE_NGRAMS verwendet, um diesen Index abzufragen und Kunden anhand ihrer E-Mail-Adresse zu finden. Da die Spalte „E-Mail“ als N-Gramm-Tokenisiert wurde, kann diese Abfrage Rechtschreibfehler enthalten und trotzdem eine korrekte Antwort zurückgeben. Die Ergebnisse werden nach der besten Übereinstimmung sortiert.

Führen Sie den folgenden Befehl aus, um übereinstimmende Kunden-E-Mail-Adressen zu finden, die madi enthalten:

java -jar target/onlinebanking.jar query email madi

Erwartete Ausgabe:

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

Diese Antwort zeigt die besten Übereinstimmungen, die madi oder einen ähnlichen String enthalten, in absteigender Reihenfolge.

So sieht die Abfrage aus, wenn sie direkt in Spanner Studio ausgeführt wird: manuelle Suche nach madi

Die ungenaue Suche kann auch bei Rechtschreibfehlern wie Falschschreibungen von emily helfen:

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

Erwartete Ausgabe:

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

In jedem Fall wird die erwartete Kunden-E-Mail-Adresse als Top-Ergebnis zurückgegeben.

Mit der Volltextsuche von Cloud Spanner können Datensätze anhand von Keywords oder Wortgruppen abgerufen werden. Es kann Rechtschreibfehler korrigieren oder nach Synonymen suchen.

Beim Erstellen der Datenbank und des Schemas wurde bereits ein Volltextsuchindex hinzugefügt:

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

Bei der getSpending-Methode wird die Volltextsuchfunktion SEARCH verwendet, um den Index abzugleichen. Es wird nach allen Ausgaben (Belastungen) der letzten 30 Tage für die angegebene Kunden-ID gesucht.

Mit dem folgenden Befehl können Sie die Gesamtausgaben für den Kunden 1 in der Kategorie groceries im letzten Monat abrufen:

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

Erwartete Ausgabe:

Total spending for customer 1 under category groceries:
  50

Sie können auch Ausgaben für andere Kategorien (die wir in einem vorherigen Schritt kategorisiert haben) aufrufen oder eine andere Kunden-ID verwenden:

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

Zusammenfassung

In diesem Schritt haben Sie sowohl genau passende als auch unscharfe und Volltextsuchen durchgeführt.

Als Nächstes

Als Nächstes binden Sie Spanner in Google BigQuery ein, um föderierte Abfragen auszuführen. So können Sie Ihre Echtzeit-Spanner-Daten mit BigQuery-Daten kombinieren.

8. Föderierte Abfragen mit BigQuery ausführen

BigQuery-Dataset erstellen

In diesem Schritt führen Sie BigQuery- und Spanner-Daten mithilfe von föderierten Abfragen zusammen.

Erstellen Sie dazu zuerst ein MarketingCampaigns-Dataset in der Cloud Shell-Befehlszeile:

bq mk --location=us-central1 MarketingCampaigns

Erwartete Ausgabe:

Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.

Und eine Tabelle CustomerSegments im Dataset:

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

Erwartete Ausgabe:

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

Stellen Sie als Nächstes eine Verbindung von BigQuery zu Spanner her:

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

Erwartete Ausgabe:

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

Fügen Sie der BigQuery-Tabelle einige Kunden hinzu, die mit unseren Spanner-Daten verknüpft werden können:

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

Erwartete Ausgabe:

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

Sie können die Verfügbarkeit der Daten überprüfen, indem Sie BigQuery abfragen:

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

Erwartete Ausgabe:

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

Diese Daten in BigQuery stellen Daten dar, die über verschiedene Bank-Workflows hinzugefügt wurden. Das kann beispielsweise die Liste der Kunden sein, die vor Kurzem Konten eröffnet oder sich für eine Marketingaktion registriert haben. Um die Liste der Kunden zu ermitteln, die wir in unserer Marketingkampagne ansprechen möchten, müssen wir sowohl diese Daten in BigQuery als auch die Echtzeitdaten in Spanner abfragen. Mit einer föderierten Abfrage ist dies in einer einzigen Abfrage möglich.

Föderierte Abfrage mit BigQuery ausführen

Als Nächstes fügen wir der Anwendung eine Methode hinzu, mit der EXTERNAL_QUERY aufgerufen wird, um die föderierte Abfrage auszuführen. So können Kundendaten in BigQuery und Spanner zusammengeführt und analysiert werden. Beispielsweise lässt sich ermitteln, welche Kunden aufgrund ihrer letzten Ausgaben die Kriterien für unsere Marketingkampagne erfüllen.

Öffnen Sie App.java und ersetzen Sie zuerst die Importe:

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;

Fügen Sie dann die Methode campaign hinzu:

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

Fügen Sie der main-Methode für die Kampagne eine weitere case-Anweisung hinzu:

        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;

Fügen Sie schließlich hinzu, wie die Kampagne verwendet werden soll, und zwar mit der Methode 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");

Speichern Sie die Änderungen, die Sie an App.java vorgenommen haben.

Erstellen Sie die Anwendung neu:

mvn package

Führen Sie eine föderierte Abfrage aus, um Kunden zu ermitteln, die in die Marketingkampagne aufgenommen werden sollen (campaign1), wenn sie in den letzten drei Monaten mindestens $5000 ausgegeben haben. Verwenden Sie dazu den Befehl campaign:

java -jar target/onlinebanking.jar campaign campaign1 5000

Erwartete Ausgabe:

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

Wir können diese Kunden jetzt mit exklusiven Angeboten oder Prämien ansprechen.

Alternativ können wir nach einer größeren Anzahl von Kunden suchen, die in den letzten drei Monaten einen niedrigeren Ausgabengrenzwert erreicht haben:

java -jar target/onlinebanking.jar campaign campaign1 2500

Erwartete Ausgabe:

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

Zusammenfassung

In diesem Schritt haben Sie erfolgreich föderierte Abfragen aus BigQuery ausgeführt, mit denen Spanner-Echtzeitdaten abgerufen wurden.

Als Nächstes

Als Nächstes können Sie die für dieses Codelab erstellten Ressourcen bereinigen, um Gebühren zu vermeiden.

9. Bereinigen (optional)

Dieser Schritt ist optional. Wenn Sie weiterhin mit Ihrer Spanner-Instanz experimentieren möchten, müssen Sie sie jetzt nicht bereinigen. Für das von Ihnen verwendete Projekt werden jedoch weiterhin Kosten für die Instanz berechnet. Wenn Sie diese Instanz nicht mehr benötigen, sollten Sie sie jetzt löschen, um diese Gebühren zu vermeiden. Zusätzlich zur Spanner-Instanz wurden in diesem Codelab auch ein BigQuery-Dataset und eine BigQuery-Verbindung erstellt, die bereinigt werden sollten, wenn sie nicht mehr benötigt werden.

Löschen Sie die Spanner-Instanz:

gcloud spanner instances delete cloudspanner-onlinebanking

Bestätigen Sie, dass Sie fortfahren möchten (geben Sie Y ein):

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

Do you want to continue (Y/n)?

BigQuery-Verbindung und ‑Dataset löschen:

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

Bestätigen Sie das Löschen des BigQuery-Datasets, indem Sie Y eingeben:

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

10. Glückwunsch

🚀 Sie haben eine neue Cloud Spanner-Instanz und eine leere Datenbank erstellt, Beispieldaten geladen, erweiterte Vorgänge und Abfragen ausgeführt und die Cloud Spanner-Instanz optional gelöscht.

Behandelte Themen

  • So richten Sie eine Spanner-Instanz ein.
  • So erstellen Sie eine Datenbank und Tabellen.
  • Daten in Ihre Cloud Spanner-Datenbanktabellen laden
  • Vertex AI-Modelle aus Spanner aufrufen
  • So fragen Sie Ihre Spanner-Datenbank mit Fuzzy-Suche und Volltextsuche ab.
  • So führen Sie föderierte Abfragen für Spanner aus BigQuery aus.
  • So löschen Sie Ihre Spanner-Instanz.

Nächste Schritte