
MyBatis Dynamic SQL 是一种类型安全的 Java 领域特定语言(DSL),用于通过编程方式构建 SQL 查询,而非编写 SQL 字符串或基于 XML 的动态查询。它在运行时使用流畅的 Java 构建器生成 SQL,同时仍通过标准的 MyBatis 映射器执行。与手动拼接字符串或复杂的 XML 逻辑相比,这使得查询构建更安全、更易于重构,并且更不容易出错。
由于查询是用 Java 编写的,列名和表引用通过强类型的元数据类在编译时进行验证,这提供了更好的 IDE 支持并减少了运行时 SQL 错误。本文将解释 MyBatis Dynamic SQL,并展示如何在 Java 应用程序中使用它。
1. 使用 MyBatis Dynamic SQL 可以做什么?
MyBatis Dynamic SQL 支持大多数常见的 SQL 操作,包括 SELECT、INSERT、UPDATE 和 DELETE,以及连接、子查询、分页、排序、条件过滤和批量操作。它允许我们逐步构建查询,仅在某些参数存在时添加条件,这使其成为搜索界面和过滤 API 的理想选择。
它直接与 MyBatis 映射器接口集成,意味着我们仍然可以受益于结果映射、事务处理和连接管理。由于它生成标准的 SQL,因此适用于 MyBatis 支持的任何数据库,没有供应商锁定的问题。
1.1 MyBatis Dynamic SQL 的工作原理
Dynamic SQL 基于两个组件:表元数据类和 DSL 构建器。元数据类用 Java 描述表和列。DSL 构建器使用这些类以流畅、类型安全的方式组装 SQL 语句。
DSL 不直接执行 SQL。相反,它生成语句提供者对象,例如 SelectStatementProvider 或 InsertStatementProvider。这些对象被传递给使用 @SelectProvider、@InsertProvider 及类似注解标注的映射器方法,然后 MyBatis 使用其正常的执行引擎来执行这些语句。
2. 项目设置与依赖
Maven 依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<dependency>
<groupId>org.mybatis.dynamic-sql</groupId>
<artifactId>mybatis-dynamic-sql</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.4.240</version>
<scope>runtime</scope>
</dependency>
这些依赖项包括 MyBatis 本身、Dynamic SQL DSL 以及一个用于测试的嵌入式数据库。
注意
数据库驱动可以替换为 MySQL、PostgreSQL 或任何其他支持的数据库。MyBatis Dynamic SQL 不依赖于数据库类型,仅依赖于标准 SQL 生成。
数据库模式
schema.sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT
);
INSERT INTO users(username, email, age) VALUES
('thomas', '[email protected]', 30),
('benjamin', '[email protected]', 22),
('charles', '[email protected]', 17);
此脚本创建一个简单的表并插入测试数据。内存中的 H2 数据库将在启动时执行此脚本,因此无需外部依赖即可测试查询。
领域模型
public class User {
private Long id;
private String username;
private String email;
private Integer age;
// Getter 和 Setter 方法...
}
这个 POJO 代表数据库中的一行。MyBatis 会自动使用匹配的字段名将列映射到字段,因此本例不需要额外的结果映射。
3. 用于 Dynamic SQL 的表元数据
下面的类以类型安全的方式定义数据库表及其列,允许在构建查询时被 Dynamic SQL DSL 引用。它充当 Java 代码与实际数据库结构之间的桥梁,实现了列名和类型的编译时验证。
public final class UserDynamicSqlSupport {
public static final User user = new User();
public static final SqlColumn<Long> id = user.id;
public static final SqlColumn<String> username = user.username;
public static final SqlColumn<String> email = user.email;
public static final SqlColumn<Integer> age = user.age;
public static final class User extends SqlTable {
public final SqlColumn<Long> id = column("id", JDBCType.BIGINT);
public final SqlColumn<String> username = column("username", JDBCType.VARCHAR);
public final SqlColumn<String> email = column("email", JDBCType.VARCHAR);
public final SqlColumn<Integer> age = column("age", JDBCType.INTEGER);
public User() {
super("users");
}
}
}
这个类为 users 表定义了类型安全的元数据,使 MyBatis Dynamic SQL 可以在不使用原始 SQL 字符串的情况下构建查询。DSL 使用这些 Java 对象而非按名称引用列,从而提高了安全性和 IDE 支持。
内部类 User 继承 SqlTable,这将其标记为可用于 from(user) 和连接等子句的数据表。构造函数调用 super("users") 来告知 MyBatis 要在 SQL 语句(如 FROM users)中呈现的确切表名。
每个列都使用 SqlTable 中的 column() 方法定义,该方法注册列名及其 JDBC 类型。这会产生强类型的 SqlColumn<T> 对象,确保比较和条件在编译时使用正确的 Java 类型。
外部类公开了对表及其列的静态引用,以便于静态导入,使得查询读起来很自然,例如:select(id, username).from(user),同时保持完全的类型安全和重构友好。
映射器接口
@Mapper
public interface UserMapper {
@SelectProvider(type = SqlProviderAdapter.class, method = "select")
List<User> selectMany(SelectStatementProvider selectStatement);
}
@Mapper 注解告诉 MyBatis 此接口应注册为映射器并在运行时进行代理。MyBatis 会自动生成实现,因此不需要具体的类。
selectMany 方法接受一个 SelectStatementProvider,它封装了完全呈现的 SQL 语句及其参数。MyBatis 执行该语句并将每个结果行映射到 User 对象,将它们作为 List<User> 返回。
@SelectProvider 注解指定 SQL 将由 MyBatis Dynamic SQL 的一部分 SqlProviderAdapter 动态提供。实际的 SQL 是在运行时从使用 DSL 构建的 SelectStatementProvider 生成的,而不是在注解或 XML 中编写 SQL。
4. 构建动态查询
在这里,我们使用流畅的 Dynamic SQL DSL 构建 SQL 语句,而不是编写原始 SQL 字符串。
public static void main(String[] args) throws Exception {
MyBatisUtil.runSchema();
try (SqlSession session = MyBatisUtil.getSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
SelectStatementProvider select
= select(id, username, email, age)
.from(user)
.where(age, isGreaterThan(18))
.and(username, isLike("%tho%"))
.orderBy(username)
.build()
.render(RenderingStrategies.MYBATIS3);
List<User> users = mapper.selectMany(select);
users.forEach(u
-> System.out.println(u.getUsername() + " - " + u.getAge()));
}
}
此代码使用流畅的 Dynamic SQL DSL 动态构建一个 SELECT 查询,并将其渲染为与 MyBatis 兼容的语句提供者。通过以编程方式添加条件,它能够以类型安全且可维护的方式创建复杂的过滤器。在本例中,查询选择 age 大于 18 岁且 username 包含 "tho" 的用户,然后按用户名字母顺序对结果进行排序。
MyBatis 工具类
public class MyBatisUtil {
private static SqlSessionFactory factory;
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static SqlSession getSession() {
return factory.openSession(true);
}
public static void runSchema() throws IOException, SQLException {
try (SqlSession session = getSession()) {
Connection conn = session.getConnection();
Statement stmt = conn.createStatement();
try (InputStream is = Resources.getResourceAsStream("schema.sql")) {
String sql = new String(is.readAllBytes(), StandardCharsets.UTF_8);
stmt.execute(sql);
}
}
}
}
此工具类加载 MyBatis 配置,构建 SqlSessionFactory,并提供对数据库会话的访问。它还通过执行 SQL 脚本(schema.sql)手动初始化数据库模式。
5. 使用 Dynamic SQL 进行插入、更新和删除
MyBatis 中的 Dynamic SQL 允许我们使用流畅的 DSL 以编程方式构造 INSERT、UPDATE 和 DELETE 语句。在此,我们演示如何执行这些常见的数据操作。
// INSERT
User newUser = new User();
newUser.setUsername("andrew");
newUser.setEmail("[email protected]");
newUser.setAge(28);
InsertStatementProvider<User> insert
= insert(newUser)
.into(user)
.map(username).toProperty("username")
.map(email).toProperty("email")
.map(age).toProperty("age")
.build()
.render(RenderingStrategies.MYBATIS3);
int inserted = mapper.insert(insert);
System.out.println("Rows inserted: " + inserted);
// UPDATE
UpdateStatementProvider update
= update(user)
.set(age).equalTo(35)
.where(username, isEqualTo("thomas"))
.build()
.render(RenderingStrategies.MYBATIS3);
int updated = mapper.update(update);
System.out.println("Rows updated: " + updated);
// DELETE
DeleteStatementProvider delete
= deleteFrom(user)
.where(age, isLessThan(18))
.build()
.render(RenderingStrategies.MYBATIS3);
int deleted = mapper.delete(delete);
System.out.println("Rows deleted: " + deleted);
相同的 DSL 风格也用于写操作。语句以流畅的方式构建、渲染,然后由映射器提供者方法执行。
- INSERT:创建一个新的
User对象并填充值。使用 Dynamic SQL DSL,我们将其字段映射到表列并生成InsertStatementProvider。映射器执行插入操作,返回受影响的行数。 - UPDATE:DSL 构建一个更新语句,将用户名为 "thomas" 的用户的年龄设置为 35。这确保只修改目标行,映射器执行更新。
- DELETE:删除语句移除所有年龄小于 18 岁的用户。在 DSL 中使用条件保证了类型安全并避免了字符串拼接。
更新后的映射器接口
为了支持这些操作,映射器接口必须包含用于 INSERT、UPDATE 和 DELETE 的方法,使用 MyBatis Dynamic SQL 提供者。
// INSERT
@InsertProvider(type = SqlProviderAdapter.class, method = "insert")
int insert(InsertStatementProvider<User> insertStatement);
// UPDATE
@UpdateProvider(type = SqlProviderAdapter.class, method = "update")
int update(UpdateStatementProvider updateStatement);
// DELETE
@DeleteProvider(type = SqlProviderAdapter.class, method = "delete")
int delete(DeleteStatementProvider deleteStatement);
映射器中的每个方法处理一个特定的 DML 操作(插入、更新或删除),并接受一个封装了生成的 SQL 及其参数的 InsertStatementProvider、UpdateStatementProvider 或 DeleteStatementProvider。这种方法允许所有写操作都在 Java 中以编程方式表达,而无需手动组合 SQL 字符串,同时仍能利用 MyBatis 高效地执行语句和映射结果。
6. 结论
在本文中,我们探讨了如何在 Java 应用程序中使用 MyBatis Dynamic SQL 来创建类型安全、可维护且可编程的 SQL 查询。通过将 SQL 构建与执行分离,MyBatis Dynamic SQL 简化了复杂查询逻辑的处理,降低了错误风险,并提高了代码可读性。这种方法非常适合查询需要动态变化或经常修改的应用程序。
7. 下载源代码
本文讨论了 MyBatis Dynamic SQL 及其在 Java 中的使用方法。
下载
您可以通过此处下载此示例的完整源代码:java mybatis dynamic sql





















