1.参考示例 2.${}和#{} 解析以及赋值对比分析 ????2.1 sql解析对比 ????2.2 sql赋值分析 3.关于${}和#{} 模糊查询方式梳理说明 ????源码视角搞清楚为什么#{}可以防止sql注入,直接进入主题!
1.参考示例
????示例方法:
List<News> findNews(String title);
????配置文件:
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news where title like '%${title1}%'
</select>
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like "%"#{title}"%"
</select>
????测试案例:
List<News> news = newsMapper.findNews("abc");
System.out.println(news);
2.
${}和#{} 解析以及赋值对比分析
mybatis配置文件解析赋值流程
GenericTokenParser.java中parse方法执行解析处理(mybatis配置文件加载,具体sql执行之前两者都会执行此方法,含有${}的sql执行赋值操作也会执行此方法).
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
builder.append(handler.handleToken(expression.toString()));
return builder.toString();
}
2.1 sql解析对比
????对于#{}处理,会将#{title}替换成?,对应源码:
SqlSourceBuilder.java中handleToken
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
????对于${}解析处理,最终只会进行参数拼接: PropertyParser.java中handleToken
public String handleToken(String content) {
if (variables != null) {
String key = content;
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}";
}
}
2.2 sql赋值对比
????${}赋值处理,获取值具体对应源码:
TextSqlNode.java
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = value == null ? "" : String.valueOf(value);
checkInjection(srtValue);
return srtValue;
}
获取值之后重新走GenericTokenParser.java中parse进行参数拼接.拼接后sql如下:
#{}赋值处理: ????按照 string类型进行赋值处理,i表示第几个参数,将?替换成对应的实参.具体对应源码: StringTypeHandler.java中setNonNullParameter
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
????需要注意的地方是对于传递的参数参数是字符串时,会将双引号替换为单引号. ClientPreparedQueryBindings.java执行上面具体的setString逻辑
public void setString(int parameterIndex, String x) {
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0:
buf.append('\\');
buf.append('0');
break;
case '\n':
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '\'':
buf.append('\'');
buf.append('\'');
break;
case '"':
if (this.session.getServerSession().useAnsiQuotedIdentifiers()) {
buf.append('\\');
}
buf.append('"');
break;
case '\032':
buf.append('\\');
buf.append('Z');
break;
case '\u00a5':
case '\u20a9':
if (this.charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
this.charsetEncoder.encode(cbuf, bbuf, true);
if (bbuf.get(0) == '\\') {
buf.append('\\');
}
}
buf.append(c);
break;
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(parameterAsString)
: (needsQuoted ? StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charEncoding)
: StringUtils.getBytes(parameterAsString, this.charEncoding));
setValue(parameterIndex, parameterAsBytes, MysqlType.VARCHAR);
}
}
赋值完成之后的sql如下 ????至此,${}和#{} sql解析以及参数赋值梳理完毕.后面查询以及结果集封装操作相同,在此不再展开.
3.关于
${}和#{} 模糊查询方式梳理说明
????${}模糊查询,无论单引还是双引,执行结果相同
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like "%${title1}%"
</select>
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like '%${title1}%'
</select>
????#{}模糊查询,下面两种执行正常
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like concat('%',#{title},'%')
</select>
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like "%"#{title}"%"
</select>
????如果单引的方式会由于解析问题导致查询不到指定内容(本例中查询结果为空),平常注意一下即可.
<select id="findNews" resultType="com.it.txm.demo.controller.News">
select id,title from find_news
where title like '%'#{title}'%'
</select>
????欢迎小伙伴评论区留言,共同探讨,相互学习!
|