MyBatis Velocity中如何通过参数传递动态SQL实现查询
错误原因
你遇到的报错是MyBatis的模板解析逻辑执行顺序导致的:
- MyBatis对
@Select注解内的模板只会做一次性解析,解析完成后再处理参数替换,你传入的sql字段里的@{}、#if等模板标签不会被二次解析,会直接作为SQL内容发送到数据库,触发语法错误。 - 第一种写法
@Select("@{sql}")使用了预编译参数语法,MyBatis会把整个SQL字符串当成字符串参数,自动加上单引号,最终执行的SQL会变成?'你拼接的SQL字符串',完全不符合SQL语法。 - 第二种写法
@Select("$_parameter.sql")虽然会直接拼接字符串,但拼接后的内容里的模板标签没有被解析,数据库无法识别@{name}这类标签,所以报错。
正确实现方案
方案1:使用@SelectProvider实现动态SQL(最推荐)
这是MyBatis官方提供的动态SQL生成方案,完全可以覆盖你需要的自定义SQL逻辑,同时保证安全性和可维护性:
- 定义SQL生成类:
public class UserSqlProvider { public String generateFindSql(Parameter param) { StringBuilder sql = new StringBuilder("select * from users where 1=1"); // 替代原来的#if模板逻辑 if (param.getName() != null) { sql.append(" and users.name = #{name}"); } if (param.getId() != null) { sql.append(" and users.id = #{id}"); } // 可根据需求自由扩展其他SQL拼接逻辑 return sql.toString(); } }
- 调整Mapper写法:
@Mapper public interface UserMyBatisRepository { @SelectProvider(type = UserSqlProvider.class, method = "generateFindSql") List<User> find(Parameter p); }
方案2:传入预处理完成的纯SQL(仅适用于完全可控的场景)
如果你确实需要把SQL完整传入参数,必须先在业务层把所有模板逻辑、变量都替换完成,生成没有任何MyBatis模板标签的可执行SQL,再通过字符串替换语法传入:
- Mapper写法:
@Select("${sql}") List<User> find(Parameter p);
- 业务层使用示例:
// 业务层提前处理完所有逻辑,生成纯可执行SQL,不能包含@{}, #if等模板标签 String fullSql = "select * from users where users.name = '张三' and users.id = 1001"; Parameter param = new Parameter("张三", 1001L, fullSql); List<User> users = userMapper.find(param);
注意:该方案存在极高的SQL注入风险,只有当你完全能控制
sql字段的生成来源,没有任何用户输入参与的场景下才能使用。
方案3:自定义LanguageDriver实现二次解析(不推荐)
如果必须要传入带模板标签的SQL片段,可以自定义MyBatis的LanguageDriver,重写SQL解析逻辑,在参数替换完成后对传入的SQL片段做二次模板解析。该方案复杂度高,需要对MyBatis核心逻辑有较深的理解,非必要不使用。
内容的提问来源于stack exchange,提问作者IgorZ
相关产品推荐
相关产品推荐

