<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-sql-client-templates</artifactId>
<version>4.5.35</version>
</dependency>
SQL 客户端模板(SQL Client Templates)
SQL Client Templates 是一个小巧的工具库,专为简化 SQL 查询的构建与执行而设计。
引入依赖
要在项目中使用 SQL Client Templates,请在构建描述文件的 dependencies 配置节中添加以下依赖:
-
Maven(在
pom.xml中):
-
Gradle(在
build.gradle文件中):
dependencies {
implementation 'io.vertx:vertx-sql-client-templates:4.5.35'
}
快速入门
以下是使用 SQL 模板的最简单方式。
SQL 模板消费具名参数(Named Parameters),因此(默认情况下)接收一个 Map 作为参数源,而不是元组(Tuple)。
SQL 模板(默认情况下)像客户端的 PreparedQuery 一样产生 RowSet<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");
});
|
使用 为避免反复支付重新解析实际查询的开销,建议复用模板实例。通常,你可以将模板实例保存为 Verticle 的成员变量。 |
|
当你的模板必须在事务中执行时,可以使用
|
模板语法
模板采用 #{XXX} 语法,其中 XXX 是合法的 Java 标识符 字符串(不受 Java 关键字保留限制)。
你可以使用反斜杠 \ 转义 字符;例如 \{foo} 将被解释为普通字符串 #{foo},而不会被当作 foo 参数处理。
行映射(Row Mapping)
默认情况下,模板生成 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 行映射
贫血式 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());
});
});
参数映射(Parameters Mapping)
模板默认消费 Map<String, Object> 作为输入。
你可以提供自定义映射器:
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 参数映射
贫血式 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 Databind 进行映射
你可以利用 Jackson Databind 的强大功能来进行对象映射。
需要在构建描述文件的 dependencies 配置节中添加 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}'
}
行映射通过将结果行的键/值对构建为 JsonObject,随后调用 mapTo 将其通过 Jackson Databind 反序列化为任意 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 将 Java 对象转换为 JsonObject,再提取键/值对来生成模板参数:
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 的 jackson-modules-java8 扩展模块,你可以映射 java.time 类型。
需要在构建描述文件的 dependencies 配置节中添加 Jackson JSR 310 数据类型依赖:
-
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());
现在你可以正常使用诸如 LocalDateTime 等 java.time 类型:
public class LocalDateTimePojo {
public LocalDateTime localDateTime;
}
基于 Vert.x 数据对象(Data Object)进行映射
SQL Client Templates 组件能够为 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;
}
}
代码生成(Code Generation)
任何标注有 @RowMapped 或 @ParametersMapped 的数据对象,都会触发生成对应的 Mapper 类。
codegen 注解处理器在编译期自动生成这些类。 这是 Java 编译器的原生特性,因此无需额外步骤,只需在构建工具中正确配置即可。
在构建配置中添加 io.vertx:vertx-codegen:processor 与 io.vertx:vertx-sql-client-templates 依赖。
以下是 Maven 的配置示例:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<version>4.5.35</version>
<classifier>processor</classifier>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-sql-client-templates</artifactId>
<version>4.5.35</version>
</dependency>
在 Gradle 中同样可以使用该特性:
annotationProcessor "io.vertx:vertx-codegen:4.5.35:processor"
annotationProcessor "io.vertx:vertx-sql-client-templates:4.5.35"
compile "io.vertx:vertx-sql-client-templates:4.5.35"
compile "io.vertx:vertx-codegen:4.5.35"
各类常用 IDE 通常自带对注解处理器的支持。
带有 processor classifier 的 codegen JAR 包会通过 META-INF/services 服务发现机制自动启用服务代理注解处理器。
如果你希望配合普通 JAR 包使用,需要显式声明注解处理器,例如在 Maven 中:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
|
codegen package-info.java 文件
|
行映射代码生成
你可以通过在数据对象上标注 @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 枚举类型映射
在底层客户端支持的情况下(例如响应式 PostgreSQL 客户端),你可以直接映射 Java 枚举类型。
通常,Java 枚举类型会被映射为字符串、数值或数据库特定的自定义枚举类型。
命名格式规范
默认模板对参数和列使用相同的大小写命名。你可以在 @Column 和 @TemplateParameter 注解中覆盖默认命名,并使用你偏好的格式。
你还可以在 @RowMapped 和 @ParametersMapped 注解中配置映射器的特定大小写命名风格:
@DataObject
@RowMapped(formatter = SnakeCase.class)
@ParametersMapped(formatter = QualifiedCase.class)
class UserDataObject {
// ...
}
支持以下命名风格:
-
CamelCase:大驼峰,如FirstName -
LowerCamelCase:小驼峰,如firstName(这是默认风格) -
SnakeCase:蛇形命名,如first_name -
KebabCase:短横线连接,如first-name -
QualifiedCase:点号连接,如first.name