作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
回答
#{}和${}是 MyBatis 两种不同的占位符,#{} 为预编译处理,而${} 是字符串替换。
| #{} | ${} | |
|---|---|---|
| 用途 | 传递参数,防止 SQL 注入 | 传递参数,不能防止 SQL 注入 |
| 实现机制 | MyBatis 将#{}中的内容替换为 ?,并在运行时通过PreparedStatement 的 set 方法设置参数值。 | MyBatis 将${}中的内容直接替换为参数值,即将参数直接拼接到 SQL 字符串中。 |
| 安全性 | 安全性高,可以有效防止 SQL 注入。 | 存在安全隐患,直接拼接 SQL 字符串容易导致 SQL 攻击。 |
使用场景
#{}用在动态 SQL 参数赋值,防止 SQL 注入。
@Select("SELECT * FROM t_user where id = #{id}")
User selectUserById(@Param("id") Long id);
// #{} 解析替换为 ? ===> SELECT * FROM t_user where id = ?
而${}用在需将参数直接拼接在 SQL 的场景(如动态表名、列名、排序)。如按月对订单表拆分后的查询场景
SELECT * FROM ${table_name} where create_time >= #{createTime}
// 当订单按月分表后,可根据日期拼接表名(如 order_202407)再查询数据。
扩展
#{}和 ${}如何被解析?
- SqlSource 接口及其实现
DynamicSqlSource:处理动态 SQL 语句(一是包含$占位符的表达式,二是包含9种动态标签中的任何一个)。
public BoundSql getBoundSql(Object parameterObject) {
// 动态上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 动态SQL标签处理,解析静态SQL
rootSqlNode.apply(context);
// sql源解析器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析为StaticSqlSource(生成预处理SQL)
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 参数token处理器(通过方法handleToken将#{}替换为?)
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 定义通用解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
// 动态 SQL 解析,替换 #{} 为 ?
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
RawSqlSource:不是DynamicSqlSource,便会被解析为 RawSqlSource。
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// sql源解析器
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 解析为StaticSqlSource(生成预处理SQL)
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
- PropertyParser:处理
${}的变量替换。 - Mybatis 将 XML 定义的 SQL 包装
XNode。解析时,Mybatis 调用了内部函数parseAttributes()和parseBody(),这两个方法内部调用了静态方法PropertyParser#parse来解析每一个 Node 节点属性。
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 遍历 Node 节点的属性
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
// 使用 PropertyParser 解析占位符值
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
public static String parse(String string, Properties variables) {
// 内部类实现 tokenHandler,支持 ${key:default} 形式的解析
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 使用 GenericTokenParser 来解析占位符属性
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
629

被折叠的 条评论
为什么被折叠?



