【前言】在上一篇文章中,我们说到了JDBC在操作数据库的同时,需要创建执行对象。对象分为:Statement和PrepareStatement两种,那么这两者之间,又有何种渊源呢?
刚一开始,不了解的时候,去网上看,上边将PrepareStatement的逼格吹得很高,比如说:在JDBC应用中,如果你已经是稍有水平的开发者,你就应该始终以PrepareStatement代替Statement。也就是说,在任何时候都不要使用Statement。看到这里,小编内心不禁暗暗的下定决心,以后也要使用PrepareStatement。
那么,在真实的开发中,它们两者又是孰优孰劣呢?
上一篇,在JDBC创建对象并执行SQL语句的时候,我们可以知道,两者在这方面的区别:
<span style="font-family:Microsoft YaHei;font-size:14px;"> //创建语句执行对象
//需要执行的sql语句
String sql = "select user_name,password from t_user where user_id=?";
//使用Statement对象
Statement st=conn.createStatement();
//使用PrepareStatement对象
PreparedStatement pst = conn.createStatement(sql);
//执行语句
//使用Statement对象
st.executeQuery("sql");
//使用PrepareStatement对象
pst.executeQuery(); </span>
可以看到,两者在使用时,使用sql的位置不同。PrepareStatement采用了预编译的方式,而Statement采用的是执行时创建。
在JDBC的API中,Statement要求开发者付出大量的时间和精力,在使用Statement获取JDBC访问时,所具有的一个共同的问题就是输入适当的格式的日期和时间戳。而这个问题,通过采用PrepareStatement可以自动解决。同时,PrepareStatement的另外一个优点字符串不是动态创建的。
OK,说到这里,我们可以初步断定,网上把PrepareStatement的逼格吹得那么高,还是有一定的缘由的。那么,为什么要说:在JDBC应用中,如果你已经是稍有水平的开发者,你就应该始终以PrepareStatement代替Statement。难道使用PrepareStatement仅仅就是为了逼格高一些吗?显然不是吧,下边小编就给您好好的唠唠。
JDBC驱动的最佳化基于使用的是什么功能。选择PrepareStatement还是Statement取决于你要怎样使用它们。对于执行一次的SQL语句,选择Statement是最好的;相反,如果SQL语句需要被多次执行,那么我们可以选用PrepareStatement。
1、PrepareStatement 提高性能
PrepareStatement属于预编译的方式,每一种数据库都会尽最大努力对预编译语句提供最大的性能优化。因为预编译语句有可能被重复调用,所以sql语句在被数据库的编译器编译后,执行代码被缓存下来,那么下次调用的时候,只要相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中,就会执行。当然,这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对整个数据库中。只要预编译的语句和缓存中相匹配,那么在任何时候就可以 再次进行编译就可以直接执行。
而在Statement的语句中,即使是相同的操作,由于每次操作的数据不同,所以整个语句相匹配的机会极小,几乎不太可能完全匹配。比如:
<span style="font-family:Microsoft YaHei;font-size:14px;"> insert into tb_name (col1,col2) values ('11','22');
insert into tb_name (col1,col2) values ('11','23');</span>
即使是相同的操作,但是因为插入的数据内容不同,所以整个语句本身并不能匹配,没有缓存语句的意义。
当然,并不是所有的预编译语句都一定会被缓存,数据库本身会使用一定的策略。比如使用访问频率等指标,来决定什么时候不在缓存已有的预编译结果,以保存更多的存储空间来存储新的预编译语句。
2、提高代码的可读性和可维护性
一个数据库插入的Demo
<span style="font-family:Microsoft YaHei;font-size:14px;"> //执行一个插入操作
//Statement
Statement st;
st.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
//PrepareStatement
PreparedStatement pst;
pst = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
pst.setString(1,var1);
pst.setString(2,var2);
pst.setString(3,var3);
pst.setString(4,var4);
pst.executeUpdate();</span>
虽然,使用PrepareStatement会比Statement多几行代码,但是这样的改变,无论是从代码可读性还是可维护性上来讲,都是值得的。
3、安全性
PrepareStatement防止了SQL注入,提高了程序的安全性。
<span style="font-family:Microsoft YaHei;font-size:14px;">String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";</span>
在这里,如果我们把[‘or’1'='1]作为varpasswd传入进来,用户名随意填写,那么就会产生SQL注入问题。
<span style="font-family:Microsoft YaHei;font-size:14px;">select * from tb_name = '随意' and passwd = '' or '1' = '1';</span>
因为‘1’=‘1’肯定成立,所以可以通过任何验证者。
更进一步,把[';drop table tb_name;]作为varpasswd传入进来
<span style="font-family:Microsoft YaHei;font-size:14px;">select * from tb_name = '随意' and passwd = '';drop table tb_name;</span>
在某些数据库上,这些语句完全可以执行。
当然,如果使用预编译语句,传入的任何内容就不会和原来的语句发生任何匹配的关系。使用PrepareStatement,预编译语句的话,就不用对传入的数据做任何考虑;如果使用Statement,就需要对drop等做判断,防止SQL注入。
【尾巴】
遇到不懂的问题,上网去查,站在巨人的肩膀上,不失为一个很好的方法。但是,对于巨人们对问题的理解,我们还需要进一步的去消化,只有这样,我们才能够成为巨人!