北京看白癜风疗效好专科医院 http://baidianfeng.39.net/a_ht/160229/4776790.html
Cause:java.sql.SQLSyntaxErrorException:Unknowncolumnitlezhiinwhereclause
好久之前的mybatis源码阅读还有一点没有完成,今天准备来收个尾,结果一执行测试代码就报错了。
#01
报错分析
报错代码与异常信息如下图:
第一瞬间我竟然不知道错在哪里了,感觉就是很正常的一句sql啊,我直接把错误信息“Cause:java.sql.SQLSyntaxErrorException:Unknowncolumnitlezhiinwhereclause”百度一下,答案是把换成#就好了。就马上试了下竟然成功了。
有的答案还说了sql中定义的类型是int型的可以不用加引号,但是如果是字符串类型的,必须加引号。从这句话中我总算知道我的sql错哪里了。
Mybatis的把name参数映射到sql语句中竟然没有加引号,以至于sql报错。
#02
联想测试
同时就想到之前经常听到的面试题:与#的区别、mybatis如何防止sql注入等。如果我把参数itlezhi换成’itlezhi’or1=1就可以实现sql注入了。
经过测试确实可以,只不过这里我调用的selectOne方法,所以直接报错了,改成selectList方法则成功返回了所有的数据。
如果把缓存#则一条数据都没有查出来,实现了防止sql注入的功能。那么在源码中是如何处理呢?
#03
源码跟踪
从之前源码知道sql来源于,把换成#之后直接跟进到创建boundSql的地方,可以看到在rootSqlNode的apply之后sql如下图:
通过debug的sqlBuilder可以看出来SqlNode的apply只处理了,#{name}还没有进行处理。
那么#是在哪里处理的呢?就在紧接着的SqlSourceBuilder中。在上图中apply之后马上初始化了一个SqlSourceBuilder对象并执行了parse方法。
SqlSourceBuilder的parse方法源码如下图:
又见到GenericTokenParser这个类了。简单介绍下这个类,这个类可以当成一个占位符解析类,接受3个参数分别表示占位符起始、结束、处理解析结果类。至于处理逻辑在TextSqlNode的apply已经详细介绍过了。这次如上图就会解析#{}。处理逻辑也在上图源码中,解析出来的处理逻辑是把#{}的内容保存下来,并用?替换#{}。
我们可以看到处理完成后的结果如下图:
Sql保存的处理后的sql语句,这个sql是需要预处理才能执行的,parameterMappings中记录着需要预处理的参数。
然后继续跟进源码直到执行sql之前,生成Statement的源码如下图:
生成的boundSql在创建StatementHandler时才使用。再通过生成的StatementHandler创建出来Statement。
从上图可以看到Statement的关键属性。ClientPreparedStatement重写了toString方法,展示了预处理后的sql,实际上的sql是“selectid,name,age,idsfrommemberwhereid=5andname=?”。
columnNames、columnMap分别表示预处理需要的参数和对应的值。columnNames集合中第一个元素值是1和columnMap中1对应的值相当于下面这句话:
preparedStatement.setString(1,"itlezhior1=1");
所以最终的执行结果sql:“selectid,name,age,idsfrommemberwhereid=5andname=itlezhior1=1”。这样的sql一般都查不出来数据了,实现了防止sql注入。
更多Java面试题,可