Mulai menggunakan Spanner di Java


Tujuan

Tutorial ini akan memandu Anda melalui langkah-langkah berikut menggunakan library klien Spanner untuk Java:

  • Buat instance dan database Spanner.
  • Menulis, membaca, dan menjalankan kueri SQL pada data di database.
  • Perbarui skema database.
  • Perbarui data menggunakan transaksi baca-tulis.
  • Tambahkan indeks sekunder ke database.
  • Gunakan indeks untuk membaca dan menjalankan kueri SQL pada data.
  • Mengambil data menggunakan transaksi hanya baca.

Biaya

Tutorial ini menggunakan Spanner, yang merupakan komponenGoogle Cloudyang dapat ditagih. Untuk mengetahui informasi tentang biaya penggunaan Spanner, lihat Harga.

Sebelum memulai

Selesaikan langkah-langkah yang dijelaskan dalam Menyiapkan, yang mencakup pembuatan dan penetapan project Google Cloud default, pengaktifan penagihan, pengaktifan Cloud Spanner API, dan penyiapan OAuth 2.0 untuk mendapatkan kredensial autentikasi guna menggunakan Cloud Spanner API.

Khususnya, pastikan Anda menjalankan gcloud auth application-default login untuk menyiapkan lingkungan pengembangan lokal dengan kredensial autentikasi.

Siapkan lingkungan Java lokal Anda

  1. Instal komponen berikut di mesin pengembangan Anda jika belum diinstal:

  2. Clone repositori aplikasi contoh ke komputer lokal Anda:

    git clone https://github.com/googleapis/java-spanner.git
    
  3. Ubah ke direktori yang berisi kode contoh Spanner:

    cd java-spanner/samples/snippets
    
  4. Buat file JAR sampel:

    mvn clean package
    

Membuat instance

Saat pertama kali menggunakan Spanner, Anda harus membuat instance, yang merupakan alokasi resource yang digunakan oleh database Spanner. Saat membuat instance, Anda memilih konfigurasi instance, yang menentukan tempat penyimpanan data Anda, dan juga jumlah node yang akan digunakan, yang menentukan jumlah resource penayangan dan penyimpanan di instance Anda.

Jalankan perintah berikut untuk membuat instance Spanner di region us-central1 dengan 1 node:

gcloud spanner instances create test-instance --config=regional-us-central1 \
    --description="Test Instance" --nodes=1

Perhatikan bahwa perintah ini membuat instance dengan karakteristik berikut:

  • ID Instance test-instance
  • Nama tampilan Test Instance
  • Konfigurasi instance regional-us-central1 (Konfigurasi regional menyimpan data di satu region, sementara konfigurasi multi-region mendistribusikan data di beberapa region. Untuk mengetahui informasi selengkapnya, lihat Tentang instance.)
  • Jumlah node 1 (node_count sesuai dengan jumlah resource serving dan penyimpanan yang tersedia untuk database di instance. Pelajari lebih lanjut di Node dan unit pemrosesan.)

Anda akan melihat:

Creating instance...done.

Melihat-lihat file contoh

Repositori contoh berisi contoh yang menunjukkan cara menggunakan Spanner dengan Java.

Buat database

GoogleSQL

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
createdatabase test-instance example-db

PostgreSQL

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
createpgdatabase test-instance example-db

Anda akan melihat:

Created database [example-db]
Kode berikut membuat database dan dua tabel dalam database.

GoogleSQL

static void createDatabase(DatabaseAdminClient dbAdminClient,
    InstanceName instanceName, String databaseId) {
  CreateDatabaseRequest createDatabaseRequest =
      CreateDatabaseRequest.newBuilder()
          .setCreateStatement("CREATE DATABASE `" + databaseId + "`")
          .setParent(instanceName.toString())
          .addAllExtraStatements(Arrays.asList(
              "CREATE TABLE Singers ("
                  + "  SingerId   INT64 NOT NULL,"
                  + "  FirstName  STRING(1024),"
                  + "  LastName   STRING(1024),"
                  + "  SingerInfo BYTES(MAX),"
                  + "  FullName STRING(2048) AS "
                  + "  (ARRAY_TO_STRING([FirstName, LastName], \" \")) STORED"
                  + ") PRIMARY KEY (SingerId)",
              "CREATE TABLE Albums ("
                  + "  SingerId     INT64 NOT NULL,"
                  + "  AlbumId      INT64 NOT NULL,"
                  + "  AlbumTitle   STRING(MAX)"
                  + ") PRIMARY KEY (SingerId, AlbumId),"
                  + "  INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build();
  try {
    // Initiate the request which returns an OperationFuture.
    com.google.spanner.admin.database.v1.Database db =
        dbAdminClient.createDatabaseAsync(createDatabaseRequest).get();
    System.out.println("Created database [" + db.getName() + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void createPostgreSqlDatabase(
    DatabaseAdminClient dbAdminClient, String projectId, String instanceId, String databaseId) {
  final CreateDatabaseRequest request =
      CreateDatabaseRequest.newBuilder()
          .setCreateStatement("CREATE DATABASE \"" + databaseId + "\"")
          .setParent(InstanceName.of(projectId, instanceId).toString())
          .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build();

  try {
    // Initiate the request which returns an OperationFuture.
    Database db = dbAdminClient.createDatabaseAsync(request).get();
    System.out.println("Created database [" + db.getName() + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}
static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    dbAdminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE TABLE Singers ("
                + "  SingerId   bigint NOT NULL,"
                + "  FirstName  character varying(1024),"
                + "  LastName   character varying(1024),"
                + "  SingerInfo bytea,"
                + "  FullName character varying(2048) GENERATED "
                + "  ALWAYS AS (FirstName || ' ' || LastName) STORED,"
                + "  PRIMARY KEY (SingerId)"
                + ")",
            "CREATE TABLE Albums ("
                + "  SingerId     bigint NOT NULL,"
                + "  AlbumId      bigint NOT NULL,"
                + "  AlbumTitle   character varying(1024),"
                + "  PRIMARY KEY (SingerId, AlbumId)"
                + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get();
    System.out.println("Created Singers & Albums tables in database: [" + databaseName + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw SpannerExceptionFactory.asSpannerException(e);
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Langkah berikutnya adalah menulis data ke database Anda.

Membuat klien database

Sebelum dapat melakukan operasi baca atau tulis, Anda harus membuat DatabaseClient. Anda dapat menganggap DatabaseClient sebagai koneksi database: semua interaksi Anda dengan Spanner harus melalui DatabaseClient. Biasanya, Anda membuat DatabaseClient saat aplikasi dimulai, lalu menggunakan kembali DatabaseClient tersebut untuk membaca, menulis, dan mengeksekusi transaksi.

SpannerOptions options = SpannerOptions.newBuilder().build();
Spanner spanner = options.getService();
DatabaseAdminClient dbAdminClient = null;
try {
  DatabaseClient dbClient = spanner.getDatabaseClient(db);
  dbAdminClient = spanner.createDatabaseAdminClient();
} finally {
  if (dbAdminClient != null) {
    if (!dbAdminClient.isShutdown() || !dbAdminClient.isTerminated()) {
      dbAdminClient.close();
    }
  }
  spanner.close();
}

Setiap klien menggunakan resource di Spanner, jadi sebaiknya tutup klien yang tidak diperlukan dengan memanggil close().

Baca selengkapnya di referensi Javadoc DatabaseClient.

Menulis data dengan DML

Anda dapat menyisipkan data menggunakan Bahasa Pengolahan Data (DML) dalam transaksi baca-tulis.

Anda menggunakan metode executeUpdate() untuk menjalankan pernyataan DML.

static void writeUsingDml(DatabaseClient dbClient) {
  // Insert 4 singer records
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        String sql =
            "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
                + "(12, 'Melissa', 'Garcia'), "
                + "(13, 'Russell', 'Morales'), "
                + "(14, 'Jacqueline', 'Long'), "
                + "(15, 'Dylan', 'Shaw')";
        long rowCount = transaction.executeUpdate(Statement.of(sql));
        System.out.printf("%d records inserted.\n", rowCount);
        return null;
      });
}

Jalankan sampel menggunakan argumen writeusingdml.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    writeusingdml test-instance example-db

Anda akan melihat:

4 records inserted.

Menulis data dengan mutasi

Anda juga dapat menyisipkan data menggunakan mutasi.

Anda dapat menulis data menggunakan objek Mutation. Objek Mutation adalah penampung untuk operasi mutasi. Mutation mewakili urutan penyisipan, pembaruan, dan penghapusan yang diterapkan Spanner secara atomik ke baris dan tabel yang berbeda dalam database Spanner.

Metode newInsertBuilder() di class Mutation membuat mutasi INSERT, yang menyisipkan baris baru dalam tabel. Jika baris sudah ada, penulisan akan gagal. Atau, Anda dapat menggunakan metode newInsertOrUpdateBuilder untuk membuat mutasi INSERT_OR_UPDATE, yang memperbarui nilai kolom jika baris sudah ada.

Metode write() di class DatabaseClient menulis mutasi. Semua mutasi dalam satu batch diterapkan secara atomik.

Kode ini menunjukkan cara menulis data menggunakan mutasi:

static final List<Singer> SINGERS =
    Arrays.asList(
        new Singer(1, "Marc", "Richards"),
        new Singer(2, "Catalina", "Smith"),
        new Singer(3, "Alice", "Trentor"),
        new Singer(4, "Lea", "Martin"),
        new Singer(5, "David", "Lomond"));

static final List<Album> ALBUMS =
    Arrays.asList(
        new Album(1, 1, "Total Junk"),
        new Album(1, 2, "Go, Go, Go"),
        new Album(2, 1, "Green"),
        new Album(2, 2, "Forever Hold Your Peace"),
        new Album(2, 3, "Terrified"));
static void writeExampleData(DatabaseClient dbClient) {
  List<Mutation> mutations = new ArrayList<>();
  for (Singer singer : SINGERS) {
    mutations.add(
        Mutation.newInsertBuilder("Singers")
            .set("SingerId")
            .to(singer.singerId)
            .set("FirstName")
            .to(singer.firstName)
            .set("LastName")
            .to(singer.lastName)
            .build());
  }
  for (Album album : ALBUMS) {
    mutations.add(
        Mutation.newInsertBuilder("Albums")
            .set("SingerId")
            .to(album.singerId)
            .set("AlbumId")
            .to(album.albumId)
            .set("AlbumTitle")
            .to(album.albumTitle)
            .build());
  }
  dbClient.write(mutations);
}

Jalankan sampel menggunakan argumen write.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    write test-instance example-db

Anda akan melihat perintah berhasil dijalankan.

Membuat kueri data menggunakan SQL

Spanner mendukung antarmuka SQL untuk membaca data, yang dapat Anda akses di command line menggunakan Google Cloud CLI atau secara terprogram menggunakan library klien Spanner untuk Java.

Di command line

Jalankan pernyataan SQL berikut untuk membaca nilai semua kolom dari tabel Albums:

gcloud spanner databases execute-sql example-db --instance=test-instance \
    --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

Hasilnya adalah:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Menggunakan library klien Spanner untuk Java

Selain menjalankan pernyataan SQL di command line, Anda dapat mengeluarkan pernyataan SQL yang sama secara terprogram menggunakan library klien Spanner untuk Java.

Metode dan class berikut digunakan untuk menjalankan kueri SQL:

  • Metode singleUse() di class DatabaseClient: gunakan ini untuk membaca nilai satu atau beberapa kolom dari satu atau beberapa baris dalam tabel Spanner. singleUse() menampilkan objek ReadContext, yang digunakan untuk menjalankan pernyataan SQL atau baca.
  • Metode executeQuery() dari class ReadContext: gunakan metode ini untuk menjalankan kueri terhadap database.
  • Class Statement: gunakan class ini untuk membuat string SQL.
  • Class ResultSet: gunakan class ini untuk mengakses data yang ditampilkan oleh pernyataan SQL atau panggilan baca.

Berikut cara mengeluarkan kueri dan mengakses data:

static void query(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Jalankan sampel menggunakan argumen query.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    query test-instance example-db

Anda akan melihat hasil berikut:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Membuat kueri menggunakan parameter SQL

Jika aplikasi Anda memiliki kueri yang sering dijalankan, Anda dapat meningkatkan performanya dengan memparameterisasinya. Kueri parametrik yang dihasilkan dapat di-cache dan digunakan kembali, yang mengurangi biaya kompilasi. Untuk mengetahui informasi selengkapnya, lihat Menggunakan parameter kueri untuk mempercepat kueri yang sering dijalankan.

Berikut adalah contoh penggunaan parameter dalam klausa WHERE untuk mengkueri data yang berisi nilai tertentu untuk LastName.

GoogleSQL

static void queryWithParameter(DatabaseClient dbClient) {
  Statement statement =
      Statement.newBuilder(
              "SELECT SingerId, FirstName, LastName "
                  + "FROM Singers "
                  + "WHERE LastName = @lastName")
          .bind("lastName")
          .to("Garcia")
          .build();
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getString("FirstName"),
          resultSet.getString("LastName"));
    }
  }
}

PostgreSQL

static void queryWithParameter(DatabaseClient dbClient) {
  Statement statement =
      Statement.newBuilder(
              "SELECT singerid AS \"SingerId\", "
                  + "firstname as \"FirstName\", lastname as \"LastName\" "
                  + "FROM Singers "
                  + "WHERE LastName = $1")
          .bind("p1")
          .to("Garcia")
          .build();
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getString("FirstName"),
          resultSet.getString("LastName"));
    }
  }
}

Jalankan contoh menggunakan argumen queryWithParameter.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    querywithparameter test-instance example-db

Anda akan melihat hasil berikut:

12 Melissa Garcia

Membaca data menggunakan read API

Selain antarmuka SQL Spanner, Spanner juga mendukung antarmuka baca.

Gunakan metode read() dari class ReadContext untuk membaca baris dari database. Gunakan objek KeySet untuk menentukan kumpulan kunci dan rentang kunci yang akan dibaca.

Berikut cara membaca data:

static void read(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .read(
              "Albums",
              KeySet.all(), // Read all rows in a table.
              Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Jalankan sampel menggunakan argumen read.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    read test-instance example-db

Anda akan melihat output yang mirip dengan:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Memperbarui skema database

Misalkan Anda perlu menambahkan kolom baru bernama MarketingBudget ke tabel Albums. Menambahkan kolom baru ke tabel yang sudah ada memerlukan pembaruan pada skema database Anda. Spanner mendukung update skema ke database saat database terus melayani traffic. Pembaruan skema tidak memerlukan database offline dan tidak mengunci seluruh tabel atau kolom; Anda dapat terus menulis data ke database selama pembaruan skema. Baca selengkapnya tentang pembaruan skema yang didukung dan performa perubahan skema di Melakukan pembaruan skema.

Menambahkan kolom

Anda dapat menambahkan kolom di command line menggunakan Google Cloud CLI atau secara terprogram menggunakan library klien Spanner untuk Java.

Di command line

Gunakan perintah ALTER TABLE berikut untuk menambahkan kolom baru ke tabel:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'

Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk Java

Gunakan metode updateDatabaseDdl() dari class DatabaseAdminClient untuk mengubah skema:

GoogleSQL

static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64")).get();
    System.out.println("Added MarketingBudget column");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint")).get();
    System.out.println("Added MarketingBudget column");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Jalankan sampel menggunakan argumen addmarketingbudget.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addmarketingbudget test-instance example-db

Anda akan melihat:

Added MarketingBudget column.

Menulis data ke kolom baru

Kode berikut menulis data ke kolom baru. Tindakan ini menetapkan MarketingBudget ke 100000 untuk baris yang dikunci oleh Albums(1, 1) dan ke 500000 untuk baris yang dikunci oleh Albums(2, 2).

static void update(DatabaseClient dbClient) {
  // Mutation can be used to update/insert/delete a single row in a table. Here we use
  // newUpdateBuilder to create update mutations.
  List<Mutation> mutations =
      Arrays.asList(
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(1)
              .set("AlbumId")
              .to(1)
              .set("MarketingBudget")
              .to(100000)
              .build(),
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(2)
              .set("AlbumId")
              .to(2)
              .set("MarketingBudget")
              .to(500000)
              .build());
  // This writes all the mutations to Cloud Spanner atomically.
  dbClient.write(mutations);
}

Jalankan sampel menggunakan argumen update.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    update test-instance example-db

Anda juga dapat menjalankan kueri SQL atau panggilan baca untuk mengambil nilai yang baru saja Anda tulis.

Berikut kode untuk menjalankan kueri:

GoogleSQL

static void queryMarketingBudget(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

PostgreSQL

static void queryMarketingBudget(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
              + "albumid as \"AlbumId\", marketingbudget as \"MarketingBudget\" "
              + "FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" :
              resultSet.getLong("MarketingBudget"));
    }
  }
}

Untuk menjalankan kueri ini, jalankan sampel menggunakan argumen querymarketingbudget.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    querymarketingbudget test-instance example-db

Anda akan melihat:

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

Memperbarui data

Anda dapat memperbarui data menggunakan DML dalam transaksi baca-tulis.

Anda menggunakan metode executeUpdate() untuk menjalankan pernyataan DML.

GoogleSQL

static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        // Transfer marketing budget from one album to another. We do it in a transaction to
        // ensure that the transfer is atomic.
        String sql1 =
            "SELECT MarketingBudget from Albums WHERE SingerId = 2 and AlbumId = 2";
        ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
        long album2Budget = 0;
        while (resultSet.next()) {
          album2Budget = resultSet.getLong("MarketingBudget");
        }
        // Transaction will only be committed if this condition still holds at the time of
        // commit. Otherwise it will be aborted and the callable will be rerun by the
        // client library.
        long transfer = 200000;
        if (album2Budget >= transfer) {
          String sql2 =
              "SELECT MarketingBudget from Albums WHERE SingerId = 1 and AlbumId = 1";
          ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
          long album1Budget = 0;
          while (resultSet2.next()) {
            album1Budget = resultSet2.getLong("MarketingBudget");
          }
          album1Budget += transfer;
          album2Budget -= transfer;
          Statement updateStatement =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = @AlbumBudget "
                          + "WHERE SingerId = 1 and AlbumId = 1")
                  .bind("AlbumBudget")
                  .to(album1Budget)
                  .build();
          transaction.executeUpdate(updateStatement);
          Statement updateStatement2 =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = @AlbumBudget "
                          + "WHERE SingerId = 2 and AlbumId = 2")
                  .bind("AlbumBudget")
                  .to(album2Budget)
                  .build();
          transaction.executeUpdate(updateStatement2);
        }
        return null;
      });
}

PostgreSQL

static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        // Transfer marketing budget from one album to another. We do it in a transaction to
        // ensure that the transfer is atomic.
        String sql1 =
            "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
                + "SingerId = 2 and AlbumId = 2";
        ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
        long album2Budget = 0;
        while (resultSet.next()) {
          album2Budget = resultSet.getLong("MarketingBudget");
        }
        // Transaction will only be committed if this condition still holds at the time of
        // commit. Otherwise it will be aborted and the callable will be rerun by the
        // client library.
        long transfer = 200000;
        if (album2Budget >= transfer) {
          String sql2 =
              "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
                  + "SingerId = 1 and AlbumId = 1";
          ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
          long album1Budget = 0;
          while (resultSet2.next()) {
            album1Budget = resultSet2.getLong("MarketingBudget");
          }
          album1Budget += transfer;
          album2Budget -= transfer;
          Statement updateStatement =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = $1 "
                          + "WHERE SingerId = 1 and AlbumId = 1")
                  .bind("p1")
                  .to(album1Budget)
                  .build();
          transaction.executeUpdate(updateStatement);
          Statement updateStatement2 =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = $1 "
                          + "WHERE SingerId = 2 and AlbumId = 2")
                  .bind("p1")
                  .to(album2Budget)
                  .build();
          transaction.executeUpdate(updateStatement2);
        }
        return null;
      });
}

Jalankan sampel menggunakan argumen writewithtransactionusingdml.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    writewithtransactionusingdml test-instance example-db

Menggunakan indeks sekunder

Misalnya, Anda ingin mengambil semua baris Albums yang memiliki nilai AlbumTitle dalam rentang tertentu. Anda dapat membaca semua nilai dari kolom AlbumTitle menggunakan pernyataan SQL atau panggilan baca, lalu menghapus baris yang tidak memenuhi kriteria, tetapi pemindaian tabel lengkap ini mahal, terutama untuk tabel dengan banyak baris. Sebagai gantinya, Anda dapat mempercepat pengambilan baris saat mencari menurut kolom non-kunci primer dengan membuat indeks sekunder pada tabel.

Menambahkan indeks sekunder ke tabel yang ada memerlukan pembaruan skema. Seperti update skema lainnya, Spanner mendukung penambahan indeks saat database terus melayani traffic. Spanner akan otomatis mengisi ulang indeks dengan data yang ada. Pengisian ulang mungkin memerlukan waktu beberapa menit untuk selesai, tetapi Anda tidak perlu menghentikan database atau menghindari penulisan ke tabel yang diindeks selama proses ini. Untuk mengetahui detail selengkapnya, lihat Menambahkan indeks sekunder.

Setelah Anda menambahkan indeks sekunder, Spanner akan otomatis menggunakannya untuk kueri SQL yang kemungkinan akan berjalan lebih cepat dengan indeks tersebut. Jika menggunakan antarmuka baca, Anda harus menentukan indeks yang ingin digunakan.

Menambahkan indeks sekunder

Anda dapat menambahkan indeks di command line menggunakan gcloud CLI atau secara terprogram menggunakan library klien Spanner untuk Java.

Di command line

Gunakan perintah CREATE INDEX berikut untuk menambahkan indeks ke database:

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk Java

Gunakan metode updateDatabaseDdl() dari class DatabaseAdminClient untuk menambahkan indeks:

static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)")).get();
    System.out.println("Added AlbumsByAlbumTitle index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Jalankan sampel menggunakan argumen addindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addindex test-instance example-db

Menambahkan indeks dapat memerlukan waktu beberapa menit. Setelah indeks ditambahkan, Anda akan melihat:

Added the AlbumsByAlbumTitle index.

Membaca menggunakan indeks

Untuk kueri SQL, Spanner otomatis menggunakan indeks yang sesuai. Di antarmuka baca, Anda harus menentukan indeks dalam permintaan.

Untuk menggunakan indeks di antarmuka baca, gunakan metode readUsingIndex() dari class ReadContext.

Kode berikut mengambil semua kolom AlbumId dan AlbumTitle dari indeks AlbumsByAlbumTitle.

static void readUsingIndex(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
    }
  }
}

Jalankan sampel menggunakan argumen readindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readindex test-instance example-db

Anda akan melihat:

2 Forever Hold Your Peace
2 Go, Go, Go
1 Green
3 Terrified
1 Total Junk

Menambahkan indeks untuk pembacaan khusus indeks

Anda mungkin telah memperhatikan bahwa contoh baca sebelumnya tidak menyertakan pembacaan kolom MarketingBudget. Hal ini karena antarmuka baca Spanner tidak mendukung kemampuan untuk menggabungkan indeks dengan tabel data guna mencari nilai yang tidak disimpan dalam indeks.

Buat definisi alternatif AlbumsByAlbumTitle yang menyimpan salinan MarketingBudget dalam indeks.

Di command line

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)

Menambahkan indeks dapat memerlukan waktu beberapa menit. Setelah indeks ditambahkan, Anda akan melihat:

Schema updating...done.

Menggunakan library klien Spanner untuk Java

Gunakan metode updateDatabaseDdl() kelas DatabaseAdminClient untuk menambahkan indeks dengan klausul STORING untuk GoogleSQL dan klausul INCLUDE untuk PostgreSQL:

GoogleSQL

static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
                + "STORING (MarketingBudget)")).get();
    System.out.println("Added AlbumsByAlbumTitle2 index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
                + "INCLUDE (MarketingBudget)")).get();
    System.out.println("Added AlbumsByAlbumTitle2 index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Jalankan sampel menggunakan argumen addstoringindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addstoringindex test-instance example-db

Menambahkan indeks dapat memerlukan waktu beberapa menit. Setelah indeks ditambahkan, Anda akan melihat:

Added AlbumsByAlbumTitle2 index

Sekarang Anda dapat menjalankan operasi baca yang mengambil semua kolom AlbumId, AlbumTitle, dan MarketingBudget dari indeks AlbumsByAlbumTitle2:

static void readStoringIndex(DatabaseClient dbClient) {
  // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle2",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong(0),
          resultSet.getString(1),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Jalankan sampel menggunakan argumen readstoringindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readstoringindex test-instance example-db

Anda akan melihat output yang mirip dengan:

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 300000

Mengambil data menggunakan transaksi hanya baca

Misalkan Anda ingin menjalankan lebih dari satu pembacaan pada stempel waktu yang sama. Transaksi hanya baca mengamati awalan yang konsisten dari histori commit transaksi, sehingga aplikasi Anda selalu mendapatkan data yang konsisten. Gunakan objek ReadOnlyTransaction untuk menjalankan transaksi hanya baca. Gunakan metode readOnlyTransaction() dari class DatabaseClient untuk mendapatkan objek ReadOnlyTransaction.

Berikut cara menjalankan kueri dan melakukan pembacaan dalam transaksi hanya baca yang sama:

static void readOnlyTransaction(DatabaseClient dbClient) {
  // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
  // We use a try-with-resource block to automatically do so.
  try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
    try (ResultSet queryResultSet =
        transaction.executeQuery(
            Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
      while (queryResultSet.next()) {
        System.out.printf(
            "%d %d %s\n",
            queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2));
      }
    } // queryResultSet.close() is automatically called here
    try (ResultSet readResultSet =
        transaction.read(
          "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
      while (readResultSet.next()) {
        System.out.printf(
            "%d %d %s\n",
            readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2));
      }
    } // readResultSet.close() is automatically called here
  } // transaction.close() is automatically called here
}

Jalankan sampel menggunakan argumen readonlytransaction.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readonlytransaction test-instance example-db

Anda akan melihat output yang mirip dengan:

2 2 Forever Hold Your Peace
1 2 Go, Go, Go
2 1 Green
2 3 Terrified
1 1 Total Junk
1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Pembersihan

Agar tidak menimbulkan biaya tambahan pada akun Penagihan Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus database dan hapus instance yang Anda buat.

Menghapus database

Jika Anda menghapus instance, semua database di dalamnya akan otomatis dihapus. Langkah ini menunjukkan cara menghapus database tanpa menghapus instance (Anda tetap dikenai biaya untuk instance tersebut).

Di command line

gcloud spanner databases delete example-db --instance=test-instance

Menggunakan Google Cloud console

  1. Buka halaman Spanner Instances di konsol Google Cloud .

    Buka halaman Instances

  2. Klik instance.

  3. Klik database yang ingin Anda hapus.

  4. Di halaman Database details, klik Delete.

  5. Konfirmasi bahwa Anda ingin menghapus database, lalu klik Hapus.

Menghapus instance

Menghapus instance akan otomatis menghapus semua database yang dibuat di instance tersebut.

Di command line

gcloud spanner instances delete test-instance

Menggunakan Google Cloud console

  1. Buka halaman Spanner Instances di konsol Google Cloud .

    Buka halaman Instances

  2. Klik instance Anda.

  3. Klik Hapus.

  4. Konfirmasi bahwa Anda ingin menghapus instance, lalu klik Hapus.

Langkah berikutnya