Statement与PreparedStatement的区别

1、Statement为每一条Sql语句生成执行计划, 如果要执行两条sql语句 
select colume from table where colume=1; 
select colume from table where colume=2; 
会生成两个执行计划,一千个查询就生成一千个执行计划。而生成计划是非常消耗资源的 
2、PreparedStatement用于使用绑定变量重用执行计划 
select * from xxx.sometable t where t.id=?; 
通过set方法给sql语句按占位符"?"先后顺序赋值,只需要生成一个执行计划,可以重复使用。 
   当处理批量SQL语句时,这个时候就可以体现PrepareStatement的优势,由于采用Cache机制,则预先编译的语句,就会放在Cache中,下次执行相同SQL语句时,则可以直接从Cache中取出来,效率要比statement高好几倍 

PreparedStatement类是Statement类的子类,它直接继承并重写了Statement的方法。PrepardStatement类有两大特点: 
  特点一:一个PreparedStatement的对象中包含的SQL声明是预编译的,因此当需要多次执行同一条SQL声明时,利用PreparedStatement传送这条SQL声明可以大大提高执行效率。 
  特点二:PreparedStatement的对象所包含的SQL声明中允许有一个或多个IN参数。创建类PreparedStatement的实例时,IN参数用“?”代替。在执行带参数的SQL声明前,必须对“?”进行赋值,为了对“?”赋值,PreparedStatement类中增添了大量的setXXX方法,完成对IN参数赋值。 
  ⑴创建PreparedStatement对象 
  与创建Statement类的实例方法类似,创建一个PreparedStatement类的对象也只需在建立连接后,调用Connection类中的方法 
  public abstract PreparedStatement prepareStatement(String sql) throws SQLException; 
  例 创建一个PreparedStatement的对象,其中包含一条带参数的SQL声明。 
  PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable(id,name) VALUES(?,?)"); 
  ⑵IN参数的赋值 
  PreparedStatement中提供了大量的setXXX方法对IN参数进行赋值。根据IN参数的SQL类型应选用合适的setXXX方法。 
  例 对上例,若需将第一个参数设为3,第二个参数设为XU,即插入的记录id=3,name="XU",可用下面的语句实现: 
  pstmt.setInt(1,3); 
  pstmt.setString(2,"XU"); 
除了setInt,setLong,setString,setBoolean,setShort,setByte等常见的方法外,PreparedStatement还提供了几种特殊的setXXX方法。 
  ①setNull(int ParameterIndex,int sqlType) 
  这个方法将参数值赋为Null。sqlType是在java.sql.Types中定义的SQL类型号。 
  例 语句 
  pstmt.setNull(1,java.sql.Types.INTEGER); 
将第一个IN参数的值赋成Null。 
  ②setUnicodeStream(int Index,InputStream x,int length); 
   setBinaryStream(int Index,inputStream x,int length); 
   setAsciiStream(int Index,inputStream x,int length); 
  当参数的值很大时,可以将参数值放在一个输入流x中,再通过调用上述三种方法将其赋于特定的参数,参数length表示输入流中字符串长度。 


    每一种数据库都会尽最大努力对预编译语句提供最大的性能优化.因为预编译语句有可能被重复调用.所以语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行.这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以直接执行.而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.比如: 
insert into tb_name (col1,col2) values ('11','22'); 
insert into tb_name (col1,col2) values ('11','23'); 
    即使是相同操作但因为数据内容不一样,所以整个个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次. 

    当然并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句. 

Code Fragment 1: 
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′"; 
stmt.executeUpdate(updateString); 
Code Fragment 2: 
PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? "); 
updateSales.setInt(1, 75); 
updateSales.setString(2, "Colombian"); 
updateSales.executeUpdate(); 

片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。 
    这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。 

 

 

ps. java中preparedstatement为什么可以防止sql注入

其实是预编译功能,用preparedstatement就会把sql的结构给数据库预编译。

SQL注入 攻 击 是利用是指利用 设计 上的漏洞,在目 标 服 务 器上运行 Sql语 句以及 进 行其他方式的 攻 击 ,
动态 生成 Sql语 句 时 没有 对 用 户输 入的数据 进 行 验证 是 Sql注入 攻 击 得逞的主要原因。
对 于 JDBC而言, SQL注入 攻 击 只 对 Statement有效, 对 PreparedStatement 是无效的, 这 是因 为 PreparedStatement 不允 许 在不同的插入 时间 改 变查询 的 逻辑结 构。
如 验证 用 户 是否存在的 SQL语 句 为 :
select count(*) from usertable where name='用 户 名 ' and pswd='密 码 '
如果在 用 户 名字段 中 输 入 ' or '1'='1' or '1'='1
或是在 密 码 字段 中 输 入 1' or '1'='1
将 绕过验证 ,但 这种 手段只 对 只 对 Statement有效, 对 PreparedStatement 无效。
PreparedStatement 相 对 Statement有以下 优 点:
1.防注入攻击
2.多次运行速度快
3.防止数据库缓冲区溢出
4.代 码 的可读性可维护性好

 

 

 

ps2. CallableStatement使用较少, 这里不做分析

posted @ 2017-03-06 21:17  WakamiyaShinobu  阅读(578)  评论(0编辑  收藏  举报