天天看点

【Java学习笔记】——Statement & PrepareStatement

【前言】在上一篇文章中,我们说到了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注入。

【尾巴】

       遇到不懂的问题,上网去查,站在巨人的肩膀上,不失为一个很好的方法。但是,对于巨人们对问题的理解,我们还需要进一步的去消化,只有这样,我们才能够成为巨人!