1。MyBatis1.1${}和#{}的区别${}和#{}Demo数据库数据:dao接口:ListfindByUsername(Stringusername);ListfindByUsername2(Stringusername);Mapper.xml:select*fromuserwhereusernamelike#{username}select*fromuserwhereusernamelike'%${value}%'执行测试代码:@TestpublicvoidfindByUsername()throwsException{InputStreamin=Resources.getResourceAsStream("SqlMapConfig.XML");SqlSessionFactoryBuilder构建器=newSqlSessionFactoryBuilder();SqlSessionFactory工厂=builder.build(in);//true:自动提交SqlSessionsession=factory.openSessi开(真);UserDaouserDao=session.getMapper(UserDao.class);ListuserList=userDao.findByUsername("%small%");ListuserList2=userDao.findByUsername2("small");System.out.println("用户列表:");for(Useruser:userList){System.out.println(user);}System.out.println("userList2:");for(Useruser:userList2){System.out.println(user);}session.close();in.close();}查看执行结果:都可以查询1.2SQL注入问题${}会产生SQL注入,#{}不会产生SQL注入问题我们做一个测试:ListuserList2=userDao.findByUsername2("aaa'or1=1--");System.out.println("userList2:");for(Useruser:userList2){System.out.println(user);}生成的SQL语句通过查询:我们传递的参数是aaa'or1=1--,从而得到查询中的所有数据。大家可以想象一下,如果我要根据id删除怎么办?从id='${value}'处的用户删除如果我通过:1'或1=1;——,结果会怎样,我想大家应该已经知道了。我这里的id是Integer类型,不好测试,就不带大家测试了。有兴趣的可以私下测试。上面如果使用#{},就不会出现SQL注入问题1.3${}和#{}的区别#{}匹配一个占位符,相当于一个?在JDBC中,会对一些敏感的字符进行过滤,编译后会在传递的值上加上双引号,这样就可以防止SQL注入问题。${}匹配真正传递的值,传递后与sql语句拼接。${}会和其他sql字符串拼接起来,不能防止sql注入问题。查看#{}和${}生成的SQL语句:Stringabc="123";#{abc}="123"${value}=123;1.4#{}底层如何防止SQL注入?1.4.1网上的解答关于这类问题,网上有很多问题。总结一下有两个原因:1)#{}底层是PreparedStatement,会被预编译,所以不会有SQL注入问题;其实预编译是MySQL本身的功能,与PreparedStatement无关;而precompilation也不是我们理解的预编译,PreparedStatement底层默认是完全不使用预编译的(需要我们手动开启)!详见下文2)#{}不会产生字符串拼接,${}会产生字符串拼接,所以${}会造成SQL注入问题;这两个答案都经不起深究,最终的答案也只是停留在表面上,具体原因谁也说不清。1.4.2为什么可以防止SQL注入?下面我们打开MySQL驱动的源码看看是怎么回事;打开PreparedStatement类的setString()方法(MyBatis在#{}中使用setString()方法传递参数,而${}没有):setString()方法都是源码:publicvoidsetString(intparameterIndex,Stringx)throwsSQLException{同步(this.checkClosed().getConnectionMutex()){if(x==null){this.setNull(parameterIndex,1);}else{这个。检查关闭();intstringLength=x.length();StringBuilder缓冲区;如果(this.connection.isNoBackslashEscapesSet()){booleanneedsHexEscape=this.isEscapeNeededForString(x,stringLength);对象参数AsBytes;byte[]parameterAsBytes;如果(!Needshexescape){parameterasbytes=null;buf=newstringbuilder(x.length()+2);buf.append('\'');buf.append(x);buf.append('\'');如果(!this.isLoadDataQuery){parameterAsBytes=StringUtils.getBytes(buf.toString(),this.charConverter,this.charEncoding,this.connection.getServerCharset(),this.connection.parserKnowsUnicode(),this.getExceptionInterceptor());}else{parameterAsBytes=StringUtils.getBytes(buf.toString());}this.setInternal(parameterIndex,parameterAsBytes);}else{parameterAsBytes=null;如果(!this.isLoadDataQuery){parameterAsBytes=StringUtils.getBytes(x,this.charConverter,this.charEncoding,this.connection.getServerCharset(),this.connection.parserKnowsUnicode(),this.getExceptionInterceptor());}else{parameterAsBytes=StringUtils.getBytes(x);}this.setBytes(parameterIndex,parameterAsBytes);}返回;}StringparameterAsString=x;booleanneedsQuoted=true;如果(this.isLoadDataQuery||this.isEscapeNeededForString(x,stringLength)){needsQuoted=false;buf=newStringBuilder((int)((double)x.length()*1.1D));buf.append('\'');ifor(intiingstring=0;++){//遍历字符串,得到每个字符charc=x.charat(i);switch(c){case'\u0000':buf.append('\\');buf.append('0');buf.append('\\');case'\n':buf.append('\\');');休息;case'\r':buf.append('\\');buf.append('r');休息;case'\u001a':buf.append('\\');buf.append('Z');休息;case'“”:if(this.usissimode){buf.append('\\');}buf.append('“”);休息;case'\'':buf.append('\\');buf.append('\'');休息;case'\\':buf.append('\\');buf.append('\\');休息;案例“¥”:case'?':if(this.charsetEncoder!=null){CharBuffercbuf=CharBuffer.allocate(1);ByteBufferbbuf=ByteBuffer.allocate(1);cbuf.put(c);cbuf.position(0);this.charsetEncoder.encode(cbuf,bbuf,true);如果(bbuf.get(0)==92){buf.append('\\');}}buf.append(c);休息;默认值:buf.append(c);}}buf.append('\'');parameterAsString=buf.toString();}buf=null;byte[]parameterAsBytes;如果(!this.isLoadDataQuery){如果(needsQuoted){parameterAsBytes=StringUtils.getBytesWrapped(parameterAsString,'\'','\'',this.charConverter,getthis.charEncoding().parserKnowsUnicode(),this.getExceptionInterceptor());}else{parameterAsBytes=StringUtils.getBytes(parameterAsString,this.charConverter,this.charEncoding,this.connection.getServerCharset(),this.connection.parserKnowsUnicode(),this.getExceptionInterceptor());}}else{parameterAsBytes=StringUtils.getBytes(parameterAsString);}this.setInternal(parameterIndex,parameterAsBytes);this.parameterTypes[parameterIndex-1+this.getParameterIndexOffset()]=12;}}}我们执行#{}的查询语句,判断点观察:最终传递的参数如下:最终传递的参数为:'aaa\'or1=1--我们在数据库中执行如下SQL语句(一定不能查询到数据):select*fromuserwhereusernamelike'aaa\'or1=1--'如果去掉PreparedStatement中添加的“/”怎么办?下面尝试执行SQL:select*fromuserwhereusernamelike'aaa'or1=1--'我们也可以通过MySQL日志观察#{}和${}生成的SQL语句来分析问题:1)开启MySQL日志:在MySQL配置文件[mysqld]下添加如下配置:#是否开启mysql日志0:关闭(默认值)1:开启general-log=1#Mysql日志存放位置general_log_file="D:/query.log"2)重启MySQL服务(以管理员身份运行):netstopmysqlnetstartmysql使用mybatis执行如下两条SQL语句:查看MySQL日志:1.5#{}和${}的应用场景因为#{}比${}好太多了,那为什么${}存在呢?只需使用#{},一切都会好起来的?其实不是的,${}也是有用的,我们都知道${}会把字符串拼接起来生成一个新的字符串Query,查询user表中所有张姓员工的信息。sql语句为:select*fromuserwherenamelike'Zhang%'此时如果传入的参数为“张”,如果使用${}:select*fromuserwherenamelike'$生成的SQL语句{value}%':select*fromuserwherenamelike'Zhang%'如果使用#{}:select*fromuserwherenamelike#{value}生成的SQL语句"%":select*fromuserwherenamelike'张'"%"如果传入的参数是"张%",使用#{}:select*fromuserwherenamelike#{value}生成的SQL语句:selectt*fromuserwherenamelike'Zhang%'uses${}:select*fromuserwherenamelike'${value}'生成SQL语句:select*fromuserwherenamelike'Zhang%'通过上面的SQL语句我们可以发现#{}会加双引号,${}匹配真实值。还有一点,如果使用${},则必须填写value,即:${value},#{}那么1.5.2中什么情况下可以随意使用${}呢?场景示例:代码测试:执行后发现执行成功。我们可以切换一下,把${}改成#{},就会出现SQL语法错误的异常。1.6小结1.6.1SQL注入问题MyBatis的#{}可以防止SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数。该方法会获取传入参数的每个字符,然后进行循环比较。如果发现敏感字符(如:单引号、双引号等),则在前面加一个'/'对这个符号进行转义,使其成为一个普通的字符串,不参与SQL语句的生成,达到了防止SQL注入的效果。其次,${}本身的初衷是参与SQL语句的语法生成,自然会导致SQL注入的问题(字符过滤的问题暂且不考虑)。1.6.2#{}和${}的使用小结1)#{}的时候,会根据传入的值来选择是否加双引号,所以我们在传递参数的时候,一般都是直接传,不用添加双引号引号,${}不会,需要我们手动添加2)传递参数时,我们说过#{}中可以写任何值,${}必须使用值;即:${value}3)#{}对SQL注入进行字符过滤,${}只是作为普通值使用,并没有考虑这些问题4)#{}的应用场景是通过条件值给SQL语句的where子句,${}的应用场景是传递一些需要参与SQL语句语法生成的值。