statement

更新时间:2022-12-28 12:33:53 阅读: 评论:0


2022年12月28日发(作者:偷情许可证)

JDBC:深⼊理解PreparedStatement和Statement

前⾔

最近听⼀个⽼师讲了公开课,在其中讲到了PreparedStatement的执⾏原理和Statement的区别。

当时听公开课⽼师讲的时候感觉以前就只知道PreparedStatement是“预编译类”,能够对sql语句进⾏预编译,预编译后能够提⾼数据库

sql语句执⾏效率。

但是,听了那个⽼师讲后我就突然很想问⾃⼰,预编译??是谁对sql语句的预编译??是数据库?还是PreparedStatement对象??到底

什么是预编译??为什么能够提⾼效率??为什么在数据库操作时能够防⽌sql注⼊攻击??这就引起了我对Preparedstatement的疑惑。

公开课⽼师讲的时候说:”PreparedStatement会对sql⽂进⾏预编译,预编译后,会存储在PreparedStatement对象中,等下次再执⾏这

个PreparedStatement对象时,会提⾼很多效率”。这句话我听了后更疑惑了,预编译是什么我不知道就算了,竟然还说:对sql预编译后

会存储在PreparedStatement对象中??我就想问问sql预编译后是什么??什么被存储在PreparedStatement对象中??

更让⼈感觉疑惑的是Statement。对就是Statement,公开课⽼师说:“同⼀条sql语句(字符串都是相同的)在Statement对象中多次执

⾏时,Statement只会对当前sql⽂编译⼀次,编译后存储在Statement中,在之后的执⾏过程中,都不会进⾏编译⽽是直接运⾏sql语

句”。什么??我没听错吧?Statement还有编译??等等等等。。。。我当时真的是听的怀疑⼈⽣。

PreparedStatement

在说PreparedStatement之前,我们来看看什么是预编译。其实预编译是MySQL数据库本⾝都⽀持的。但是MySQLServer4.1之前的

版本是不⽀持预编译的。(具体是否包括4.1还得读者们亲⾃试验)

在这⾥,笔者⽤的是MySQL5.6绿⾊版。

MySQL中的预编译功能是这样的

预编译的好处:

MySQL执⾏预编译

MySQL执⾏预编译分为如三步:

如果需要再次执⾏myfun,那么就不再需要第⼀步,即不需要再编译语句了:

如果你看MySQL⽇志记录,你就会看到:

配置MySQL⽇志记录

⼤家平时都使⽤过JDBC中的PreparedStatement接⼝,它有预编译功能。什么是预编译功能呢?它有什么好处呢?

当客户发送⼀条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执⾏的函数,最后才是执⾏SQL语句。其中

注意:可执⾏函数存储在MySQL服务器中,并且当前连接断开后,MySQL服务器会清除已经存储的可执⾏函数。

如果我们需要执⾏多次inrt语句,但只是每次插⼊的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。

1.执⾏预编译语句,例如:prepareshowUrsByLikeNamefrom'lect*fromurwhereurnamelike?';

2.设置变量,例如:t@urname='%⼩明%';

3.执⾏语句,例如:executeshowUrsByLikeNameusing@urname;

1.设置变量,例如:t@urname='%⼩宋%';

2.执⾏语句,例如:executeshowUrsByLikeNameusing@urname;

路径地址可以⾃⼰修改。

配置之后就重启MySQL服务器:

在cmd管理员界⾯执⾏以下操作。

使⽤PreparedStatement执⾏sql查询

JDBCMySQL驱动5.0.5以后的版本默认PreparedStatement是关闭预编译功能的,所以需要我们⼿动开启。⽽之前的JDBC

MySQL驱动版本默认是开启预编译功能的。

MySQL数据库服务器的预编译功能在4.1之后才⽀持预编译功能的。如果数据库服务器不⽀持预编译功能时,并且使⽤

PreparedStatement开启预编译功能是会抛出异常的。这点⾮常重要。笔者⽤的是mysql-connector-jar-5.1.13版本的JDBC驱动。

在我们以前写项⽬的时候,貌似都没有注意是否开启PreparedStatement的预编译功能,以为它⼀直都是在使⽤的,现在看看不开启

PreparedStatement的预编译,查看MySQL的⽇志输出到底是怎么样的。

log-output=FILE

general-log=1

general_log_file="E:"

slow-query-log=1

slow_query_log_file="E:mysql_"

long_query_time=2

netstopmysql

netstartmysql

@Test

publicvoidshowUr(){

//数据库连接

Connectionconnection=null;

//预编译的Statement,使⽤预编译的Statement提⾼数据库性能

PreparedStatementpreparedStatement=null;

//结果集

ResultSetresultSet=null;

try{

//加载数据库驱动

e("");

//通过驱动管理类获取数据库链接

connection=nection("jdbc:mysql://localhost:3306/mybatis","root","");

//定义sql语句?表⽰占位符

Stringsql="lect*fromurwhereurname=?";

//获取预处理statement

preparedStatement=eStatement(sql);

//设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值

ing(1,"王五");

这是输出⽇志:

可以看到,在⽇志中并没有看到"prepare"命令来预编译"lect*fromurwhereurname=?"这个sql模板。所以我们⼀般⽤的

PreparedStatement并没有⽤到预编译功能的,只是⽤到了防⽌sql注⼊攻击的功能。防⽌sql注⼊攻击的实现是在PreparedStatement中

实现的,和服务器⽆关。笔者在源码中看到,PreparedStatement对敏感字符已经转义过了。

在PreparedStatement中开启预编译功能

ing(1,"王五");

//向数据库发出sql执⾏查询,查询出结果集

resultSet=eQuery();

ing(1,"张三");

resultSet=eQuery();

//遍历查询结果集

while(()){

n(ing("id")+""+ing("urname"));

}

();

();

n("

#");

}catch(Exceptione){

tackTrace();

}finally{

//释放资源

if(resultSet!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(preparedStatement!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(connection!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

}

}

20Query/*mysql-connector-java-5.1.13(Revision:${on-id})*/SELECT@@_increment_increment

20QuerySHOWCOLLATION

20QuerySETNAMESutf8mb4

20QuerySETcharacter_t_results=NULL

20QuerySETautocommit=1

20Querylect*fromurwhereurname='王五'

20Querylect*fromurwhereurname='张三'

20Quit

设置MySQL连接URL参数:uServerPrepStmts=true,如下所⽰。

jdbc:mysql://localhost:3306/mybatis?&uServerPrepStmts=true

这样才能保证mysql驱动会先把SQL语句发送给服务器进⾏预编译,然后在执⾏executeQuery()时只是把参数发送给服务器。

再次执⾏上⾯的程序看下MySQL⽇志输出:

很明显已经进⾏了预编译,Preparelect*fromurwhereurname=?,这⼀句就是对sql语句模板进⾏预编译的⽇志。好的⾮常Nice。

注意:

我们设置的是MySQL连接参数,⽬的是告诉MySQLJDBC的PreparedStatement使⽤预编译功能(5.0.5之后的JDBC驱动版本需要⼿动

开启,⽽之前的默认是开启的),不管我们是否使⽤预编译功能,MySQLServer4.1版本以后都是⽀持预编译功能的。

cachePrepStmts参数

当使⽤不同的PreparedStatement对象来执⾏相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数

key,导致⼆次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true。例如:

程序代码:

21QuerySHOWWARNINGS

21Query/*mysql-connector-java-5.1.13(Revision:${on-id})*/SELECT@@_increment_increment

21QuerySHOWCOLLATION

21QuerySETNAMESutf8mb4

21QuerySETcharacter_t_results=NULL

21QuerySETautocommit=1

21Preparelect*fromurwhereurname=?

21Executelect*fromurwhereurname='王五'

21Executelect*fromurwhereurname='张三'

21Clostmt

21Quit

jdbc:mysql://localhost:3306/mybatis?uServerPrepStmts=true&cachePrepStmts=true

@Test

publicvoidshowUr(){

//数据库连接

Connectionconnection=null;

//预编译的Statement,使⽤预编译的Statement提⾼数据库性能

PreparedStatementpreparedStatement=null;

//结果集

ResultSetresultSet=null;

try{

//加载数据库驱动

e("");

//通过驱动管理类获取数据库链接

connection=nection("jdbc:mysql://localhost:3306/mybatis?&uServerPrepStmts=true&cachePrepStmts=true","root"

preparedStatement=eStatement("lect*fromurwhereurnamelike?");

ing(1,"%⼩明%");

resultSet=eQuery();

//遍历查询结果集

while(()){

n(ing("id")+""+ing("urname"));

}

//注意这⾥必须要关闭当前PreparedStatement对象流,否则下次再次创建PreparedStatement对象的时候还是会再次预编译sql模板,使⽤PreparedStat

();

();

preparedStatement=eStatement("lect*fromurwhereurnamelike?");

⽇志输出:

注意:每次使⽤PreparedStatement对象后都要关闭该PreparedStatement对象流,否则预编译后的函数key是不会缓存的。

Statement执⾏sql语句是否会对编译后的函数进⾏缓存

这个不好说,对于每个数据库的具体实现都是不⼀样的,对于预编译肯定都⼤体相同,但是对于Statement和普通sql,数据库⼀般都是先

检查sql语句是否正确,然后编译sql语句成为函数,最后执⾏函数。其实也不乏某些数据库很疯狂,对于普通sql的函数进⾏缓存。但是⽬

前的主流数据库都不会对sql函数进⾏缓存的。因为sql语句变化那么多,如果对所有函数缓存,那么对于内存的消耗也是⾮常巨⼤的。

preparedStatement=eStatement("lect*fromurwhereurnamelike?");

ing(1,"%三%");

resultSet=eQuery();

//遍历查询结果集

while(()){

n(ing("id")+""+ing("urname"));

}

();

();

}catch(Exceptione){

tackTrace();

}finally{

//释放资源

if(resultSet!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(preparedStatement!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(connection!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

}

}

24QuerySHOWWARNINGS

24Query/*mysql-connector-java-5.1.13(Revision:${on-id})*/SELECT@@_increment_increment

24QuerySHOWCOLLATION

24QuerySETNAMESutf8mb4

24QuerySETcharacter_t_results=NULL

24QuerySETautocommit=1

24Preparelect*fromurwhereurnamelike?

24Executelect*fromurwhereurnamelike'%⼩明%'

24Executelect*fromurwhereurnamelike'%三%'

24Quit

如果你不确定普通sql语句的函数是否被存储,那要怎么做呢??

其实还是⼀个道理,查看MySQL⽇志记录:检查第⼆次执⾏相同sql语句时,是否是直接通过execute来进⾏查询的。

@Test

publicvoidshowUr(){

//数据库连接

Connectionconnection=null;

//预编译的Statement,使⽤预编译的Statement提⾼数据库性能

PreparedStatementpreparedStatement=null;

//结果集

ResultSetresultSet=null;

try{

//加载数据库驱动

e("");

//通过驱动管理类获取数据库链接

connection=nection("jdbc:mysql://localhost:3306/mybatis?&uServerPrepStmts=true&cachePrepStmts=true","root"

Statementstatement=Statement();

resultSet=eQuery("lect*fromurwhereurname='⼩天'");

//遍历查询结果集

while(()){

n(ing("id")+""+ing("urname"));

}

();

();

statement=Statement();

resultSet=eQuery("lect*fromurwhereurname='⼩天'");

//遍历查询结果集

while(()){

n(ing("id")+""+ing("urname"));

}

();

();

}catch(Exceptione){

tackTrace();

}finally{

//释放资源

if(resultSet!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(preparedStatement!=null){

try{

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

if(connection!=null){

try{

();

⽇志记录:

看⽇志就会知道,都是Query命令,所以并没有存储函数。

总结:

所以到了这⾥我的疑惑都解开了,PreparedStatement的预编译是数据库进⾏的,编译后的函数key是缓存在PreparedStatement中

的,编译后的函数是缓存在数据库服务器中的。预编译前有检查sql语句语法是否正确的操作。只有数据库服务器⽀持预编译功能

时,JDBC驱动才能够使⽤数据库的预编译功能,否则会报错。预编译在⽐较新的JDBC驱动版本中默认是关闭的,需要配置连接参数

才能够打开。在已经配置好了数据库连接参数的情况下,Statement对于MySQL数据库是不会对编译后的函数进⾏缓存的,数据库不

会缓存函数,Statement也不会缓存函数的key,所以多次执⾏相同的⼀条sql语句的时候,还是会先检查sql语句语法是否正确,然后

编译sql语句成函数,最后执⾏函数。

对于PreparedStatement在设置参数的时候会对参数进⾏转义处理。

因为PreparedStatement已经对sql模板进⾏了编译,并且存储了函数,所以PreparedStatement做的就是把参数进⾏转义后直接传

⼊参数到数据库,然后让函数执⾏。这就是为什么PreparedStatement能够防⽌sql注⼊攻击的原因了。

PreparedStatement的预编译还有注意的问题,在数据库端存储的函数和在PreparedStatement中存储的key值,都是建⽴在数据库

连接的基础上的,如果当前数据库连接断开了,数据库端的函数会清空,建⽴在连接上的PreparedStatement⾥⾯的函数key也会被清

空,各个连接之间的预编译都是互相独⽴的。

使⽤Statement执⾏预编译

使⽤Statement执⾏预编译就是把上⾯的原始SQL语句预编译执⾏⼀次。

();

}catch(SQLExceptione){

//TODOAuto-generatedcatchblock

tackTrace();

}

}

}

}

26QuerySHOWWARNINGS

26Query/*mysql-connector-java-5.1.13(Revision:${on-id})*/SELECT@@_increment_increment

26QuerySHOWCOLLATION

26QuerySETNAMESutf8mb4

26QuerySETcharacter_t_results=NULL

26QuerySETautocommit=1

26Querylect*fromurwhereurname='⼩天'

26Querylect*fromurwhereurname='⼩天'

26Quit

在持久层框架中存在的问题

很多主流持久层框架(MyBatis,Hibernate)其实都没有真正的⽤上预编译,预编译是要我们⾃⼰在参数列表上⾯配置的,如果我们不⼿动

开启,JDBC驱动程序5.0.5以后版本默认预编译都是关闭的。

所以我们要在参数列表中配置,例如:

注意:

在MySQL中,既要开启预编译也要开启缓存。因为如果只是开启预编译的话效率还没有不开启预编译效率⾼,⼤家可以做⼀下性能测试,

其中性能测试结果在这篇博客中有写到,,⽽在MySQL中开启预编译和开启缓存,其中的查询效率和不开启预编译和不开启缓存的效率是

持平的。这⾥⽤的测试类是PreparedStatement。

参考资料:

在写这篇⽂章的时候发⽣了很多让⼈恼⽕的事情,⽐如⽹上很多的答案基本上都是错误的,竟然还有⼈说好??不知道就不要乱说,乱发表

博客,误⼈⼦弟!!

Connectioncon=nection();

Statementstmt=Statement();

eUpdate("preparemyfunfrom'lect*fromt_bookwherebid=?'");

eUpdate("t@str='b1'");

ResultSetrs=eQuery("executemyfunusing@str");

while(()){

(ing(1)+",");

(ing(2)+",");

(ing(3)+",");

n(ing(4));

}

eUpdate("t@str='b2'");

rs=eQuery("executemyfunusing@str");

while(()){

(ing(1)+",");

(ing(2)+",");

(ing(3)+",");

n(ing(4));

}

();

();

();

jdbc:mysql://localhost:3306/mybatis?&uServerPrepStmts=true&cachePrepStmts=true

本文发布于:2022-12-28 12:33:53,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/46650.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:鞭笞
下一篇:deb
标签:statement
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图