mybatis 通过 @selectprovider 等 provider 注解将动态 sql 构建委托给 java 方法,返回含 ? 占位符的 sql 字符串,由 mybatis 自动绑定参数并交由 preparedstatement 安全执行。

Java 中 PreparedStatement 本身不支持注解,也不能直接“结合注解生成动态 SQL”。它只是 JDBC 提供的预编译 SQL 执行接口,负责安全地设置参数、防止 SQL 注入。真正实现“用注解写 SQL + 动态拼接 + PreparedStatement 执行”的,是像 MyBatis 这样的持久层框架——它在底层封装了 PreparedStatement,同时提供 @Select、@Update 等注解,并通过 OGNL 或脚本引擎(如 XML 中的 <if></if>、<where></where>)处理动态逻辑。
MyBatis 注解方式写动态 SQL 的核心机制
MyBatis 的 @Select、@Update 等注解只支持静态 SQL 字符串;要写动态 SQL,必须借助 @SelectProvider、@UpdateProvider 等“Provider 注解”,把 SQL 构建逻辑交给 Java 方法:
- 注解本身不解析 if/foreach,而是调用你写的 Provider 类中的方法
- 该方法返回一个字符串(即最终要执行的 SQL),MyBatis 拿它创建
PreparedStatement并设参 - 参数对象会自动绑定到 SQL 中的
#{xxx}占位符,底层仍走PreparedStatement#setXXX()
用 @SelectProvider 写带条件的动态查询
例如根据用户名和状态动态查询用户:
@SelectProvider(type = UserSqlBuilder.class, method = "buildSelect")
List<user> selectUsers(@Param("name") String name, @Param("status") Integer status);
</user>
对应的 Provider 类:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
public class UserSqlBuilder {
public String buildSelect(Map<string object> params) {
StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
if (params.get("name") != null) {
sql.append(" AND name LIKE CONCAT('%', ?, '%')");
}
if (params.get("status") != null) {
sql.append(" AND status = ?");
}
return sql.toString();
}
}
</string>
⚠️ 注意:这里拼的是 SQL 字符串,? 占位符由 MyBatis 自动按参数顺序或 @Param 名称匹配,并交由 PreparedStatement 安全设置——你不用手动调用 setString()。
更推荐:混合使用注解 + XML 实现动态 SQL
纯注解写复杂动态 SQL(如多级嵌套、批量插入)可读性差、难维护。MyBatis 允许在注解中引用 XML 中定义的 SQL:
- 在 Mapper 接口方法上用
@Select("SELECT * FROM user ${filter}")不安全,应避免 - 正确做法是:用
@Select(value = {"<script>SELECT * FROM user <where><if test='name!=null'>name LIKE CONCAT('%',#{name},'%')</script>"})—— MyBatis 支持在注解里写内联脚本(需开启 scripting-enabled) - 但生产环境更建议把动态 SQL 放进 XML 文件,接口只用简单注解引用:
@SelectProvider(type = XmlSqlProvider.class, method = "getSql"),再由该方法读取 XML 片段
为什么不能自己用注解 + PreparedStatement 手动实现?
有人想自定义注解(如 @DynamicSQL),然后在运行时解析、拼 SQL、再用 Connection.prepareStatement() 执行——这理论上可行,但实际会重复造轮子:
- 你要处理占位符匹配、参数类型推断、空值跳过、集合遍历(
IN (?, ?, ?))、SQL 注入防护等 - 这些正是 MyBatis、JOOQ 等框架已稳定解决的问题
- 自行实现容易出 bug,且失去事务管理、缓存、插件扩展等生态能力
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










