SQL 客户端模版

SQL 客户端模版是一个用来方便执行SQL查询的库。

用法

要使用 SQL 客户端模版,需添加如下依赖:

  • Maven(在您的 pom.xml 文件中):

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-sql-client-templates</artifactId>
 <version>4.3.8</version>
</dependency>
  • Gradle(在您的 build.gradle 文件中):

dependencies {
 implementation 'io.vertx:vertx-sql-client-templates:4.3.8'
}

开始

以下是 SQL 模版最简易的使用方式。

一个 SQL 模版接收 已命名的 参数,因此,默认情况下,它会接收一个map作为参数载体,而非接收元组(tuple)作为参数。

一个SQL 模版默认情况下生成一个类似 PreparedQueryRowSet<Row> 。 实际上这个模版是 PreparedQuery 的轻量级封装。

Map<String, Object> parameters = Collections.singletonMap("id", 1);

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .execute(parameters)
  .onSuccess(users -> {
    users.forEach(row -> {
      System.out.println(row.getString("first_name") + " " + row.getString("last_name"));
    });
  });

当您需要执行一个插入或更新操作,而您并不关心执行结果,您可以用 SqlTemplate.forUpdate

Map<String, Object> parameters = new HashMap<>();
parameters.put("id", 1);
parameters.put("firstName", "Dale");
parameters.put("lastName", "Cooper");

SqlTemplate
  .forUpdate(client, "INSERT INTO users VALUES (#{id},#{firstName},#{lastName})")
  .execute(parameters)
  .onSuccess(v -> {
    System.out.println("Successful update");
  });

模板语法

模板语法使用 #{XXX} 的语法,其中 {XXX} 是一个有效的 java identifier 字符串 (不受关键字约束)

您可以用反斜杠(\)来转义 字符,例如 \{foo} 会被解析成 #{foo} 字符串,而不是名为 foo 的参数。

行映射

默认情况下模版以 Row 作为结果值类型。

您可以提供一个自定义的 RowMapper 来实现底层的映射操作:

RowMapper<User> ROW_USER_MAPPER = row -> {
  User user = new User();
  user.id = row.getInteger("id");
  user.firstName = row.getString("firstName");
  user.lastName = row.getString("lastName");
  return user;
};

实现底层映射操作:

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .mapTo(ROW_USER_MAPPER)
  .execute(Collections.singletonMap("id", 1))
  .onSuccess(users -> {
    users.forEach(user -> {
      System.out.println(user.firstName + " " + user.lastName);
    });
  });

JSON行映射

(译者注:原文为 anemic json row mapping,即anemic mapping,指单纯的属性映射,无行为)

JSON 行映射是一个简单的模板映射,它用 toJson 将数据行映射成JSON对象。

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .mapTo(Row::toJson)
  .execute(Collections.singletonMap("id", 1))
  .onSuccess(users -> {
    users.forEach(user -> {
      System.out.println(user.encode());
    });
  });

参数映射

模板默认接收一个 Map<String, Object> 作为输入参数。

您可以提供一个自定义的映射(Mapper):

TupleMapper<User> PARAMETERS_USER_MAPPER = TupleMapper.mapper(user -> {
  Map<String, Object> parameters = new HashMap<>();
  parameters.put("id", user.id);
  parameters.put("firstName", user.firstName);
  parameters.put("lastName", user.lastName);
  return parameters;
});

实现参数映射:

User user = new User();
user.id = 1;
user.firstName = "Dale";
user.firstName = "Cooper";

SqlTemplate
  .forUpdate(client, "INSERT INTO users VALUES (#{id},#{firstName},#{lastName})")
  .mapFrom(PARAMETERS_USER_MAPPER)
  .execute(user)
  .onSuccess(res -> {
    System.out.println("User inserted");
  });

您也可以轻松搞定批处理:

SqlTemplate
  .forUpdate(client, "INSERT INTO users VALUES (#{id},#{firstName},#{lastName})")
  .mapFrom(PARAMETERS_USER_MAPPER)
  .executeBatch(users)
  .onSuccess(res -> {
    System.out.println("Users inserted");
  });

JSON 参数映射

(译者注:原文为 anemic json parameters mapping,即anemic mapping,指单纯的属性映射,无行为)

JSON 参数映射是一个在模板参数和JSON对象之间的简单映射:

JsonObject user = new JsonObject();
user.put("id", 1);
user.put("firstName", "Dale");
user.put("lastName", "Cooper");

SqlTemplate
  .forUpdate(client, "INSERT INTO users VALUES (#{id},#{firstName},#{lastName})")
  .mapFrom(TupleMapper.jsonObject())
  .execute(user)
  .onSuccess(res -> {
    System.out.println("User inserted");
  });

用Jackson的数据绑定功能做映射

您可以用Jackson的数据绑定功能来实现映射。

您需要添加 jackson-databind 依赖:

  • Maven (在您的 pom.xml 文件):

<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-databind</artifactId>
 <version>${jackson.version}</version>
</dependency>
  • Gradle (在您的 build.gradle 文件):

dependencies {
 compile 'com.fasterxml.jackson.core:jackson-databind:${jackson.version}'
}

行映射是通过用键值对(key/value pair)来创建 JsonObject 实现的,然后 调用 mapTo 来将它映射为任何Java类。

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .mapTo(User.class)
  .execute(Collections.singletonMap("id", 1))
  .onSuccess(users -> {
    users.forEach(user -> {
      System.out.println(user.firstName + " " + user.lastName);
    });
  });

相似的,参数映射是用 JsonObject.mapFrom 将对象映射为 JsonObject 而实现的, 而后用 key/value pairs 来生成模板参数。

User u = new User();
u.id = 1;

SqlTemplate
  .forUpdate(client, "INSERT INTO users VALUES (#{id},#{firstName},#{lastName})")
  .mapFrom(User.class)
  .execute(u)
  .onSuccess(res -> {
    System.out.println("User inserted");
  });

Java Date/Time API 映射

您可以用 jackson-modules-java8 的Jackson扩展包来实现对 java.time 的映射。

您需要加入 Jackson JSR 310 datatype 依赖:

  • Maven (在您的 pom.xml 文件):

<dependency>
 <groupId>com.fasterxml.jackson.datatype</groupId>
 <artifactId>jackson-datatype-jsr310</artifactId>
 <version>${jackson.version}</version>
</dependency>
  • Gradle (在您的 build.gradle 文件):

dependencies {
 compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jackson.version}'
}

然后您需要将时间模块注册到 Jackson的 ObjectMapper

ObjectMapper mapper = io.vertx.core.json.jackson.DatabindCodec.mapper();

mapper.registerModule(new JavaTimeModule());

您可以用 java.time 包中的类型,例如 LocalDateTime

public class LocalDateTimePojo {

 public LocalDateTime localDateTime;

}

用Vert.x数据对象做映射

SQL 客户端模版组件可为 Vert.x 数据对象生成映射方法。

Vert.x 数据对象是指被 @DataObject 注解修饰的普通 Java Bean。

@DataObject
class UserDataObject {

  private long id;
  private String firstName;
  private String lastName;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

代码生成

任何被 @RowMapped@ParametersMapped 注解修饰的数据对象将触发相关映射类的生成。

codegen 注解执行器在编译期间生成这些类。 这是Java编译器的特性,所以 不需要额外步骤 ,仅需要在构建时保证配置的正确性:

构建时添加 io.vertx:vertx-codegen:processorio.vertx:vertx-sql-client-templates 依赖:

以下是Maven配置示例

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-codegen</artifactId>
 <version>4.3.8</version>
 <classifier>processor</classifier>
</dependency>
<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-sql-client-templates</artifactId>
 <version>4.3.8</version>
</dependency>

Gradle中依然可以使用此特性:

annotationProcessor "io.vertx:vertx-codegen:4.3.8:processor"
compile "io.vertx:vertx-sql-client-templates:4.3.8"

IDE都会对 annotation processors 提供支持

codegen processor classifier 在jar中添加服务代理注解执行器的自动配置, 这是通过 META-INF/services 机制实现的。

如果需要,也可以用普通jar包,但是您需要额外声明注解执行器。 Maven 示例:

<plugin>
 <artifactId>maven-compiler-plugin</artifactId>
 <configuration>
   <annotationProcessors>
     <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
   </annotationProcessors>
 </configuration>
</plugin>

行映射

您可以通过在数据对象上声明 @RowMapped 注解的方式生成一个行映射器。

@DataObject
@RowMapped
class UserDataObject {

  private long id;
  private String firstName;
  private String lastName;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

默认情况下,每一列的名称与数据对象属性的名称相对应,例如 userName 属性会 绑定 userName 列。

通过 @Column 注解,您可以用自定义名称。

@DataObject
@RowMapped
class UserDataObject {

  private long id;
  @Column(name = "first_name")
  private String firstName;
  @Column(name = "last_name")
  private String lastName;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

您可以用注解修饰属性、getter、setter方法。

生成的映射器可以如 行映射章节 所示来做行映射。

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .mapTo(UserDataObjectRowMapper.INSTANCE)
  .execute(Collections.singletonMap("id", 1))
  .onSuccess(users -> {
    users.forEach(user -> {
      System.out.println(user.getFirstName() + " " + user.getLastName());
    });
  });

参数映射

您可以通过在数据对象上加 @ParametersMapped 注解的方式生成参数映射器

@DataObject
@ParametersMapped
class UserDataObject {

  private long id;
  private String firstName;
  private String lastName;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

默认情况下,每个参数的名称和数据对象的属性名称相对应,例如 userName 属性绑定到 userName 参数。

通过 @TemplateParameter 注解,您可以使用自定义参数名称。

@DataObject
@ParametersMapped
class UserDataObject {

  private long id;
  @TemplateParameter(name = "first_name")
  private String firstName;
  @TemplateParameter(name = "last_name")
  private String lastName;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

您可以用注解修饰属性、getter、setter方法。

生成的映射器可以用来做参数映射,正如 参数映射章节 所示

UserDataObject user = new UserDataObject().setId(1);

SqlTemplate
  .forQuery(client, "SELECT * FROM users WHERE id=#{id}")
  .mapFrom(UserDataObjectParamMapper.INSTANCE)
  .execute(user)
  .onSuccess(users -> {
    users.forEach(row -> {
      System.out.println(row.getString("firstName") + " " + row.getString("lastName"));
    });
  });

Java 枚举类型映射

如果客户端(例如 Reactive PostgreSQL client)提供了支持,那么您可以映射Java枚举类型。

Java枚举类经常被映射为 string/number/自定义数据库枚举类型。

命名格式

默认的模版下,参数和数据库列之间的映射不忽略大小写。您可以在 ColumnTemplateParameter 注解中用您喜欢的格式来重写默认名称。

您也可以在 RowMappedParametersMapped 注解中配置一个特定的大小写格式:

@DataObject
@RowMapped(formatter = SnakeCase.class)
@ParametersMapped(formatter = QualifiedCase.class)
class UserDataObject {
  // ...
}

Vert.x提供了下列大小写模式: