Tạo ứng dụng ngân hàng trực tuyến bằng Spanner

1. Tổng quan

Spanner là một dịch vụ cơ sở dữ liệu được quản lý toàn diện, có thể mở rộng theo chiều ngang và được phân phối trên toàn cầu, phù hợp với cả khối lượng công việc vận hành quan hệ và không quan hệ. Ngoài các chức năng cốt lõi, Spanner còn cung cấp các tính năng nâng cao mạnh mẽ giúp xây dựng các ứng dụng thông minh và dựa trên dữ liệu.

Lớp học lập trình này dựa trên kiến thức cơ bản về Spanner và đi sâu vào việc tận dụng các tính năng tích hợp nâng cao của Spanner để nâng cao khả năng xử lý và phân tích dữ liệu, dựa trên một ứng dụng ngân hàng trực tuyến.

Chúng ta sẽ tập trung vào 3 tính năng nâng cao chính:

  • Tích hợp Vertex AI: Khám phá cách tích hợp liền mạch Spanner với nền tảng AI của Google Cloud, Vertex AI. Bạn sẽ tìm hiểu cách gọi các mô hình Vertex AI ngay trong các truy vấn SQL Spanner, cho phép các phép biến đổi và dự đoán mạnh mẽ trong cơ sở dữ liệu, cho phép ứng dụng ngân hàng của chúng ta tự động phân loại các giao dịch cho các trường hợp sử dụng như theo dõi ngân sách và phát hiện bất thường.
  • Tìm kiếm toàn bộ văn bản: Tìm hiểu cách triển khai chức năng tìm kiếm toàn bộ văn bản trong Spanner. Bạn sẽ khám phá cách lập chỉ mục dữ liệu văn bản và viết truy vấn hiệu quả để tìm kiếm dựa trên từ khoá trên dữ liệu hoạt động, cho phép khám phá dữ liệu mạnh mẽ, chẳng hạn như tìm khách hàng hiệu quả theo địa chỉ email trong hệ thống ngân hàng của chúng tôi.
  • Truy vấn liên kết của BigQuery: Khám phá cách tận dụng các chức năng truy vấn liên kết của Spanner để truy vấn trực tiếp dữ liệu nằm trong BigQuery. Điều này cho phép bạn kết hợp dữ liệu hoạt động theo thời gian thực của Spanner với các tập dữ liệu phân tích của BigQuery để có thông tin chi tiết và báo cáo toàn diện mà không cần sao chép dữ liệu hoặc các quy trình ETL phức tạp, hỗ trợ nhiều trường hợp sử dụng trong ứng dụng ngân hàng của chúng tôi, chẳng hạn như các chiến dịch tiếp thị được nhắm mục tiêu bằng cách kết hợp dữ liệu khách hàng theo thời gian thực với các xu hướng trong quá khứ rộng hơn từ BigQuery.

Kiến thức bạn sẽ học được

  • Cách thiết lập một thực thể Spanner.
  • Cách tạo cơ sở dữ liệu và bảng.
  • Cách tải dữ liệu vào các bảng cơ sở dữ liệu Spanner.
  • Cách gọi các mô hình Vertex AI từ Spanner.
  • Cách truy vấn cơ sở dữ liệu Spanner bằng tính năng tìm kiếm mờ và tìm kiếm toàn bộ văn bản.
  • Cách thực hiện truy vấn liên kết trên Spanner từ BigQuery.
  • Cách xoá thực thể Spanner.

Bạn cần có

  • Một dự án Google Cloud được kết nối với một tài khoản thanh toán.
  • Một trình duyệt web, chẳng hạn như Chrome hoặc Firefox.

2. Cách thiết lập và yêu cầu

Tạo một dự án

Nếu bạn đã bật tính năng thanh toán cho một dự án Google Cloud, hãy nhấp vào trình đơn thả xuống để chọn dự án ở phía trên bên trái của bảng điều khiển:

Dự án hiện có

Với dự án đã chọn, hãy chuyển sang phần Bật các API bắt buộc.

Nếu chưa có Tài khoản Google (Gmail hoặc Google Apps), bạn phải tạo một tài khoản. Đăng nhập vào bảng điều khiển Google Cloud Platform (console.cloud.google.com) rồi tạo một dự án mới.

Nhấp vào nút "NEW PROJECT" (DỰ ÁN MỚI) trong hộp thoại xuất hiện để tạo dự án mới:

Dự án mới

Nếu chưa có dự án, bạn sẽ thấy một hộp thoại như sau để tạo dự án đầu tiên:

Hộp thoại Project (Dự án)

Hộp thoại tạo dự án tiếp theo cho phép bạn nhập thông tin chi tiết về dự án mới.

Ghi nhớ mã dự án. Đây là một tên duy nhất trên tất cả các dự án Google Cloud. Mã này sẽ được gọi là PROJECT_ID ở phần sau của lớp học lập trình này.

Chi tiết dự án

Tiếp theo, nếu chưa bật, bạn cần bật tính năng thanh toán trong Bảng điều khiển dành cho nhà phát triển để sử dụng các tài nguyên của Google Cloud và bật Spanner API, Vertex AI API, BigQuery APIBigQuery Connection API.

Thanh toán dự án

Bạn có thể xem thông tin về giá của Spanner tại đây. Các chi phí khác liên quan đến các tài nguyên khác sẽ được ghi lại trong các trang giá cụ thể của chúng.

Người dùng mới của Google Cloud Platform đủ điều kiện dùng thử miễn phí 300 USD.

Thiết lập Google Cloud Shell

Trong lớp học lập trình này, chúng ta sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trên đám mây.

Máy ảo dựa trên Debian này được tải sẵn tất cả các công cụ phát triển mà bạn cần. Ứng dụng này cung cấp một thư mục gốc 5 GB ổn định và chạy trong Google Cloud, giúp cải thiện đáng kể hiệu suất mạng và xác thực. Điều này có nghĩa là bạn chỉ cần một trình duyệt để tham gia lớp học lập trình này.

Để kích hoạt Cloud Shell từ Cloud Console, bạn chỉ cần nhấp vào nút Kích hoạt Cloud Shell Biểu tượng Cloud Shell (quá trình cấp phép và kết nối với môi trường sẽ chỉ mất vài phút).

Cloud Shell

Sau khi kết nối với Cloud Shell, bạn sẽ thấy mình đã được xác thực và dự án đã được đặt thành PROJECT_ID.

gcloud auth list

Kết quả đầu ra dự kiến:

Credentialed Accounts

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

Kết quả đầu ra dự kiến:

[core]
project = <PROJECT_ID>

Nếu vì lý do nào đó mà dự án không được đặt, hãy đưa ra lệnh sau:

gcloud config set project <PROJECT_ID>

Bạn đang tìm PROJECT_ID? Kiểm tra mã nhận dạng bạn đã sử dụng trong các bước thiết lập hoặc tra cứu mã nhận dạng đó trong trang tổng quan của Cloud Console:

Mã dự án

Theo mặc định, Cloud Shell cũng đặt một số biến môi trường. Các biến này có thể hữu ích khi bạn chạy các lệnh trong tương lai.

echo $GOOGLE_CLOUD_PROJECT

Kết quả đầu ra dự kiến:

<PROJECT_ID>

Bật các API bắt buộc

Bật API Spanner, Vertex AI và BigQuery cho dự án:

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

Tóm tắt

Ở bước này, bạn đã thiết lập dự án (nếu chưa có), kích hoạt Cloud Shell và bật các API cần thiết.

Tiếp theo

Tiếp theo, bạn sẽ thiết lập thực thể Spanner.

3. Thiết lập một thực thể Spanner

Tạo thực thể Spanner

Trong bước này, bạn sẽ thiết lập một thực thể Spanner cho lớp học lập trình. Để thực hiện việc này, hãy mở Cloud Shell và chạy lệnh sau:

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

Kết quả đầu ra dự kiến:

Creating instance...done.

Tóm tắt

Ở bước này, bạn đã tạo thực thể Spanner.

Tiếp theo

Tiếp theo, bạn sẽ chuẩn bị ứng dụng ban đầu và tạo cơ sở dữ liệu cũng như giản đồ.

4. Tạo cơ sở dữ liệu và giản đồ

Chuẩn bị ứng dụng ban đầu

Ở bước này, bạn sẽ tạo cơ sở dữ liệu và giản đồ thông qua mã.

Trước tiên, hãy tạo một ứng dụng Java có tên onlinebanking bằng 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

Kiểm tra và sao chép các tệp dữ liệu mà chúng ta sẽ thêm vào cơ sở dữ liệu (xem tại đây để biết kho lưu trữ mã):

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

Chuyển đến thư mục ứng dụng:

cd onlinebanking

Mở tệp pom.xml của Maven. Thêm phần quản lý phần phụ thuộc để sử dụng BOM Maven nhằm quản lý phiên bản của các thư viện 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>

Trình chỉnh sửa và tệp sẽ có dạng như sau: shell trên đám mây

Đảm bảo phần dependencies bao gồm các thư viện mà ứng dụng sẽ sử dụng:

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

Cuối cùng, hãy thay thế các trình bổ trợ bản dựng để ứng dụng được đóng gói thành tệp JAR có thể chạy:

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

Lưu các thay đổi bạn đã thực hiện đối với tệp pom.xml bằng cách chọn "Lưu" trong trình đơn "Tệp" của Trình chỉnh sửa Cloud Shell hoặc nhấn phím Ctrl+S.

Giờ đây, khi các phần phụ thuộc đã sẵn sàng, bạn sẽ thêm mã vào Ứng dụng để tạo giản đồ, một số chỉ mục (bao gồm cả tìm kiếm) và mô hình AI được kết nối với một điểm cuối từ xa. Bạn sẽ xây dựng dựa trên các cấu phần phần mềm này và thêm các phương thức khác vào lớp này trong lớp học lập trình này.

Mở App.java trong onlinebanking/src/main/java/com/google/codelabs rồi thay thế nội dung bằng mã sau:

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

Lưu các thay đổi vào App.java.

Hãy xem các thực thể mà mã của bạn đang tạo và tạo tệp JAR ứng dụng:

mvn package

Kết quả đầu ra dự kiến:

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

Chạy ứng dụng để xem thông tin sử dụng:

java -jar target/onlinebanking.jar

Kết quả đầu ra dự kiến:

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.

Tạo cơ sở dữ liệu và giản đồ

Thiết lập các biến môi trường ứng dụng bắt buộc:

export SPANNER_INSTANCE=cloudspanner-onlinebanking
export SPANNER_DATABASE=onlinebanking

Tạo cơ sở dữ liệu và giản đồ bằng cách chạy lệnh create:

java -jar target/onlinebanking.jar create

Kết quả đầu ra dự kiến:

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

Kiểm tra giản đồ trong Spanner

Trong bảng điều khiển Spanner, hãy chuyển đến phiên bản và cơ sở dữ liệu bạn vừa tạo.

Bạn sẽ thấy cả 3 bảng – Accounts, CustomersTransactionLedger.

Xem giản đồ

Thao tác này sẽ tạo giản đồ cơ sở dữ liệu, bao gồm các bảng Accounts, CustomersTransactionLedger, cùng với các chỉ mục phụ để truy xuất dữ liệu được tối ưu hoá và tham chiếu mô hình Vertex AI.

Sơ đồ quan hệ thực thể

Bảng TransactionLedger được xen kẽ trong Tài khoản để nâng cao hiệu suất truy vấn cho các giao dịch dành riêng cho tài khoản thông qua việc cải thiện tính cục bộ của dữ liệu.

Các chỉ mục phụ (CustomersByEmail, CustomersFuzzyEmail, AccountsByCustomer, TransactionLedgerByAccountType, TransactionLedgerByCategory, TransactionLedgerTextSearch) được triển khai để tối ưu hoá các mẫu truy cập dữ liệu phổ biến được sử dụng trong lớp học lập trình này, chẳng hạn như tra cứu khách hàng theo email chính xác và không chính xác, truy xuất tài khoản theo khách hàng, cũng như truy vấn và tìm kiếm dữ liệu giao dịch một cách hiệu quả.

TransactionCategoryModel tận dụng Vertex AI để cho phép các lệnh gọi SQL trực tiếp đến LLM. Lệnh gọi này được dùng để phân loại giao dịch động trong lớp học lập trình này.

Tóm tắt

Ở bước này, bạn đã tạo cơ sở dữ liệu và giản đồ Spanner.

Tiếp theo

Tiếp theo, bạn sẽ tải dữ liệu ứng dụng mẫu.

5. Tải dữ liệu

Bây giờ, bạn sẽ thêm chức năng để tải dữ liệu mẫu từ tệp CSV vào cơ sở dữ liệu.

Mở App.java và bắt đầu bằng cách thay thế các lệnh nhập:

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;

Sau đó, hãy thêm các phương thức chèn vào lớp App:

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

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

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

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

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

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

              count++;
            }

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

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

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

Thêm một câu lệnh case khác trong phương thức main để chèn trong 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;

Cuối cùng, hãy thêm cách sử dụng tính năng chèn vào phương thức printUsageAndExit:

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

Lưu các thay đổi bạn đã thực hiện đối với App.java.

Tạo lại ứng dụng:

mvn package

Chèn dữ liệu mẫu bằng cách chạy lệnh insert:

java -jar target/onlinebanking.jar insert

Kết quả đầu ra dự kiến:

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

Trong Spanner Console, hãy quay lại Spanner Studio cho phiên bản và cơ sở dữ liệu của bạn. Sau đó, hãy chọn bảng TransactionLedger rồi nhấp vào "Dữ liệu" trong thanh bên để xác minh dữ liệu đã được tải. Bảng phải có 200 hàng.

Xem dữ liệu

Tóm tắt

Ở bước này, bạn đã chèn dữ liệu mẫu vào cơ sở dữ liệu.

Tiếp theo

Tiếp theo, bạn sẽ tận dụng tính năng tích hợp Vertex AI để tự động phân loại các giao dịch ngân hàng ngay trong Spanner SQL.

6. Phân loại dữ liệu bằng Vertex AI

Trong bước này, bạn sẽ tận dụng sức mạnh của Vertex AI để tự động phân loại các giao dịch tài chính ngay trong Spanner SQL. Với Vertex AI, bạn có thể chọn một mô hình được huấn luyện sẵn hiện có hoặc huấn luyện và triển khai mô hình của riêng mình. Xem các mô hình có sẵn trong Vertex AI Model Garden.

Trong lớp học lập trình này, chúng ta sẽ sử dụng một trong các mô hình Gemini, Gemini Flash Lite. Phiên bản Gemini này tiết kiệm chi phí nhưng vẫn có thể xử lý hầu hết khối lượng công việc hằng ngày.

Hiện tại, chúng ta có một số giao dịch tài chính mà chúng ta muốn phân loại (groceries, transportation, v.v.) tuỳ thuộc vào nội dung mô tả. Chúng ta có thể thực hiện việc này bằng cách đăng ký một mô hình trong Spanner, sau đó sử dụng ML.PREDICT để gọi mô hình AI.

Trong ứng dụng ngân hàng, chúng ta có thể muốn phân loại các giao dịch để hiểu rõ hơn về hành vi của khách hàng. Nhờ đó, chúng ta có thể cá nhân hoá dịch vụ, phát hiện các giao dịch bất thường hiệu quả hơn hoặc giúp khách hàng theo dõi ngân sách của họ theo từng tháng.

Bước đầu tiên đã được thực hiện khi chúng ta tạo cơ sở dữ liệu và giản đồ, tạo ra một mô hình như sau:

tạo câu lệnh mô hình

Tiếp theo, chúng ta sẽ thêm một phương thức vào ứng dụng để gọi ML.PREDICT.

Mở App.java rồi thêm phương thức 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;
    }
  }

Thêm một câu lệnh case khác trong phương thức main để phân loại:

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

Cuối cùng, hãy thêm cách sử dụng tính năng phân loại vào phương thức printUsageAndExit:

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

Lưu các thay đổi bạn đã thực hiện đối với App.java.

Tạo lại ứng dụng:

mvn package

Phân loại các giao dịch trong cơ sở dữ liệu bằng cách chạy lệnh categorize:

java -jar target/onlinebanking.jar categorize

Kết quả đầu ra dự kiến:

Categorizing transactions...
Completed categorizing transactions

Trong Spanner Studio, hãy chạy câu lệnh Xem trước dữ liệu cho bảng TransactionLedger. Giờ đây, cột Category sẽ được điền sẵn cho tất cả các hàng.

Xem dữ liệu được phân loại

Giờ đây, khi đã phân loại các giao dịch, chúng ta có thể sử dụng thông tin này cho các truy vấn nội bộ hoặc dành cho khách hàng. Trong bước tiếp theo, chúng ta sẽ xem xét cách tìm số tiền mà một khách hàng cụ thể chi tiêu trong một danh mục trong tháng.

Tóm tắt

Ở bước này, bạn đã sử dụng một mô hình được huấn luyện trước để phân loại dữ liệu bằng AI.

Tiếp theo

Tiếp theo, bạn sẽ sử dụng tính năng mã hoá để thực hiện tìm kiếm mờ và tìm kiếm toàn văn bản.

7. Truy vấn bằng phương thức tìm kiếm toàn bộ văn bản

Thêm mã truy vấn

Spanner cung cấp nhiều cụm từ tìm kiếm toàn văn bản. Trong bước này, bạn sẽ thực hiện tìm kiếm khớp chính xác, sau đó tìm kiếm bằng kiểu khớp mờ và tìm kiếm toàn bộ văn bản.

Mở App.java và bắt đầu bằng cách thay thế các lệnh nhập:

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;

Sau đó, hãy thêm các phương thức truy vấn:

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

Thêm một câu lệnh case khác trong phương thức main cho truy vấn:

        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;

Cuối cùng, hãy thêm cách sử dụng các lệnh truy vấn vào phương thức printUsageAndExit:

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

Lưu các thay đổi bạn đã thực hiện đối với App.java.

Tạo lại ứng dụng:

mvn package

Tìm kiếm kiểu khớp chính xác cho số dư tài khoản của khách hàng

Truy vấn khớp chính xác tìm kiếm các hàng khớp chính xác với một cụm từ.

Để cải thiện hiệu suất, một chỉ mục đã được thêm khi bạn tạo cơ sở dữ liệu và giản đồ:

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

Phương thức getBalance ngầm sử dụng chỉ mục này để tìm những khách hàng khớp với customerId đã cung cấp, đồng thời kết hợp với các tài khoản thuộc về khách hàng đó.

Dưới đây là nội dung truy vấn khi được thực thi trực tiếp trong Spanner Studio: truy vấn số dư theo cách thủ công

Liệt kê(các) số dư tài khoản của khách hàng 1 bằng cách chạy lệnh:

java -jar target/onlinebanking.jar query balance 1

Kết quả đầu ra dự kiến:

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

Có 100 khách hàng, vì vậy, bạn cũng có thể truy vấn số dư tài khoản của bất kỳ khách hàng nào khác bằng cách chỉ định một mã khách hàng khác:

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

Tìm kiếm mờ theo email của khách hàng

Tính năng tìm kiếm mờ giúp tìm những cụm từ tìm kiếm gần khớp, bao gồm cả các biến thể chính tả và lỗi chính tả.

Chỉ mục n-gram đã được thêm khi bạn tạo cơ sở dữ liệu và giản đồ:

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

Phương thức findCustomers sử dụng SEARCH_NGRAMSSCORE_NGRAMS để truy vấn theo chỉ mục này nhằm tìm khách hàng theo email. Vì cột email đã được mã hoá thành mã n-gram nên truy vấn này có thể chứa lỗi chính tả nhưng vẫn trả về câu trả lời chính xác. Kết quả được sắp xếp dựa trên mức độ phù hợp nhất.

Tìm địa chỉ email khách hàng khớp chứa madi bằng cách chạy lệnh:

java -jar target/onlinebanking.jar query email madi

Kết quả đầu ra dự kiến:

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

Phản hồi này cho thấy các kết quả phù hợp nhất bao gồm madi hoặc một chuỗi tương tự, theo thứ tự được xếp hạng.

Dưới đây là nội dung truy vấn nếu được thực thi trực tiếp trong Spanner Studio: tìm kiếm madi theo cách thủ công

Tính năng tìm kiếm mờ cũng có thể giúp bạn tìm được các lỗi chính tả, chẳng hạn như lỗi chính tả của 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

Kết quả đầu ra dự kiến:

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

Trong mỗi trường hợp, email khách hàng dự kiến sẽ được trả về dưới dạng kết quả hàng đầu.

Tính năng tìm kiếm toàn bộ văn bản của Spanner được dùng để truy xuất bản ghi dựa trên từ khoá hoặc cụm từ. Tính năng này có thể sửa lỗi chính tả hoặc tìm từ đồng nghĩa.

Chỉ mục tìm kiếm toàn bộ văn bản đã được thêm khi bạn tạo cơ sở dữ liệu và giản đồ:

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

Phương thức getSpending sử dụng hàm tìm kiếm toàn văn bản SEARCH để so khớp với chỉ mục đó. Hàm này tìm tất cả khoản chi tiêu (phát sinh nợ) trong 30 ngày qua cho mã khách hàng đã cho.

Lấy tổng mức chi tiêu trong tháng trước cho khách hàng 1 trong danh mục groceries bằng cách chạy lệnh:

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

Kết quả đầu ra dự kiến:

Total spending for customer 1 under category groceries:
  50

Bạn cũng có thể tìm thấy mức chi tiêu theo các danh mục khác (mà chúng ta đã phân loại ở bước trước) hoặc sử dụng một mã khách hàng khác:

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

Tóm tắt

Trong bước này, bạn đã thực hiện các cụm từ tìm kiếm khớp chính xác cũng như tìm kiếm mờ và tìm kiếm toàn bộ văn bản.

Tiếp theo

Tiếp theo, bạn sẽ tích hợp Spanner với Google BigQuery để thực hiện các truy vấn liên kết, cho phép bạn kết hợp dữ liệu Spanner theo thời gian thực với dữ liệu BigQuery.

8. Chạy truy vấn liên kết với BigQuery

Tạo tập dữ liệu BigQuery

Trong bước này, bạn sẽ kết hợp dữ liệu BigQuery và Spanner thông qua việc sử dụng truy vấn liên kết.

Để thực hiện việc này, trong dòng lệnh Cloud Shell, trước tiên, hãy tạo một tập dữ liệu MarketingCampaigns:

bq mk --location=us-central1 MarketingCampaigns

Kết quả đầu ra dự kiến:

Dataset '<PROJECT_ID>:MarketingCampaigns' successfully created.

Và một bảng CustomerSegments trong tập dữ liệu:

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

Kết quả đầu ra dự kiến:

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

Tiếp theo, hãy tạo kết nối từ BigQuery đến 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

Kết quả đầu ra dự kiến:

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

Cuối cùng, hãy thêm một số khách hàng vào bảng BigQuery có thể được kết hợp với dữ liệu Spanner của chúng ta:

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

Kết quả đầu ra dự kiến:

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

Bạn có thể xác minh dữ liệu có sẵn bằng cách truy vấn BigQuery:

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

Kết quả đầu ra dự kiến:

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

Dữ liệu này trong BigQuery thể hiện dữ liệu đã được thêm thông qua nhiều quy trình làm việc của ngân hàng. Ví dụ: đây có thể là danh sách khách hàng đã mở tài khoản hoặc đăng ký một chương trình khuyến mãi gần đây. Để xác định danh sách khách hàng mà chúng ta muốn nhắm đến trong chiến dịch tiếp thị, chúng ta cần truy vấn cả dữ liệu này trong BigQuery và dữ liệu theo thời gian thực trong Spanner. Truy vấn liên kết cho phép chúng ta thực hiện việc này trong một truy vấn duy nhất.

Chạy truy vấn liên kết với BigQuery

Tiếp theo, chúng ta sẽ thêm một phương thức vào ứng dụng để gọi EXTERNAL_QUERY nhằm thực hiện truy vấn liên kết. Điều này sẽ cho phép kết hợp và phân tích dữ liệu khách hàng trên BigQuery và Spanner, chẳng hạn như xác định những khách hàng đáp ứng tiêu chí cho chiến dịch tiếp thị của chúng tôi dựa trên mức chi tiêu gần đây của họ.

Mở App.java và bắt đầu bằng cách thay thế các lệnh nhập:

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;

Sau đó, hãy thêm phương thức 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);
    }
  }

Thêm một câu lệnh case khác trong phương thức main cho chiến dịch:

        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;

Cuối cùng, hãy thêm cách sử dụng chiến dịch vào phương thức 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");

Lưu các thay đổi bạn đã thực hiện đối với App.java.

Tạo lại ứng dụng:

mvn package

Chạy truy vấn liên kết để xác định những khách hàng nên được đưa vào chiến dịch tiếp thị (campaign1) nếu họ đã chi tiêu ít nhất $5000 trong 3 tháng qua bằng cách chạy lệnh campaign:

java -jar target/onlinebanking.jar campaign campaign1 5000

Kết quả đầu ra dự kiến:

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

Giờ đây, chúng ta có thể nhắm đến những khách hàng này bằng các ưu đãi hoặc phần thưởng độc quyền.

Hoặc chúng ta có thể tìm kiếm một số lượng lớn hơn những khách hàng đã đạt ngưỡng chi tiêu nhỏ hơn trong 3 tháng qua:

java -jar target/onlinebanking.jar campaign campaign1 2500

Kết quả đầu ra dự kiến:

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

Tóm tắt

Ở bước này, bạn đã thực thi thành công các truy vấn liên kết từ BigQuery, nhờ đó thu thập được dữ liệu Spanner theo thời gian thực.

Tiếp theo

Tiếp theo, bạn có thể dọn dẹp các tài nguyên đã tạo cho lớp học lập trình này để tránh bị tính phí.

9. Dọn dẹp (không bắt buộc)

Bước này là bước không bắt buộc. Nếu muốn tiếp tục thử nghiệm với thực thể Spanner, bạn không cần phải dọn dẹp thực thể đó tại thời điểm này. Tuy nhiên, dự án bạn đang sử dụng sẽ tiếp tục bị tính phí cho thực thể đó. Nếu không cần đến phiên bản này nữa, bạn nên xoá phiên bản này ngay để tránh các khoản phí này. Ngoài thực thể Spanner, lớp học lập trình này cũng đã tạo một tập dữ liệu và kết nối BigQuery. Bạn nên dọn dẹp các tập dữ liệu và kết nối này khi không cần đến nữa.

Xoá thực thể Spanner:

gcloud spanner instances delete cloudspanner-onlinebanking

Xác nhận bạn muốn tiếp tục (nhập Y):

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

Do you want to continue (Y/n)?

Xoá mối kết nối và tập dữ liệu BigQuery:

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

Xác nhận xoá tập dữ liệu BigQuery (nhập Y):

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

10. Xin chúc mừng

🚀 Bạn đã tạo một thực thể Cloud Spanner mới, tạo một cơ sở dữ liệu trống, tải dữ liệu mẫu, thực hiện các thao tác và truy vấn nâng cao, đồng thời xoá thực thể Cloud Spanner (không bắt buộc).

Nội dung đã đề cập

  • Cách thiết lập một thực thể Spanner.
  • Cách tạo cơ sở dữ liệu và bảng.
  • Cách tải dữ liệu vào các bảng cơ sở dữ liệu Spanner.
  • Cách gọi các mô hình Vertex AI từ Spanner.
  • Cách truy vấn cơ sở dữ liệu Spanner bằng tính năng tìm kiếm mờ và tìm kiếm toàn bộ văn bản.
  • Cách thực hiện truy vấn liên kết trên Spanner từ BigQuery.
  • Cách xoá thực thể Spanner.

Tiếp theo là gì?