Mybatis中配置文件中#和$区别

mybatis框架作为一款半自动化的持久层框架,其sql语句都要我们自己手动来编写。既然是手动编写sql,那么mybatis就需要对我们手动编写的sql语句就行处理。那么在处理sql语句过程中,若是不当的sql语句编译,SQL注入就会乘虚而入。我们如何来防止呢?

先来了解下预编译:

sql预编译:sql预编译指的是在数据库驱动在发送sql语句和参数给DBMS之前对sql进行编译,这样DBMS执行sql时,就不需要重新编译。

jdbc的PreparedStatements类抽象预编译语句,①使用预编译可以优化sql执行。预编译之后的sql多数情况下可以直接执行,DBMS不需要再次编译,编译的复杂度越大,预编译阶段可以合并为多次操作为一个操作。②预编译语句对象可以重复复利用。把一个sql预编译后产生的PreparedStatement对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的PreparedStatement对象。

众所周知,在JDBC编程中,常用Statement,PreparedStatement和CallableStatement三种方式来执行查询语句,其中Statement用于通用查询,PreparedStatement用于执行参数化查询,而CallableStatement则用于存储过程。

①Statement 该对像执行静态的SQL语句,并且返回执行结果。此处的SQL语句必须是完整的,有明确的数据指示。查的是哪条记录?改的是哪条记录?都要指示清楚。通过调用Connection对象的createStatement方法创建该对象。查询:ResultSet executeQuery(String sql)----返回查询结果的封装对象ResultSet。用next()遍历结果,getXX()获取记录数据。修改,删除,增加:int excuteUpdate(String sql)----返回影响的数据表记录数。②PreparedStatement SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。可以通过调用Connection对象的preparedStatement()方法获取PreparedStatement对象。PreparedStatement对象所执行地SQL语句中,参数用问号(?)来表示,调用PreparedStatement对象的setXXX()方法来设置这些参数。setXXX()方法有两个参数,第一个参数是要设置地SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值,注意用setXXX()方式设置时,需要与数据库中的字段类型对应,例如mysql中字段为varchar,就需要使用setString方法,如果为Date类型,就需要使用setDate方法来设置具体sql参数。简单来说就是,预编译的SQL语句不是有具体数值的语句,而是用(?)来替代具体数据,然后在执行的时候再调用setXXX()方法把具体的数据传入。同时,这个语句只在第一次执行的时候编译一次,然后保存在缓存中。之后执行时,只需从缓存中抽取编译过了的代码以及新传进来的具体数据,即可获得完整的sql命令。这样一来就省下了后面每次执行时语句的编译时间。

使用预编译分4步走:

①定义预编译的sql语句,其中待填入的参数有?占位。注意?无关类型,不需要加分号之类。其具体数据类型在下面setXXX()时决定。

②创建预编译Statement,并把sql语句传入。此时sql语句已与此PreparedStatement绑定。所以第4步执行语句时无需再把sql语句作为参数传入execute()。

③填入具体参数。通过setXXX(问号下标,数值)来为sql语句填入具体数据。注意:问号下标从1开始,setXXX与数值类型有关,字符串就是setString(index,str)。

④执行预处理对象。主要有:boolean execute();在此PreparedStatement对象中执行SQL语句,该语句可以是任何种类的SQL语句。ResultSet executeQuery;在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象。int executeUpdate();在此PreparedStatement对象中执行SQL语句,该语句必须是一个SQL数据库操作语言DML,比如INSERT、UPDATE或DELETE语句;或者是无返回内容的SQL语句,比如DDL语句。

注意:前面创建PreparedStatement时已经把SQL语句传入了,此时执行不需要再把SQL语句传入,这是与一般statement执行SQL语句所不同之处。比如:

     String sql="select Sname from stu where Sno=?"

     PreparedStatement prestmt = conn.prepareStatement(sql);

     prestmt.setString(1,sno);

     prestmt.executeQuery();

使用预编译的好处:

1:PreparedStatement比Statement要快 

使用PreparedStatement最重要的一点好处是它拥有更佳的性能趋势,SQL语句会预编译在数据库中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。

2:PreparedStatement可以防止SQL注入式攻击

SQL注入攻击:SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,从而利用系统的SQL引擎完成恶意行为的做法。

比如:某个网站的登录验证SQL查询代码为:

String sql="select * from users where name=' "+userName+" ' and password=' "+passWord+" ' ";

恶意填入:

userName="1'OR'1'='1 ";
passWord="1'OR'1'='1 ";

那么最终SQL语句编程了:

String sql="select * from users where name='1'OR'1'='1' and password='1'OR'1'='1' ";

因为where条件恒为真,这就相当于执行:

String sql="select * from users";

因此可以达到无账号密码亦可登录网站。

使用PreparedStatement的参数化查询可以阻挡大部分的SQL注入。在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破环性的指令,也不会被数据库所运行。因为对于参数化查询来说,查询SQL语句的格式是已经规定好了,需要查询的数据也设置好了,缺的只是具体的几个数据而已。所以用户提供的只是数据,而且只能按需提供,无法更进一步作出影响数据库的其他举动来。

总之:PreparedStatement可以尽可能地提高访问数据库的性能。我们都知道数据库在处理SQL语句时,都有一个预编译的过程,而预编译对象(PreparedStatement)就是把一些格式固定的SQL编译后,存放在内存池中即数据库缓存池,当我们再次执行相同的SQL语句时就不需要预编译过程了,只需要DBMS运行SQL语句。所以当你需要执行Statements对象多次的时候,PreparedStatement对象将会大大降低运行时间,特别是大型的数据库中,它可以有效的加快访问数据库的速度。其次,使用PreparedStatement对象可以大大提高代码的可读性和可维护性。

Mysql支持预编译,只是默认没开启。

看到这里,大家是否有个疑问呢?我大概知道了

大家都知道,Mybatis内置参数,形如#{XXX}的,均采用了sql预编译形式,大致知道mybatis底层使用PreparedStatement,过程是先将带有占位符(?)的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存,然后直接执行带有真实参数的sql。如果你的基本结论也是如此,那你就大错特错了。

Oracle里面除了查询结果集缓存外,还有SQL缓存。

mysql是否支持预编译有两层意思:

1.db(数据库)是否支持预编译

2.连接数据库的url是否指定了需要预编译,比如:jdbc:mysql://127.0.0.1:3306/user?useServerPrepStmts=true,其中

useServerPrepStmts=true是非常非常重要的参数。如果不配置,PrepreStatement实际是假的PreparedStatement。(注意,JDBC只是java定义的规范,可以理解为接口,每种数据库必须有自己的实现,实现之后一般叫做数据库驱动。本文所涉及的PreparedStatement,是由MySQL实现的,并不是JDK实现的默认行为,也就是说,不同的数据库表现不同,不能一概而论。PreparedStatement也只是有效防止SQL注入,并不是万事无忧,比如当用户输入"%XX%"+"%"时,PreparedStatement竟然没有将%转义,%是like的通配符,sql结果可能是like '%XX%%',那么真个查询意思就变了。虽然此种SQL注入危害不大,但是这种查询会耗尽系统资源,从而演化为拒绝服务攻击。这种可能需要我们自己做一些处理。)

注意:mysql预编译功能有版本要求,包括server版本和mysql.jar包版本。以前的版本默认useServerPrepStmts=true,5.0.5以后的版本默认useServerPrepStmts=false。

动态SQL是mybatis的强大特性之一,也是它优于其它ORM(对象/关系 映射)框架的一个重要原因。mybatis在对sql语句进行sql语句进行预编译之前,会对sql进行动态解析,解析为一个BoundSql对象(封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数),也是在此对动态SQL进行处理的。在动态SQL解析阶段,#{}和${}会有不同的表现。

#{}:解析为一个JDBC预编译语句(prepared statement)的参数标记符。

例如,Mapper.xml中如下sql语句:

select * from user where name = #{name};

动态解析为:

select * from user where name = ?; 

一个#{}被解析为一个参数占位符?

而${}仅仅为一个纯粹的String替换,在动态SQL解析阶段将会进行变量替换。

例如,Mapper.xml中如下sql:

select * from user where name = ${name};

当我们传递的参数为“jack”时,上述sql解析为:

select * from user where name = "Jack";

预编译之前的SQL语句已经不包含变量了,完全已经是常量数据了。综上所得,${}变量的替换阶段在动态SQL解析阶段,而${}变量的替换是在DBMS中。

 总之,mybatis的#{}和${}区别,按照上面的分析,应该不难理解,都是发生于Mybatis底层动态解析过程。#{}被解析为占位符?,而${}仅仅为一个纯粹的String替换,在动态SQL解析阶段将会进行变量替换,而#{}的变量替换是在DBMS中。之后再调用connection进行sql预编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值