Vertx 3 Data Access

  1. JDBC client
    1. Creating a the client
      1. Using default shared data source
        1. Specifying a data source name
        2. Creating a client with a non shared data source
        3. Specifying a data source
    2. Closing the client
    3. Getting a connection
    4. Configuration
      1. 因为我们使用了C3P0的连接池,因此我们还可以使用下列属性
  • Vert.x MySQL - PostgreSQL client
    1. Creating a the client
      1. Using default shared pool
      2. Specifying a pool name
      3. Creating a client with a non shared data source
  • Closing the client
  • Getting a connection
  • Configuration
  • Common SQL interface
  • The SQL Connection
    1. Auto-commit
    2. Executing queries
    3. Prepared statement queries
    4. Executing INSERT, UPDATE or DELETE
    5. Prepared statement updates
    6. Executing other operations
    7. Using transactions
    8. Closing connections

  • JDBC client

    这个客户端允许你在Vertx应用中使用异步API与JDBC数据库相交互.

    JDBCClient接口定义了异步方式的JDBC API

    Creating a the client

    下面的几种方式介绍了如何创建一个客户端:

    Using default shared data source

    在大多数情景中,你可能需要在不同的客户端实例(client instances)中共享同一个data source.

    例如,你想要通过部署多个verticle实例来拓展应用的规模,然而每个verticle都共享相同的datasource,这样你就避免了多个datasource pool了。

    如下:

    1
    JDBCClient client = JDBCClient.createShared(vertx, config);

    当第一次调用JDBCClient.createShared的时候,确实会根据传进的配置创建出data source. 但是当接下来再次调用这个方法的时候,也会创建一个新的客户端,但是它却没有创建新的data source,因此第二次传进的配置也没有生效.

    Specifying a data source name

    在创建JDBCClient实例的时候也可以指定data source的名字

    1
    JDBCClient client = JDBCClient.createShared(vertx, config, "MyDataSource");

    如果使用相同的Vertx实例和相同的data source名字创建出不同的客户端,那这些客户端会使用相同的data source.

    使用这种创建方式你可以在不同的客户端上使用不同的datasource, 例如和不同的数据库进行交互.

    Creating a client with a non shared data source

    虽然在大部分情况下不同的客户端实例需要共享相同的data source,但是有时候也可能客户端需要一个非共享的data source, 你可以使用JDBCClient.createNonShared方法.

    1
    JDBCClient client = JDBCClient.createNonShared(vertx, config);

    这种方式和调用JDBCClient.createShared时传递一个唯一的data source名字达到相同的效果.

    Specifying a data source

    如果你想使用一个已经存在的data source, 那么你也可以直接使用那个data source创建一个客户端.

    1
    JDBCClient client = JDBCClient.create(vertx, dataSource);

    Closing the client

    你可以在整个verticle生命周期之内都持有客户端的引用,但是一旦你使用完该客户端你就应该主动关闭它.

    由于data source内部持有一个引用计数器,每当客户端关闭一次,data source内部的技术器就会减1,当计数器为0的时候,data source就会关闭自己.

    Getting a connection

    当你成功创建出客户端之后,你可以使用getConnection方法来获得一个连接.

    当连接池中有了可用连接之后,handler会获得一个可用连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    client.getConnection(res -> {
    if (res.succeeded()) {

    SQLConnection connection = res.result();

    connection.query("SELECT * FROM some_table", res2 -> {
    if (res2.succeeded()) {

    ResultSet rs = res2.result();
    // Do something with results
    }
    });
    } else {
    // Failed to get connection - deal with it
    }
    });

    获得的连接是一个SQLConnection实例, SQLConnection接口更多的是被Vert.x sql service使用.

    Configuration

    当我们创建客户端时,向其传递了一个配置,该配置包含下面属性:

    • provider_class : 用于管理数据库连接的类名. 默认的类名是io.vertx.ext.jdbc.spi.impl.C3P0DataSourceProvider, 但是如果你想要使用其他连接池,那么你可以使用你自己的实现覆盖该属性.
    因为我们使用了C3P0的连接池,因此我们还可以使用下列属性
    • url : 数据库的JDBC连接URL地址
    • driver_class : JDBCdirver名称
    • user : 数据库名称
    • password : 数据库密码
    • max_pool_size : 连接池最大数量(默认是15)
    • initial_pool_size : 连接池初始大小(默认是3)
    • min_pool_size : 连接池最小值
    • max_statements : 缓存prepared statements的最大值(默认是0)
    • max_statements_per_connection : 每个连接缓存prepared statements的最大值(默认是0)
    • max_idle_time : 该值表示一个闲置连接多少秒后会被关闭(默认是0, 从不关闭).

    如果你还想要配置其他C3P0的配置,那么你可以在classpath上添加一个c3p0.properties文件

    下面给出了一个配置示例:

    1
    2
    3
    4
    5
    6
    JsonObject config = new JsonObject()
    .put("url", "jdbc:hsqldb:mem:test?shutdown=true")
    .put("driver_class", "org.hsqldb.jdbcDriver")
    .put("max_pool_size", 30);

    JDBCClient client = JDBCClient.createShared(vertx, config);

    Vert.x MySQL - PostgreSQL client

    MySQL / PostgreSQL客户端为Vert.x应用提供了一个与MySQL / PostgreSQL数据库交互的接口.

    它使用Mauricio Linhares开源驱动与MySQL / PostgreSQL数据库进行非阻塞交互.

    Creating a the client

    下面给出了几种创建方式:

    Using default shared pool

    在大多数情况下,我们需要在多个客户端实例中共享同一个连接池

    例如你通过部署多个verticle实例的方式进行程序拓展,但是可以每个verticle可以共享同一个连接池.

    1
    2
    3
    4
    5
    6
    7
    JsonObject mySQLClientConfig = new JsonObject().put("host", "mymysqldb.mycompany");
    AsyncSQLClient mySQLClient = MySQLClient.createShared(vertx, mySQLClientConfig);

    // To create a PostgreSQL client:

    JsonObject postgreSQLClientConfig = new JsonObject().put("host", "mypostgresqldb.mycompany");
    AsyncSQLClient postgreSQLClient = PostgreSQLClient.createShared(vertx, postgreSQLClientConfig);

    MySQLClient.createShared或者PostgreSQLClient.createShared会根据指定的配置创建一个连接池. 随后再调用这俩个方式时会使用同一个连接池,同时新的配置不会被采用.

    Specifying a pool name

    你也可以像下面这样指定一个连接池的名字.

    1
    2
    3
    4
    5
    6
    7
    JsonObject mySQLClientConfig = new JsonObject().put("host", "mymysqldb.mycompany");
    AsyncSQLClient mySQLClient = MySQLClient.createShared(vertx, mySQLClientConfig, "MySQLPool1");

    // To create a PostgreSQL client:

    JsonObject postgreSQLClientConfig = new JsonObject().put("host", "mypostgresqldb.mycompany");
    AsyncSQLClient postgreSQLClient = PostgreSQLClient.createShared(vertx, postgreSQLClientConfig, "PostgreSQLPool1");

    如果不同的客户端使用相同的Vertx实例和相同的连接池名字,那么他们将使用同一个连接池.

    使用这种创建方式你可以在不同的客户端上使用不同的datasource, 例如和不同的数据库进行交互.

    Creating a client with a non shared data source

    虽然在大部分情况下不同的客户端实例需要共享相同的data source,但是有时候也可能客户端需要一个非共享的data source, 你可以使用MySQLClient.createNonShared或者PostgreSQLClient.createNonShared方法.

    1
    2
    3
    4
    5
    6
    7
    JsonObject mySQLClientConfig = new JsonObject().put("host", "mymysqldb.mycompany");
    AsyncSQLClient mySQLClient = MySQLClient.createNonShared(vertx, mySQLClientConfig);

    // To create a PostgreSQL client:

    JsonObject postgreSQLClientConfig = new JsonObject().put("host", "mypostgresqldb.mycompany");
    AsyncSQLClient postgreSQLClient = PostgreSQLClient.createNonShared(vertx, postgreSQLClientConfig);

    这种方式和调用MySQLClient.createNonShared或者PostgreSQLClient.createNonShared时传递一个唯一的data source名字达到相同的效果.

    Closing the client

    你可以在整个verticle生命周期之内都持有客户端的引用,但是一旦你使用完该客户端你就应该调用close关闭它.

    Getting a connection

    当你成功创建出客户端之后,你可以使用getConnection方法来获得一个连接.

    当连接池中有了可用连接之后,handler会获得一个可用连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    client.getConnection(res -> {
    if (res.succeeded()) {

    SQLConnection connection = res.result();

    // Got a connection

    } else {
    // Failed to get connection - deal with it
    }
    });

    连接是SQLConnection的一个实例, SQLConnection是一个被Sql客户端使用的公共接口.

    需要注意的是datetimestamps类型. 无论何时从数据库中获取date时, 客户端会将它转换成ISO 8601形式的字符串(yyyy-MM-ddTHH:mm:ss.SSS).Mysql会忽略毫秒数.

    Configuration

    PostgreSqlMySql使用了下面相同的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "host" : <your-host>,
    "port" : <your-port>,
    "maxPoolSize" : <maximum-number-of-open-connections>,
    "username" : <your-username>,
    "password" : <your-password>,
    "database" : <name-of-your-database>
    }
    • host : 数据库主机地址(默认是localhost)
    • port : 数据库端口(PostgreSQL默认是5432. MySQL默认是3306)
    • maxPoolSize : 连接池保持开启的最大数量,默认是10.
    • username : 连接数据库使用的用户名.(PostgreSQL的默认值是postgres, MySQL的默认值是root)
    • password : 连接数据库使用的密码(默认没有设置密码).
    • database : 连接的数据库名称.(默认值是test)

    Common SQL interface

    通用SQL接口是用来和VertxSQL服务交互的.

    通过指定的SQL服务接口我们可以获取一个指定的连接.

    The SQL Connection

    我们使用SQLConnection表示与一个数据库的连接.

    Auto-commit

    当连接的auto commit被设置为true. 这意味着每个操作都会在连接自己的事务中被高效地执行.

    如果你想要在一个单独的事务中执行多个操作,你应该使用setAutoCommit方法将auto commit被设置为false.

    当操作完成之后,我们设置的handler会自动地被调用.

    1
    2
    3
    4
    5
    6
    7
    connection.setAutoCommit(false, res -> {
    if (res.succeeded()) {
    // OK!
    } else {
    // Failed!
    }
    });

    Executing queries

    我们使用query来执行查询操作

    query方法的参数是原生SQL语句, 我们不必使用针对不同的数据库使用不同的SQL方言.

    当查询完成之后,我们设置的handler会自动地被调用. query的结果使用ResultSet表示.

    1
    2
    3
    4
    5
    6
    7
    8
    connection.query("SELECT ID, FNAME, LNAME, SHOE_SIZE from PEOPLE", res -> {
    if (res.succeeded()) {
    // Get the result set
    ResultSet resultSet = res.result();
    } else {
    // Failed!
    }
    });

    ResultSet实例中的getColumnNames方法可以获得可用列名, getResults可以获得查询真实的结果.

    实际上,查询的结果是一个JsonArrayList实例,每一个元素都代表一行结果.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    List<String> columnNames = resultSet.getColumnNames();

    List<JsonArray> results = resultSet.getResults();

    for (JsonArray row: results) {

    String id = row.getString(0);
    String fName = row.getString(1);
    String lName = row.getString(2);
    int shoeSize = row.getInteger(3);

    }

    另外你还可以使用getRows获取一个Json对象实例的List, 这种方式简化了刚才的方式,但是有一点需要注意的是,SQL结果可能包含重复的列名, 如果你的情景是这种情况,你应该使用getResults.

    下面给出了一种使用getRows获取结果的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<JsonObject> rows = resultSet.getRows();

    for (JsonObject row: rows) {

    String id = row.getString("ID");
    String fName = row.getString("FNAME");
    String lName = row.getString("LNAME");
    int shoeSize = row.getInteger("SHOE_SIZE");

    }

    Prepared statement queries

    我们可以使用queryWithParams来执行prepared statement查询.

    下例中,演示了使用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    String query = "SELECT ID, FNAME, LNAME, SHOE_SIZE from PEOPLE WHERE LNAME=? AND SHOE_SIZE > ?";
    JsonArray params = new JsonArray().add("Fox").add(9);

    connection.queryWithParams(query, params, res -> {

    if (res.succeeded()) {
    // Get the result set
    ResultSet resultSet = res.result();
    } else {
    // Failed!
    }
    });

    Executing INSERT, UPDATE or DELETE

    我们可以直接使用update方法进行数据更新操作.

    同样update方法的参数同样是原生SQL语句,不必使用SQL方言.

    当更新完成之后,我们会获得一个更新结果UpdateResult

    我们可以调用更新结果的getUpdated方法获得有多少行发生了改变, 而且如果更新时生成了一些key,那么我们可以通过getKeys获得

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    List<String> columnNames = resultSet.getColumnNames();

    List<JsonArray> results = resultSet.getResults();

    for (JsonArray row: results) {

    String id = row.getString(0);
    String fName = row.getString(1);
    String lName = row.getString(2);
    int shoeSize = row.getInteger(3);

    }

    Prepared statement updates

    如果想要执行prepared statement更新操作,我们可以使用updateWithParams.

    如下例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    String update = "UPDATE PEOPLE SET SHOE_SIZE = 10 WHERE LNAME=?";
    JsonArray params = new JsonArray().add("Fox");

    connection.updateWithParams(update, params, res -> {

    if (res.succeeded()) {

    UpdateResult updateResult = res.result();

    System.out.println("No. of rows updated: " + updateResult.getUpdated());

    } else {

    // Failed!

    }
    });

    Executing other operations

    如果想要执行其他数据库操作,例如创建数据库,你可以使用execute方法.

    同样execute执行的语句也是原生SQL语句.当操作执行完之后,我们设置的handler会被调用.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    String sql = "CREATE TABLE PEOPLE (ID int generated by default as identity (start with 1 increment by 1) not null," +
    "FNAME varchar(255), LNAME varchar(255), SHOE_SIZE int);";

    connection.execute(sql, execute -> {
    if (execute.succeeded()) {
    System.out.println("Table created !");
    } else {
    // Failed!
    }
    });

    Using transactions

    如果想要使用事务,那么首先要调用setAutoCommitauto-commit设置为false.

    接下来你就可以进行事务操作, 例如提交时使用commit, 回滚时使用rollback.

    一旦commit/rollback完成之后, 我们设置的handler会被调用, 然后下一个事务会自动开始.

    1
    2
    3
    4
    5
    6
    7
    connection.commit(res -> {
    if (res.succeeded()) {
    // Committed OK!
    } else {
    // Failed!
    }
    });

    Closing connections

    当你执行完全部的操作之后,你应该使用close将连接资源还给连接池.