在项目中使用orm框架Mybatis对数据库进行相关操作,使用Mybatis最大的好处是可以使用动态sql。所谓动态sql就是Mybatis对sql进行预编译之前会对sql语句进行动态解析,动态解析处理之后会进行预编译,最后数据库驱动再发送sql和参数到DB执行。
Statement的执行机制
Statement的excute方法直接将SQL语句作为参数传入并提交给数据库执行 -> 数据库引擎对SQL语句进行编译得到数据库可执行代码 -> 执行SQL语句。这就表示,每次提交SQL都需要经过编译再执行。
预编译执行机制
一、预编译语句
通常一条SQL从被数据库接收到执行完毕返回,会经历以下三个过程:
- 词法和语义解析;
- 优化sql语句,制定执行计划;
- 执行并返回结果。
在很多情况下,一条SQL语句可能需要反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句不同,insert的values值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。预编译语句就是将上述sql语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化。
二、PreparedStatement的预编译机制
- 获取预编译语句:数据库驱动在发送sql和参数到DB之前,会先调用Connection.prepareStatement(String sql)方法将该sql语句直接提交给MySQL数据库服务器进行编译处理(获取预编译语句可以从三个渠道获取:本地缓存的预编译语句、调用MySQL服务端编译接口获取预编译语句、客户端预编译接口获取预编译语句。注意MySQL的老版本(4.1之前)是不支持服务端预编译的),得到PreparedStatement 预编译语句对象。得到的PreparedStatement对象其实是一个预编译好的SQL语句。
- 之后调用excute方法会直接将该预编译好的语句提交给MySQL数据库运行,不需要再次编译,提高了效率;
- Mybatis 默认情况下,将对所有的 sql 进行预编译处理。Mybatis中使用 statementType来设置编译类型,默认是PreparedStatement。
三、使用预编译可以带来哪些好处
- 提高效率(一次编译、多次运行,省去了解析优化等过程)
- 预编译可以将多个操作步骤合并成一个步骤,一般而言,越复杂的sql,编译程度也会复杂,难度大,耗时,费性能,而预编译可以合并这些操作,预编译之后DBMS可以省去编译直接运行sql;
- 预编译语句可以重复利用,Mybatis如果开启缓存,会把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,在执行预编译时会先去判断是否存在缓存,如果存在则直接使用这个缓存的 PreparedStatement 对象,然后对参数清空,绑定新的参数。如果不存在则调用数据库进行预编译处理生成一个新的PreparedStatement对象。
- 提高安全性:防止sql注入。
#{}和${}在动态解析时的区别
Mybatis动态解析时,对于变量的值替换发生在不同阶段。
- #{ } 解析为一个 JDBC 预编译语句(prepared statement)的占位符(?),变量替换完成是在MySQL服务器中;
- ${ } 仅仅为一个纯碎的 String 替换,在动态 SQL 解析阶段将会进行变量替换,MySQL服务器拿到的即为执行的sql。
总结
预编译的局限性
由于预编译时数据库会进行词法和语义的解析、生成执行计划,因此占位符只能占位SQL语句中的普通值,而表名、列名、关键字等影响编译的部分是不可以使用占位符的(也就是不能使用#{}传参,而是需要使用${}传参)。因此,order by中排序的参数ASC和排序字段都只能使用${}传参(例如: order by ${column} ${ASC})。
#与$的使用场景
- 能使用#{ }的地方就使用#{ },这样不仅可以提高SQL执行的效率,还可以防止SQL注入;
- 表名、列名、关键字等必须使用${}。
${}传参的类型为String时的处理
如果使用${}来传参,如果参数类型为字符串,我们需要在服务端在该字符串两端拼接上“ ' ”与“ ' ”,否则在动态解析时,该字符串参数会被省掉引号,只把值拼到sql中,如下:
update table_name set name = ${"张三"} where id = 1;动态解析后会变成update table_name set name = 张三 where id = 1
update table_name set name = ${" '张三' "} where id = 1;动态解析后会变成update table_name set name = '张三' where id = 1
这里需要说明的是并不是使用${}一定会产生注入。是否会产生注入与是否预编译有关。
本文介绍了Mybatis中的StatementType,重点解析了预编译执行机制,阐述了#{}与${}在动态SQL解析时的区别。预编译能提高SQL执行效率和安全性,但限制了其在某些场景(如表名、列名)的使用。建议尽量使用#{ }以避免SQL注入问题。
2262

被折叠的 条评论
为什么被折叠?



