数据库与持久化
你当前正在浏览历史版本 Vert.x 5.1.10 的文档。 访问本页面的 最新版本。

响应式 DB2 客户端(Reactive DB2 Client)

Reactive DB2 Client 是一款直观易用的 DB2 客户端,专注于高伸缩性与低开销。

该客户端采用响应式与非阻塞设计,允许仅凭单个线程即可处理大量数据库连接。

特性列表

  • 支持运行于 Linux、Unix 以及 Windows 平台的 DB2

  • 对 z/OS 上的 DB2 提供有限支持

  • 事件驱动

  • 轻量高效

  • 内置连接池

  • 预编译查询(Prepared Queries)缓存

  • 批量操作与游标

  • 行流式处理(Row streaming)

  • 提供 RxJava API

  • 零不必要拷贝,直接从内存映射到对象

  • 支持 Java 8 日期和时间类型

  • 支持 SSL/TLS

  • 支持 HTTP/1.x CONNECT、SOCKS4a 或 SOCKS5 代理

当前已知限制

  • 暂不支持存储过程

  • 暂不支持部分列类型(如 BLOB 和 CLOB)

引入依赖(Usage)

要使用 Reactive DB2 Client,请将以下依赖项添加到构建描述文件的 dependencies 部分:

  • Maven(在 pom.xml 中):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-db2-client</artifactId>
  <version>5.1.10</version>
</dependency>
  • Gradle(在 build.gradle 中):

dependencies {
  compile 'io.vertx:vertx-db2-client:5.1.10'
}

快速入门(Getting started)

以下是连接、查询以及断开连接的最简单示例:

DB2ConnectOptions connectOptions = new DB2ConnectOptions()
  .setPort(50000)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret");

// 连接池选项
PoolOptions poolOptions = new PoolOptions()
  .setMaxSize(5);

// 创建客户端连接池
Pool client = DB2Builder.pool()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .build();

// 执行简单查询
client
  .query("SELECT * FROM users WHERE id='julien'")
  .execute()
  .onComplete(ar -> {
  if (ar.succeeded()) {
    RowSet<Row> result = ar.result();
    System.out.println("Got " + result.size() + " rows ");
  } else {
    System.out.println("Failure: " + ar.cause().getMessage());
  }

  // 随后关闭连接池
  client.close();
});

连接至 DB2 数据库

大多数情况下,你会使用连接池来连接 DB2:

DB2ConnectOptions connectOptions = new DB2ConnectOptions()
  .setPort(50000)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret");

// 连接池选项
PoolOptions poolOptions = new PoolOptions()
  .setMaxSize(5);

// 创建池化客户端
SqlClient client = DB2Builder.client()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

池化客户端底层使用连接池,执行任何操作时都会从连接池中借用一个连接,执行完毕后将其归还给连接池。

如果你在 Vert.x 环境中运行,可以将你的 Vertx 实例传递给它:

DB2ConnectOptions connectOptions = new DB2ConnectOptions()
  .setPort(50000)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret");

// 连接池选项
PoolOptions poolOptions = new PoolOptions()
  .setMaxSize(5);
// 创建池化客户端
SqlClient client = DB2Builder.client()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

当你不再需要该客户端时,应当将其释放关闭:

client.close();

当你需要在同一连接上执行多次操作时,需要从连接池中获取一个 连接(connection)。

你可以很方便地从池中获取连接:

DB2ConnectOptions connectOptions = new DB2ConnectOptions()
  .setPort(50000)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret");

// 连接池选项
PoolOptions poolOptions = new PoolOptions()
  .setMaxSize(5);

// 创建连接池客户端
Pool client = DB2Builder.pool()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

// 从连接池获取连接
client.getConnection().compose(conn -> {
  System.out.println("Got a connection from the pool");

  // 所有操作均在同一连接上执行
  return conn
    .query("SELECT * FROM users WHERE id='julien'")
    .execute()
    .compose(res -> conn
      .query("SELECT * FROM users WHERE id='emad'")
      .execute())
    .onComplete(ar -> {
      // 将连接释放回连接池
      conn.close();
    });
}).onComplete(ar -> {
  if (ar.succeeded()) {

    System.out.println("Done");
  } else {
    System.out.println("Something went wrong " + ar.cause().getMessage());
  }
});

连接使用完毕后,你必须将其关闭以归还给连接池,以便连接能够被复用。

连接池 vs 池化客户端(Pool versus pooled client)

DB2Builder 允许你创建连接池(pool)或池化客户端(pooled client):

SqlClient client = DB2Builder.client()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

// 流水线式(Pipelined)执行
Future<RowSet<Row>> res1 = client.query(sql).execute();

// 连接池
Pool pool = DB2Builder.pool()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

// 非流水线式执行
Future<RowSet<Row>> res2 = pool.query(sql).execute();
  • 连接池(pool)操作默认不采用流水线式(pipelined),只有从连接池借出的连接才会进行流水线式处理

  • 池化客户端(pooled client)操作采用流水线式,但你无法直接从池化客户端中借取连接

连接池共享(Pool sharing)

你可以在多个 Verticle 或同一 Verticle 的多个实例之间共享同一个连接池。此类连接池应在 Verticle 外部创建,否则当创建它的 Verticle 被卸载时,连接池也会被随之关闭:

Pool pool = Pool.pool(database, new PoolOptions().setMaxSize(maxSize));
vertx.deployVerticle(() -> new VerticleBase() {
  @Override
  public Future<?> start() throws Exception {
    // 使用连接池
    return super.start();
  }
}, new DeploymentOptions().setInstances(4));

你也可以直接在各个 Verticle 内部声明获取共享连接池:

vertx.deployVerticle(() -> new VerticleBase() {
  Pool pool;
  @Override
  public Future<?> start() throws Exception {
    // 获取或创建共享连接池
    // 本质上是获取对该池的一份租约(Lease)
    // 当该 Verticle 卸载时,租约会自动释放
    pool = DB2Builder.pool()
      .with(new PoolOptions()
        .setMaxSize(maxSize)
        .setShared(true)
        .setName("my-pool"))
      .connectingTo(database)
      .using(vertx)
      .build();
    return super.start();
  }
}, new DeploymentOptions().setInstances(4));

首次创建共享连接池时,系统会为该连接池分配底层资源。随后的调用将直接复用该池并创建新的租约。当所有租约均被关闭后,底层系统资源才会被完全释放。

默认情况下,当连接池需要创建 TCP 物理连接时,会复用当前的 EventLoop。因此共享连接池会随机使用正在使用它的各 Verticle 的 EventLoop。

你也可以独立于调用上下文,为连接池显式分配指定数量的 EventLoop:

Pool pool = DB2Builder.pool()
  .with(new PoolOptions()
    .setMaxSize(maxSize)
    .setShared(true)
    .setName("my-pool")
    .setEventLoopSize(4))
  .connectingTo(database)
  .using(vertx)
  .build();

配置(Configuration)

客户端为你提供了多种配置方式。

数据对象(Data Object)

配置客户端的一种简单方式是指定 DB2ConnectOptions 数据对象。

DB2ConnectOptions connectOptions = new DB2ConnectOptions()
  .setPort(50000)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret");

// 连接池选项
PoolOptions poolOptions = new PoolOptions().setMaxSize(5);

// 基于数据对象创建连接池
Pool pool = DB2Builder.pool()
  .with(poolOptions)
  .connectingTo(connectOptions)
  .using(vertx)
  .build();

pool.getConnection()
  .onComplete(ar -> {
    // 处理你的数据库连接
  });

你也可以通过 setProperties 或 addProperty 方法配置通用属性。请注意,setProperties 会覆盖客户端的默认属性。

连接 URI(Connection URI)

除了使用 DB2ConnectOptions 数据对象进行配置外,如果你想通过连接 URI 进行配置,我们也为你提供了另一种连接方式:

String connectionUri = "db2://dbuser:secret@the-host:50000/mydb";

// 基于连接 URI 创建连接池
Pool pool = DB2Builder.pool()
  .connectingTo(connectionUri)
  .using(vertx)
  .build();

// 基于连接 URI 创建连接
DB2Connection.connect(vertx, connectionUri)
  .onComplete(res -> {
    // 处理你的数据库连接
  });

连接字符串的 URI 格式为:

db2://[user[:[password]]@]host[:port][/database][?<key1>=<value1>[&<key2>=<value2>]]
在连接 URI 中配置的参数将会覆盖默认属性。

目前,客户端支持以下参数键:

  • host

  • port

  • user

  • password

  • database

其他额外参数将被添加至 properties 中。

连接 URI 解析器会将所有属性键转换为小写。

连接重试(Connect retries)

你可以配置客户端在建立连接失败时自动进行重试。

options
  .setReconnectAttempts(2)
  .setReconnectInterval(1000);

执行查询

当你不需要事务或仅运行单次查询时,可以直接在连接池上执行查询;连接池会自动从池中借调一个连接来执行该查询并向你返回结果。

以下演示如何执行简单查询:

client
  .query("SELECT * FROM users WHERE id='andy'")
  .execute()
  .onComplete(ar -> {
  if (ar.succeeded()) {
    RowSet<Row> result = ar.result();
    System.out.println("Got " + result.size() + " rows ");
  } else {
    System.out.println("Failure: " + ar.cause().getMessage());
  }
});

预编译查询(Prepared Query)

你同样可以对预编译查询执行类似操作。

SQL 字符串中可以通过位置引用参数,使用数据库特定的占位符语法 `?`:

client
  .preparedQuery("SELECT * FROM users WHERE id=?")
  .execute(Tuple.of("andy"))
  .onComplete(ar -> {
  if (ar.succeeded()) {
    RowSet<Row> rows = ar.result();
    System.out.println("Got " + rows.size() + " rows ");
  } else {
    System.out.println("Failure: " + ar.cause().getMessage());
  }
});

查询方法返回异步的 RowSet 实例,适用于各类 SELECT 查询:

client
  .preparedQuery("SELECT first_name, last_name FROM users")
  .execute()
  .onComplete(ar -> {
  if (ar.succeeded()) {
    RowSet<Row> rows = ar.result();
    for (Row row : rows) {
      System.out.println("User " + row.getString(0) + " " + row.getString(1));
    }
  } else {
    System.out.println("Failure: " + ar.cause().getMessage());
  }
});

对于数据变更操作,同样支持获取受影响的行数:

client
  .preparedQuery("INSERT INTO users (first_name, last_name) VALUES (?, ?)")
  .execute(Tuple.of("Andy", "Guibert"))
  .onComplete(ar -> {
  if (ar.succeeded()) {
    RowSet<Row> rows = ar.result();
    System.out.println(rows.rowCount());
  } else {
    System.out.println("Failure: " + ar.cause().getMessage());
  }
});

Row 允许通过索引序号或列名访问数据字段:

System.out.println("User " + row.getString(0) + " " + row.getString(1));
System.out.println("User " + row.getString("first_name") + " " + row.getString("last_name"));

客户端不会对参数类型进行任何魔法猜测,所有的参数都应当严格遵守 SQL 类型系统。

你可以执行批处理预编译查询:

List<Tuple> batch = new ArrayList<>();
batch.add(Tuple.of("julien", "Julien Viet"));
batch.add(Tuple.of("emad", "Emad Alblueshi"));
batch.add(Tuple.of("andy", "Andy Guibert"));

// 执行批处理
client
  .preparedQuery("INSERT INTO USERS (id, name) VALUES (?, ?)")
  .executeBatch(batch)
  .onComplete(res -> {
  if (res.succeeded()) {

    // 获取所有行结果
    RowSet<Row> rows = res.result();
  } else {
    System.out.println("Batch failed " + res.cause());
  }
});

你可以通过将查询包装在 SELECT <COLUMNS> FROM FINAL TABLE ( <SQL> ) 中来获取自动生成的主键,例如:

client
  .preparedQuery("SELECT color_id FROM FINAL TABLE ( INSERT INTO color (color_name) VALUES (?), (?), (?) )")
  .execute(Tuple.of("white", "red", "blue"))
  .onComplete(ar -> {
    if (ar.succeeded()) {
      RowSet<Row> rows = ar.result();
      System.out.println("Inserted " + rows.rowCount() + " new rows.");
      for (Row row : rows) {
        System.out.println("generated key: " + row.getInteger("color_id"));
      }
    } else {
      System.out.println("Failure: " + ar.cause().getMessage());
    }
  });

使用独立连接

获取数据库连接

当你需要执行一系列顺序查询(不包含事务)时,可以创建新连接或直接从连接池借取连接。请牢记,在从池中获取连接到归还连接期间,你需要妥善维护该连接,因为它可能因服务器端空闲超时等原因被意外关闭:

pool
  .getConnection()
  .compose(connection ->
    connection
      .preparedQuery("INSERT INTO Users (first_name,last_name) VALUES (?, ?)")
      .executeBatch(Arrays.asList(
        Tuple.of("Julien", "Viet"),
        Tuple.of("Emad", "Alblueshi")
      ))
      .compose(res -> connection
        // 处理返回的行数据
        .query("SELECT COUNT(*) FROM Users")
        .execute()
        .map(rows -> rows.iterator().next().getInteger(0)))
      // 将连接安全归还给连接池
      .eventually(() -> connection.close())
  ).onSuccess(count -> {
  System.out.println("Insert users, now the number of users is " + count);
});

同样可以创建并使用预编译查询(Prepared Query):

connection
  .prepare("SELECT * FROM users WHERE first_name LIKE ?")
  .compose(pq ->
    pq.query()
      .execute(Tuple.of("Julien"))
      .eventually(() -> pq.close())
  ).onSuccess(rows -> {
  // 获取所有行数据
});

简化的连接管理 API

在使用连接池时,可以调用 withConnection 方法,传入一个在连接作用域内执行的函数。

该方法会自动从连接池借出连接,并将该连接作为参数调用你的函数。

传入的函数必须返回一个包含任意结果的 Future。

在该 Future 完成后,连接会被自动安全归还给连接池,并对外返回最终的处理结果:

pool.withConnection(connection ->
  connection
    .preparedQuery("INSERT INTO Users (first_name,last_name) VALUES (?, ?)")
    .executeBatch(Arrays.asList(
      Tuple.of("Julien", "Viet"),
      Tuple.of("Emad", "Alblueshi")
    ))
    .compose(res -> connection
      // 处理查询返回的行数据
      .query("SELECT COUNT(*) FROM Users")
      .execute()
      .map(rows -> rows.iterator().next().getInteger(0)))
).onSuccess(count -> {
  System.out.println("Insert users, now the number of users is " + count);
});

使用事务

基于连接的事务管理

你可以使用 SQL BEGIN/COMMIT/ROLLBACK 语句手动执行事务;若这么做,你必须获取并自行管理 SqlConnection 连接。

或者,推荐使用 SqlConnection 提供的事务 API:

pool.getConnection()
  // 事务必须在独占的物理连接中运行
  .onSuccess(conn -> {
    // 开启事务
    conn.begin()
      .compose(tx -> conn
        // 执行一系列操作
        .query("INSERT INTO Users (first_name,last_name) VALUES ('Julien','Viet')")
        .execute()
        .compose(res2 -> conn
          .query("INSERT INTO Users (first_name,last_name) VALUES ('Emad','Alblueshi')")
          .execute())
        // 提交事务
        .compose(res3 -> tx.commit()))
      // 无论成功还是失败,均将连接归还至连接池
      .eventually(() -> conn.close())
      .onSuccess(v -> System.out.println("Transaction succeeded"))
      .onFailure(err -> System.out.println("Transaction failed: " + err.getMessage()));
  });

当数据库服务端判定当前事务失败时(例如出现著名的 current transaction is aborted, commands ignored until end of transaction block),事务会自动回滚,并且 completion Future 会以 TransactionRollbackException 异常宣告失败:

tx.completion()
  .onFailure(err -> {
    System.out.println("Transaction failed => rolled back");
  });

简化的事务管理 API

在使用连接池时,你可以调用 withTransaction,传入一个在事务内执行的函数。

该方法会自动从连接池借取连接、开启事务,并调用你的业务函数,由客户端在该事务的上下文中执行所有数据库操作。

该业务函数必须返回包含任意操作结果的 Future:

  • 当 Future 成功时,客户端会自动提交该事务;

  • 当 Future 失败时,客户端会自动回滚该事务。

事务执行完毕(无论提交或回滚)后,连接都会自动归还给连接池,并向调用方返回整体操作的最终结果:

pool.withTransaction(client -> client
  .query("INSERT INTO Users (first_name,last_name) VALUES ('Julien','Viet')")
  .execute()
  .flatMap(res -> client
    .query("INSERT INTO Users (first_name,last_name) VALUES ('Emad','Alblueshi')")
    .execute()
    // 转换为业务提示消息
    .map("Users inserted")))
  .onSuccess(v -> System.out.println("Transaction succeeded"))
  .onFailure(err -> System.out.println("Transaction failed: " + err.getMessage()));

游标(Cursor)与流式查询

默认情况下,执行预编译查询会一次性抓取所有数据行。你可以使用 Cursor 来精准控制每次读取的数据行数:

connection
  .prepare("SELECT * FROM users WHERE first_name LIKE ?")
  .onComplete(ar0 -> {
  if (ar0.succeeded()) {
    PreparedStatement pq = ar0.result();

    // 游标要求在事务内部运行
    connection
      .begin()
      .onComplete(ar1 -> {
      if (ar1.succeeded()) {
        Transaction tx = ar1.result();

        // 创建游标
        Cursor cursor = pq.cursor(Tuple.of("julien"));

        // 读取 50 行
        cursor.read(50).onComplete(ar2 -> {
          if (ar2.succeeded()) {
            RowSet<Row> rows = ar2.result();

            // 检查是否还有剩余数据行?
            if (cursor.hasMore()) {
              // 重复读取流程...
            } else {
              // 全部数据行读取完毕 - 提交事务
              tx.commit();
            }
          }
        });
      }
    });
  }
});

当游标需要提前释放时,必须显式调用关闭方法:

cursor
  .read(50)
  .onComplete(ar2 -> {
  if (ar2.succeeded()) {
    // 关闭游标
    cursor.close();
  }
});

游标还提供了更方便的流式 API,尤其是在搭配 RxJava 版本时极为顺畅:

connection
  .prepare("SELECT * FROM users WHERE first_name LIKE ?")
  .onComplete(ar0 -> {
  if (ar0.succeeded()) {
    PreparedStatement ps = ar0.result();

    // 数据流要求在事务内部运行
    connection
      .begin()
      .onComplete(ar1 -> {
      if (ar1.succeeded()) {
        Transaction tx = ar1.result();

        // 每次抓取 50 行
        RowStream<Row> stream = ps.createStream(50, Tuple.of("julien"));

        // 使用数据流
        stream.exceptionHandler(err -> {
          System.out.println("Error: " + err.getMessage());
          ps.close();
        });
        stream.endHandler(v -> {
          // 关闭流以释放数据库中的游标资源
          stream
            .close()
            .onComplete(closed -> {
            tx.commit()
              .onComplete(committed -> {
                System.out.println("End of stream");
                ps.close();
            });
          });
        });
        stream.handler(row -> {
          System.out.println("User: " + row.getString("last_name"));
        });
      }
    });
  }
});

数据流会以每次 50 行的批次读取并推送;当该批数据行全部传递给处理器后,再读取新的 50 行批次,以此类推。

该流支持随时暂停(Pause)或恢复(Resume);已加载的行将缓存在内存中直到被消费,在此期间游标会暂停迭代抓取。

查询链路追踪(Tracing queries)

当 Vert.x 启用了链路追踪(Tracing)时,SQL 客户端可以对查询执行过程进行全链路追踪。

客户端会上报以下 客户端(Client) Span:

  • 操作名称为 QueryMessage

  • 包含以下 Tags:

  • db.system:数据库管理系统产品名称

  • db.user:数据库用户名

  • db.instance:数据库实例

  • db.statement:SQL 查询语句

  • db.type:固定为 sql

默认的链路追踪策略为 PROPAGATE,客户端仅在参与现有活跃链路时才会创建 Span。

你可以通过 setTracingPolicy 更改客户端追踪策略,例如设置为 ALWAYS 以始终上报 Span:

options.setTracingPolicy(TracingPolicy.ALWAYS);

DB2 数据类型映射(DB2 type mapping)

目前,客户端支持以下 DB2 类型:

DB2 类型 Java 类型 说明

BOOLEAN

java.lang.Boolean

仅支持 DB2 LUW

SMALLINT

java.lang.Short

INTEGER

java.lang.Integer

BIGINT

java.lang.Long

REAL

java.lang.Float

DOUBLE

java.lang.Double

DECIMAL

io.vertx.sqlclient.data.Numeric

CHAR

java.lang.String

VARCHAR

java.lang.String

ENUM

java.lang.String

DATE

java.time.LocalDate

TIME

java.time.LocalTime

TIMESTAMP

java.time.LocalDateTime

BINARY

byte[]

VARBINARY

byte[]

ROWID

io.vertx.db2client.impl.drda.DB2RowId or java.sql.RowId

仅支持 DB2 z/OS

暂不支持的类型(Unsupported types)

以下类型当前暂不支持:

类型 说明

XML

尚未实现

BLOB

尚未实现

CLOB

尚未实现

DBCLOB

尚未实现

GRAPHIC / VARGRAPHIC

尚未实现

有关 DB2 数据类型的更多文档,请参阅:

Tuple 在解码并存储数值时会采用上述类型。在可能的情况下,它还会对实际值执行动态类型转换:

pool
  .query("SELECT an_int_column FROM exampleTable")
  .execute().onSuccess(rowSet -> {
    Row row = rowSet.iterator().next();

    // 存储为 INTEGER 列类型,并表示为 java.lang.Integer
    Object value = row.getValue(0);

    // 转换为 java.lang.Long
    Long longValue = row.getLong(0);
  });

使用 Java 枚举类型(Using Java enum types)

你可以将 Java 枚举类型 映射到以下数据库列类型:

  • 字符串(VARCHAR、TEXT)

  • 数值(SMALLINT、INTEGER、BIGINT)

client.preparedQuery("SELECT day_name FROM FINAL TABLE ( INSERT INTO days (day_name) VALUES (?), (?), (?) )")
  .execute(Tuple.of(Days.FRIDAY, Days.SATURDAY, Days.SUNDAY))
  .onComplete(ar -> {
    if (ar.succeeded()) {
      RowSet<Row> rows = ar.result();
      System.out.println("Inserted " + rows.rowCount() + " new rows");
      for (Row row : rows) {
        System.out.println("Day: " + row.get(Days.class, "day_name"));
      }
    } else {
      System.out.println("Failure: " + ar.cause().getMessage());
    }
  });
client.preparedQuery("SELECT day_num FROM FINAL TABLE ( INSERT INTO days (day_num) VALUES (?), (?), (?) )")
  .execute(Tuple.of(Days.FRIDAY.ordinal(), Days.SATURDAY.ordinal(), Days.SUNDAY.ordinal()))
  .onComplete(ar -> {
    if (ar.succeeded()) {
      RowSet<Row> rows = ar.result();
      System.out.println("Inserted " + rows.rowCount() + " new rows");
      for (Row row : rows) {
        System.out.println("Day: " + row.get(Days.class, "day_num"));
      }
    } else {
      System.out.println("Failure: " + ar.cause().getMessage());
    }
  });

字符串类型会与 Java 枚举通过 name() 方法返回的名称进行匹配。

数值类型会与 Java 枚举通过 ordinal() 方法返回的序号进行匹配,且 row.get() 方法会根据所检索到的整数序号位置返回对应枚举的 name() 值。

使用收集器查询(Collector queries)

你可以在查询 API 中使用 Java Collector 收集器:

Collector<Row, ?, Map<Long, String>> collector = Collectors.toMap(
  row -> row.getLong("id"),
  row -> row.getString("last_name"));

// 结合收集器执行查询
client.query("SELECT * FROM users")
  .collecting(collector)
  .execute()
  .onComplete(ar -> {
    if (ar.succeeded()) {
      SqlResult<Map<Long, String>> result = ar.result();

      // 获取收集器生成的 Map
      Map<Long, String> map = result.value();
      System.out.println("Got " + map);
    } else {
      System.out.println("Failure: " + ar.cause().getMessage());
    }
  });

收集器在处理过程中绝不能保留对 Row 对象的引用,因为在处理整个结果集时内部复用的是单一行实例。

Java Collectors 提供了许多开箱即用的预定义收集器,例如你可以轻松地直接从行结果集中构建字符串:

Collector<Row, ?, String> collector = Collectors.mapping(
  row -> row.getString("last_name"),
  Collectors.joining(",", "(", ")")
);

// 结合收集器执行查询
client
  .query("SELECT * FROM users")
  .collecting(collector)
  .execute()
  .onComplete(ar -> {
    if (ar.succeeded()) {
      SqlResult<String> result = ar.result();

      // 获取收集器生成的字符串
      String list = result.value();
      System.out.println("Got " + list);
    } else {
      System.out.println("Failure: " + ar.cause().getMessage());
    }
  });

使用 SSL/TLS

若要配置客户端使用 SSL 连接,你可以像配置 Vert.x NetClient 一样配置 DB2ConnectOptions。

DB2ConnectOptions options = new DB2ConnectOptions()
  .setPort(50001)
  .setHost("the-host")
  .setDatabase("the-db")
  .setUser("user")
  .setPassword("secret")
  .setSsl(true)
  .setSslOptions(new ClientSSLOptions().setTrustOptions(new JksOptions()
    .setPath("/path/to/keystore.p12")
    .setPassword("keystoreSecret")));

DB2Connection.connect(vertx, options)
  .onComplete(res -> {
    if (res.succeeded()) {
      // 已通过 SSL 成功连接
    } else {
      System.out.println("Could not connect " + res.cause());
    }
  });

更多信息请参阅 Vert.x 官方文档。

使用代理(Using a proxy)

你还可以配置客户端使用 HTTP/1.x CONNECT、SOCKS4a 或 SOCKS5 代理。

更多信息请参阅 Vert.x 官方文档。

连接池高级配置(Advanced pool configuration)

服务器负载均衡

你可以为连接池配置多个服务器地址列表以实现负载均衡,而不是仅指定单一服务器:

Pool pool = DB2Builder.pool()
  .with(options)
  .connectingTo(Arrays.asList(server1, server2, server3))
  .using(vertx)
  .build();

连接池在创建底层物理连接时采用轮询(Round-Robin)算法在不同服务器之间进行分配。

该负载均衡策略发生在连接创建阶段,而非从池中借取(Borrow)连接的阶段。

连接池初始化处理器

你可以使用 withConnectHandler 在物理连接创建完成后、放入连接池之前与其进行交互并执行初始化设置:

builder.withConnectHandler(conn -> {
  conn.query(sql).execute().onSuccess(res -> {
    // 将连接归还给池,准备供应用程序正常借用
    conn.close();
  });
});

一旦你完成对该连接的初始化操作,只需直接调用 close() 将其标记为就绪,以便连接池开始调度使用。

动态连接配置

你可以使用 Java Supplier 函数式接口动态提供连接配置详情,而非传入静态的 SqlConnectOptions 实例。

由于该 Supplier 是异步的,因此可用于实现连接配置的动态变更(例如数据库密码自动轮换):

Pool pool = DB2Builder.pool()
  .with(poolOptions)
  .connectingTo(() -> {
    Future<SqlConnectOptions> connectOptions = retrieveOptions();
    return connectOptions;
  })
  .using(vertx)
  .build();