什么是预编译?
当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,
然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。
其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
注意:
可执行函数存储在MySQL服务器中,并且当前连接断开后,MySQL服务器会清除已经存储的可执行函数。
如果我们需要执行多次insert语句,但只是每次插入的值不同,
MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。
如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。
MySQL执行预编译
MySQL执行预编译分为如三步:
1. 执行预编译语句,例如:prepare showUsersByLikeName from 'select * from user where username like ?';
2. 设置变量,例如:set @username='%小明%';
3. 执行语句,例如:execute showUsersByLikeName using @username;
如果需要再次执行myfun,那么就不再需要第一步,即不需要再编译语句了:
1. 设置变量,例如:set @username='%小宋%';
2. 执行语句,例如:execute showUsersByLikeName using @username;
在PreparedStatement中开启预编译功能
设置MySQL连接URL参数:useServerPrepStmts=true,
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true
这样才能保证mysql驱动会先把SQL语句发送给服务器进行预编译,然后在执行executeQuery()时只是把参数发送给服务器。
注意:
我们设置的是MySQL连接参数,目的是告诉MySQL JDBC的PreparedStatement使用预编译功能(5.0.5之后的JDBC驱动版本需要手动开启,而之前的默认是开启的),不管我们是否使用预编译功能,MySQL Server4.1版本以后都是支持预编译功能的。
Statement 和 PreparedStatement之间的关系和区别.
关系:PreparedStatement继承自Statement,都是接口
区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
1. Statement
每次都会执行SQL语句,相关数据库都要执行SQL语句的编译。
Statement为一条Sql语句生成执行计划,
如果要执行两条sql语句
select colume from table where colume=1;
select colume from table where colume=2;
会生成两个执行计划
一千个查询就生成一千个执行计划!
2. PreparedStatement
用于处理动态SQL语句,在执行前会有一个预编译过程,这个过程是有时间开销的,虽然相对数据库的操作,该时间开销可以忽略不计,
但是PreparedStatement的预编译结果会被缓存,下次执行相同的预编译语句时,就不需要编译,只要将参数直接传入编译过的语句执行代码
中就会得到执行,所以,对于批量处理可以大大提高效率。
(1) PreparedStatement接口继承Statement,PreparedStatement 实例包含已编译的 SQL 语句,
所以其执行速度要快于 Statement 对象。
(2)作为 Statement 的子类,PreparedStatement 继承了 Statement 的所有功能。
三种方法**execute**、 **executeQuery** 和 **executeUpdate** 已被更改以使之不再需要参数
3:应该尽可能以PreparedStatement代替Statement,其原因如下:
(1)代码的可读性和可维护性。
当sql语句冗长的时候,Statement方式的 sql 拼接会很麻烦,容易出错。
而PreparedStatement方式则不会出现上述问题,利用占位符可以很方便的解决 sql 字符串的拼接问题,相对的对占位符的赋值过程就多与Statement方式,但是这样修改程序结构比较明了,易于维护。
(2)性能。
PreparedStatement在执行之前进行了一次预编译,sql 语句执行的时候只需要将数据替换就可以了,不需要每次执行都进行编译,尤其是批处理的时候优化效果比较明显。而Statement处理的时候每次都要重新制定一次执行计划,将sql 进行一次编译,该过程会降低SQL效率。
(3)安全性。
在使用preparedStatement对象执行sql时候命令被数据库编译和解析,然后被放到命令缓冲区,然后每当执行同一个preparedStatement时候,他就被再解析一次,但不会在编译,在缓冲区中可以发现预编译的命令,并且可以重新使用。
所以用户数据不会参与编译,只要把数据替换占位符即可,保证了数据库的安全性。
而Statement只是单纯的sql拼接,用户数据会参与到编译过程中去,对于用户的恶意sql注入,会对数据库带来很大的危害。
罗列出PreparedStatement对象在初级开发中几个常用的方法:
1). PreparedStatement.execute()方法:
该语句可以是任何种类的 SQL 语句
execute 方法返回一个 boolean 值,以指示第一个结果的形式。必须调用 getResultSet 或 getUpdateCount 方法来检索结果,并且必须调用 getMoreResults 移动到任何后面的结果。
返回:
如果第一个结果是 ResultSet 对象,则返回 true;如果第一个结果是更新计数或者没有结果,则返回 false
意思就是如果是查询的话返回true,如果是更新或插入的话就返回false了;
2). PreparedStatement.executeQuery()方法:
返回执行查询语句后得到的ResultSet结果集,注意:该结果集永远不能为null。
3). PreparedStatement.executeUpdate()方法:
返回一个int类型的值,该值代表执行INSERT、DELETE以及UPDATE语句后的更新行数。
如果该值为0,则代表SQL语句没有执行成功。
4).PreparedStatement.addBatch(); executeBatch()方法:
将一组参数添加到此 PreparedStatement 对象的批处理命令中。缓存sql语句,然后批量执行!
将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
总结:
- PreparedStatement的预编译是数据库进行的,编译后的函数key是缓存在PreparedStatement中的,编译后的函数是缓存在数据库服务器中的。预编译前有检查sql语句语法是否正确的操作。只有数据库服务器支持预编译功能时,JDBC驱动才能够使用数据库的预编译功能,否则会报错。预编译在比较新的JDBC驱动版本中默认是关闭的,需要配置连接参数才能够打开。在已经配置好了数据库连接参数的情况下,Statement对于MySQL数据库是不会对编译后的函数进行缓存的,数据库不会缓存函数,Statement也不会缓存函数的key,所以多次执行相同的一条sql语句的时候,还是会先检查sql语句语法是否正确,然后编译sql语句成函数,最后执行函数。
- PreparedStatement源码在设置参数的时候会对用户参数参数进行特殊字符转义处理。
- 因为PreparedStatement已经对sql模板进行了编译,并且存储了函数,所以PreparedStatement做的就是把参数进行转义后直接传入参数到数据库,然后让函数执行。这就是为什么PreparedStatement能够防止sql注入攻击的原因了。
- PreparedStatement的预编译还有注意的问题,在数据库端存储的函数和在PreparedStatement中存储的key值,都是建立在数据库连接的基础上的,如果当前数据库连接断开了,数据库端的函数会清空,建立在连接上的PreparedStatement里面的函数key也会被清空,各个连接之间的预编译都是互相独立的。
在持久层框架中存在的问题
很多主流持久层框架(MyBatis,Hibernate)其实都没有真正的用上预编译,预编译是要我们自己在参数列表上面配置的,如果我们不手动开启,JDBC驱动程序5.0.5以后版本 默认预编译都是关闭的。
所以我们要在参数列表中配置,例如:
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true