使用开源 JDBC 驱动程序

本页面介绍了如何使用开源 JDBC 驱动程序在 Cloud Spanner 中执行基本操作。

安装 JDBC 驱动程序

按照 Cloud Spanner 客户端库中的步骤设置身份验证,然后将 Cloud Spanner JDBC 驱动程序依赖项(如以下代码段所示)添加到 pom.xml 文件。

<dependencies>
  <!-- The Spanner JDBC driver dependency -->
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner-jdbc</artifactId>
  </dependency>

  <!-- Test dependencies -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>com.google.truth</groupId>
    <artifactId>truth</artifactId>
    <version>1.1.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>
如果您使用的框架需要 Java 类名称才能加载 JDBC 驱动程序,其为 com.google.cloud.spanner.jdbc.JdbcDriver。 如需了解如何设置连接,请参阅 JdbcDriver 的 API 文档

连接到模拟器

如需连接到模拟器,请设置 SPANNER_EMULATOR_HOST 环境变量,例如:

Linux/macOS

export SPANNER_EMULATOR_HOST=localhost:9010

Windows

set SPANNER_EMULATOR_HOST=localhost:9010

这会指示 JDBC 驱动程序连接到 localhost 上运行的模拟器,而不是连接到默认生产服务。

运行架构更新

以下代码示例通过首先创建 JDBC 连接,然后创建表来将 Singers 表添加到数据库:

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

class CreateTableExample {

  static void createTable() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    createTable(projectId, instanceId, databaseId);
  }

  @SuppressFBWarnings(
      value = "OBL_UNSATISFIED_OBLIGATION",
      justification = "https://github.com/spotbugs/spotbugs/issues/293")
  static void createTable(String projectId, String instanceId, String databaseId)
      throws SQLException {
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
            projectId, instanceId, databaseId);
    try (Connection connection = DriverManager.getConnection(connectionUrl)) {
      try (Statement statement = connection.createStatement()) {
        statement.execute(
            "CREATE TABLE Singers (\n"
                + "  SingerId   INT64 NOT NULL,\n"
                + "  FirstName  STRING(1024),\n"
                + "  LastName   STRING(1024),\n"
                + "  SingerInfo BYTES(MAX),\n"
                + "  Revenues   NUMERIC,\n"
                + ") PRIMARY KEY (SingerId)\n");
      }
    }
    System.out.println("Created table [Singers]");
  }
}

在自动提交模式下使用事务来添加行

如果您不需要将多个操作作为一个组提交,则可以在自动提交模式下使用事务,这是默认行为。以下代码示例在自动提交模式下使用事务向 Singers 表添加行:

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;

class InsertDataExample {
  // Class to contain singer sample data.
  static class Singer {
    final long singerId;
    final String firstName;
    final String lastName;
    final BigDecimal revenues;

    Singer(long singerId, String firstName, String lastName, BigDecimal revenues) {
      this.singerId = singerId;
      this.firstName = firstName;
      this.lastName = lastName;
      this.revenues = revenues;
    }
  }

  static final List<Singer> SINGERS =
      Arrays.asList(
          new Singer(10, "Marc", "Richards", new BigDecimal("104100.00")),
          new Singer(20, "Catalina", "Smith", new BigDecimal("9880.99")),
          new Singer(30, "Alice", "Trentor", new BigDecimal("300183")),
          new Singer(40, "Lea", "Martin", new BigDecimal("20118.12")),
          new Singer(50, "David", "Lomond", new BigDecimal("311399.26")));

  static void insertData() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    insertData(projectId, instanceId, databaseId);
  }

  @SuppressFBWarnings(
      value = "OBL_UNSATISFIED_OBLIGATION",
      justification = "https://github.com/spotbugs/spotbugs/issues/293")
  static void insertData(String projectId, String instanceId, String databaseId)
      throws SQLException {
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
            projectId, instanceId, databaseId);
    try (Connection connection = DriverManager.getConnection(connectionUrl)) {
      try (PreparedStatement ps =
          connection.prepareStatement(
              "INSERT INTO Singers\n"
                  + "(SingerId, FirstName, LastName, SingerInfo, Revenues)\n"
                  + "VALUES\n"
                  + "(?, ?, ?, ?, ?)")) {
        for (Singer singer : SINGERS) {
          ps.setLong(1, singer.singerId);
          ps.setString(2, singer.firstName);
          ps.setString(3, singer.lastName);
          ps.setNull(4, Types.BINARY);
          ps.setBigDecimal(5, singer.revenues);
          ps.addBatch();
        }
        int[] updateCounts = ps.executeBatch();
        System.out.printf("Insert counts: %s%n", Arrays.toString(updateCounts));
      }
    }
  }
}

控制以群组形式提交多项操作的方式

如果您想要控制 Cloud Spanner 是否以群组形式同时提交多项操作,您可以停用自动提交模式。以下代码示例使用 connection.setAutoCommit(false)connection.commit()Singers 表添加行。

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;

class BatchDmlExample {

  static void batchDml() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    batchDml(projectId, instanceId, databaseId);
  }

  @SuppressFBWarnings(
      value = "OBL_UNSATISFIED_OBLIGATION",
      justification = "https://github.com/spotbugs/spotbugs/issues/293")
  static void batchDml(String projectId, String instanceId, String databaseId) throws SQLException {
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
            projectId, instanceId, databaseId);
    try (Connection connection = DriverManager.getConnection(connectionUrl)) {
      connection.setAutoCommit(false);
      try (Statement statement = connection.createStatement()) {
        statement.addBatch(
            "INSERT INTO Singers (SingerId, FirstName, LastName, Revenues)\n"
                + "VALUES (10, 'Marc', 'Richards', 100000)");
        statement.addBatch(
            "INSERT INTO Singers (SingerId, FirstName, LastName, Revenues)\n"
                + "VALUES (11, 'Amirah', 'Finney', 195944.10)");
        statement.addBatch(
            "INSERT INTO Singers (SingerId, FirstName, LastName, Revenues)\n"
                + "VALUES (12, 'Reece', 'Dunn', 10449.90)");
        int[] updateCounts = statement.executeBatch();
        connection.commit();
        System.out.printf("Batch insert counts: %s%n", Arrays.toString(updateCounts));
      }
    }
  }
}

运行 SQL 查询

以下代码示例返回 Singers 表中的所有行,按歌手的姓氏排序:

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class SingleUseReadOnlyExample {

  static void singleUseReadOnly() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    singleUseReadOnly(projectId, instanceId, databaseId);
  }

  @SuppressFBWarnings(
      value = "OBL_UNSATISFIED_OBLIGATION",
      justification = "https://github.com/spotbugs/spotbugs/issues/293")
  static void singleUseReadOnly(String projectId, String instanceId, String databaseId)
      throws SQLException {
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
            projectId, instanceId, databaseId);
    try (Connection connection = DriverManager.getConnection(connectionUrl);
        Statement statement = connection.createStatement()) {
      // When the connection is in autocommit mode, any query that is executed will automatically
      // be executed using a single-use read-only transaction, even if the connection itself is in
      // read/write mode.
      try (ResultSet rs =
          statement.executeQuery(
              "SELECT SingerId, FirstName, LastName, Revenues FROM Singers ORDER BY LastName")) {
        while (rs.next()) {
          System.out.printf(
              "%d %s %s %s%n",
              rs.getLong(1), rs.getString(2), rs.getString(3), rs.getBigDecimal(4));
        }
      }
    }
  }
}

会话管理语句

除了 SQL 查询语句之外,Cloud Spanner 的开源 JDBC(Java 数据库连接)驱动程序还支持会话管理语句,让您可以修改连接状态、执行事务并高效执行批量语句。

连接语句

以下语句会更改或显示当前连接的属性。

SHOW VARIABLE READONLY

返回一个结果集,其中包含 BOOL 类型的一行和一列,指示连接当前是否处于只读模式。

SET READONLY

语法
SET READONLY = { true | false }

设置连接是否处于只读模式。

只有在没有活跃事务的情况下您才能执行此语句。

SHOW VARIABLE AUTOCOMMIT

返回一个结果集,其中包含 BOOL 类型的一行和一列,指示连接是否处于 AUTOCOMMIT 模式。

SET AUTOCOMMIT

语法
SET AUTOCOMMIT = { true | false }

设置连接的 AUTOCOMMIT 模式。

只有在没有活跃事务的情况下您才能执行此语句。

SHOW VARIABLE RETRY_ABORTS_INTERNALLY

返回一个结果集,其中包含 BOOL 类型的一行和一列,指示连接是否自动重试已中止的事务。

SET RETRY_ABORTS_INTERNALLY

语法
SET RETRY_ABORTS_INTERNALLY = { true | false }

设置连接是否自动重试已中止的事务。

只有在没有活跃事务的情况下您才能执行此语句。

启用 RETRY_ABORTS_INTERNALLY 后,连接会保持连接返回到客户端应用的所有数据的加密校验和以及连接在事务期间报告的所有更新计数。如果 Cloud Spanner 中止事务,则连接将尝试执行同一事务,并检查返回的数据是否与原始事务返回的数据完全相同。如果数据匹配,则 Cloud Spanner 将继续执行事务。如果数据不匹配,则会抛出 AbortedDueToConcurrentModification 导致事务失败。

此设置默认为启用状态。如果您已经在应用中重试中止的事务,我们建议您停用此设置。

SHOW VARIABLE AUTOCOMMIT_DML_MODE

返回一个结果集,其中包含 STRING 类型的一行和一列,指示数据操纵语言 (DML) 语句的自动提交模式。

只有在您对连接启用 AUTOCOMMIT 模式的情况下此变量才会有效。

SET AUTOCOMMIT_DML_MODE

语法
SET AUTOCOMMIT_DML_MODE = { 'TRANSACTIONAL' | 'PARTITIONED_NON_ATOMIC' }

为 DML 语句设置自动提交模式:

  • TRANSACTIONAL 模式下,驱动程序将 DML 语句作为单独的原子化事务执行。驱动程序会创建新事务、执行 DML 语句,并在成功执行后提交事务,或在发生错误时回滚事务。
  • PARTITIONED_NON_ATOMIC 模式下,驱动程序将 DML 语句作为分区更新语句执行。分区更新语句可以作为一系列多个事务运行,每个事务均覆盖一部分受影响的行,分区语句会提供弱化语义,从而换取更高的可扩缩性和性能。

只有在您已启用 AUTOCOMMIT 模式的情况下才能执行此语句。

SHOW VARIABLE STATEMENT_TIMEOUT

返回一个结果集,其中包含 STRING 类型的一行和一列,指示语句的当前超时值。该值为一个整数,后跟表示时间单位的后缀。值 NULL 表示未设置超时值。如果已设置语句超时值,则超过指定超时值的语句将导致 java.sql.SQLTimeoutException 并使事务失效。

SET STATEMENT_TIMEOUT

语法
SET STATEMENT_TIMEOUT = { '<INT64>{ s | ms | us | ns }' | NULL }

为连接上的所有后续语句设置语句超时值。将超时值设置为 NULL 会对该连接停用语句超时。

受支持的时间单位包括:

  • s:秒
  • ms:毫秒
  • us:微秒
  • ns:纳秒

事务期间语句超时会使事务失效,失效事务中的所有后续语句(ROLLBACK 除外)均会失败,并且 JDBC 驱动程序抛出 java.sql.SQLTimeoutException

SHOW VARIABLE READ_ONLY_STALENESS

返回一个结果集,其中包含类型为 STRING 的一行和一列,指示 Cloud Spanner 在 AUTOCOMMIT 模式下用于只读事务和查询的当前只读过时设置。默认设置为 STRONG

SET READ_ONLY_STALENESS

语法
SET READ_ONLY_STALENESS = { 'STRONG' | 'MIN_READ_TIMESTAMP <timestamp>' | 'READ_TIMESTAMP <timestamp>' |
    'MAX_STALENESS <INT64>{ s | ms | us | ns }' | 'EXACT_STALENESS <INT64>{ s | ms | us | ns }' }

在非 AUTOCOMMIT 模式下为所有后续只读事务设定只读过时设置,在 AUTOCOMMIT 模式下为所有查询设定该设置。

时间戳边界选项如下所示:

时间戳必须采用以下格式:

YYYY-[M]M-[D]DT[[H]H:[M]M:[S]S[.DDDDDD]][timezone]

设置 MAX_STALENESSEXACT_STALENESS 值所支持的时间单位包括:

  • s:秒
  • ms:毫秒
  • us:微秒
  • ns:纳秒

只有在没有活跃事务的情况下您才能执行此语句。

事务语句

以下语句用于管理和提交 Cloud Spanner 事务。

SHOW VARIABLE READ_TIMESTAMP

返回一个结果集,其中包含 TIMESTAMP 类型的一行和一列,该结果集包括最近一次只读事务的读取时间戳。只有在只读事务仍处于活跃状态并且已执行至少一个查询时,或者只有在紧接着已提交只读事务之后且新事务启动之前,此语句才会返回时间戳。否则,结果为 NULL

SHOW VARIABLE COMMIT_TIMESTAMP

返回一个结果集,其中包含 TIMESTAMP 类型的一行和一列,该结果集包括 Cloud Spanner 提交的最近一次读写事务的提交时间戳。只有当您在提交读写事务之后、执行任何后续 SELECTDML 或架构更改语句之前执行此语句时,此语句才会返回时间戳。否则,结果为 NULL

BEGIN [TRANSACTION]

启动新事务。

  • 如果您启用了 AUTOCOMMIT 模式,则此语句会暂时将连接退出 AUTOCOMMIT 模式。当事务结束时,连接将返回 AUTOCOMMIT 模式。
  • 如果您停用了 AUTOCOMMIT 模式,则此语句是可选的,不会产生任何影响。

只有在没有活跃事务的情况下您才能执行此语句。

COMMIT [TRANSACTION]

提交当前事务。

  • 提交读写事务会使此事务的所有更新对其他事务可见,并解除对 Cloud Spanner 的所有事务锁定。
  • 提交只读事务会结束当前的只读事务。任何后续语句都会启动新事务。对于只读事务,COMMITROLLBACK 之间没有语义差异。

只有在存在活跃事务的情况下才能执行此语句。

ROLLBACK [TRANSACTION]

对当前事务执行 ROLLBACK

  • 对读写事务执行 ROLLBACK 会清除所有缓存的变更,在 Cloud Spanner 上回滚该事务并解除该事务持有的所有锁定。
  • 对只读事务执行 ROLLBACK 会结束当前的只读事务。任何后续语句都会启动新事务。对于连接上的只读事务,COMMITROLLBACK 之间没有语义差异。

只有在存在活跃事务的情况下才能执行此语句。

SET TRANSACTION

语法
SET TRANSACTION { READ ONLY | READ WRITE }

设置当前事务的事务模式。

只有在您未启用 AUTOCOMMIT 模式时,或者已通过执行 BEGIN [TRANSACTION] 启动临时事务并且尚未在该事务中执行任何语句的情况下,才能执行此语句。

批量语句

以下语句用于管理批量 DDL 语句并将这些批量 DDL 语句发送到 Cloud Spanner。

START BATCH DDL

在连接上启动一批 DDL 语句。批处理期间的所有后续语句都必须是 DDL 语句。DDL 语句在本地缓存,并在执行 RUN BATCH 时作为一个批次发送到 Cloud Spanner。将多个 DDL 语句作为一个批次执行通常比单独运行语句速度快。

只有在没有活跃事务的情况下您才能执行此语句。

RUN BATCH

将当前 DDL 批次中所有缓存的 DDL 语句发送到数据库,等待 Cloud Spanner 执行这些语句,然后结束当前 DDL 批次。

如果 Cloud Spanner 无法执行至少一个 DDL 语句,则 RUN BATCH 会针对 Cloud Spanner 无法执行的第一个 DDL 语句返回错误。否则,RUN BATCH 会成功返回。

ABORT BATCH

清除当前 DDL 批次中所有缓存的 DDL 语句,然后结束该批次。

只有在 DDL 批次处于活跃状态的情况下您才能执行此语句。无论该批次是否包含缓存的 DDL 语句,您都可以使用 ABORT BATCH

START BATCH DML

以下语句同时批处理两个 DML 语句,并通过一次调用将其发送到服务器。DML 批处理可以作为事务的一部分执行,也可以在自动提交模式下执行。

START BATCH DML
INSERT INTO MYTABLE (ID, NAME) VALUES (1, 'ONE')
INSERT INTO MYTABLE (ID, NAME) VALUES (2, 'TWO')
RUN BATCH

后续步骤

获取有关开源 JDBC 驱动程序的常见问题解答