1: #{}和${}的区别
首先配置在UserMapper.xm中配置l两个sql:
<mapper namespace="com.demo.mybatis.UserMapper"> <select id="selectUser" resultType="com.demo.mybatis.domain.User"> select * from user where id = #{id} </select> <select id="selectUserById" timeout="0" resultType="User"> select * from user where id = ${id} </select> </mapper>
测试代码:
@Test public void dollarAndPound() { // select * from user where id = #{id} String selectUser = "com.demo.mybatis.UserMapper.selectUser"; // select * from user where id = ${id} String selectUserById = "com.demo.mybatis.UserMapper.selectUserById"; List users1 = sqlSessionTest.selectList(selectUser,"1 or 1=1 "); List users2 = sqlSessionTest.selectList(selectUserById,"1 or 1=1"); System.out.println(users1.size()); System.out.println(users2.size()); }
测试结果
22:45:13.624 [main] DEBUG com.demo.mybatis.UserMapper.selectUser - ==> Preparing: select * from user where id = ? 22:45:13.654 [main] DEBUG com.demo.mybatis.UserMapper.selectUser - ==> Parameters: 1 or 1=1 (String) 22:45:13.674 [main] DEBUG com.demo.mybatis.UserMapper.selectUser - <== Total: 1 after.... before.... 22:45:15.194 [main] DEBUG com.demo.mybatis.UserMapper.selectUserById - ==> Preparing: select * from user where id = 1 or 1=1 22:45:15.194 [main] DEBUG com.demo.mybatis.UserMapper.selectUserById - ==> Parameters: 22:45:15.194 [main] DEBUG com.demo.mybatis.UserMapper.selectUserById - <== Total: 6
执行结果截然不同,使用$的sql不能防止sql注入,这是为什么呢?
我们要知道mybatis在处理#和$的sql的语句是怎样的流程。
首先mybatis在解析UserMapper.xml的时候读取所有的sql语句节点,并解析,代码如下
XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
遍历所得Sql节点,XMLScriptBuilder对sqlNode执行解析生成sqlSource
public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
这里会判断是否是动态sql,isDynamic的条件如下:
TextSqlNode.java中
public boolean isDynamic() { DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser(); GenericTokenParser parser = createParser(checker); parser.parse(text); return checker.isDynamic(); }
private GenericTokenParser createParser(TokenHandler handler) { return new GenericTokenParser("${", "}", handler)0; }
这里很清楚了,在sql中包含${}的最终会判断为isDynamic的sql
在DynamicSqlSource方法getBoundSql()中rootSqlNode.apply(context)完成sql拼接,
在这里是将参数 1 or 1=1拼接到 select * from user where id = 后面;
@Override public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); context.getBindings().forEach(boundSql::setAdditionalParameter); return boundSql; }
如果是RawSqlSource 中构造方法:
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); }
产生SqlSourceBuilder 解析sql的parse()方法中使用ParameterMappingTokenHandler
对象来处理sql
@Override public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; }
将#{}部分都用?代替,实现预编译sql。
到这里sql中 #与
的
处
理
梳
理
完
毕
。
那
么
既
然
的处理梳理完毕。 那么既然
的处理梳理完毕。那么既然会出现sql注入的问题,那么什么场景下可以受那个$动态sql呢?
1 将表名,列名当作参数使用的场景。
select * from ${tableName};这里就不能使用#了
2 group by的指定字段排序。