JDBC中的Statement和PreparedStatement的区别

以Oracle为例吧

Statement为一条Sql语句生成执行计划,
如果要执行两条sql语句
select colume from table where colume=1;
select colume from table where colume=2;

会生成两个执行计划
一千个查询就生成一千个执行计划!

PreparedStatement用于使用绑定变量重用执行计划
select colume from table where colume=:x;
通过set不同数据只需要生成一次执行计划,可以重用

是否使用绑定变量对系统影响非常大,生成执行计划极为消耗资源
两种实现 速度差距可能成百上千倍

1.PreparedStatement是预编译的,对于批量处理可以大大提高效率.也叫JDBC存储过程


2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement对象的开销比Statement大,对于一次性操作并不会带来额外的好处。


3.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得,preparedstatement支持批处理


4.
Code Fragment 1:

[java]  view plain  copy
  1. String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERECOF_NAME LIKE ′Colombian′";  
  2. stmt.executeUpdate(updateString);  
Code Fragment 2:
[java]  view plain  copy
  1. PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SETSALES = ? WHERE COF_NAME LIKE ? ");  
  2. updateSales.setInt(175);  
  3. updateSales.setString(2"Colombian");  
  4. updateSales.executeUpdate();  
片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句, 而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。 当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。


5.执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。

然而,在Oracle环境中,开发人员实际上有更大的灵活性。当使用Statement或PreparedStatement对象时,Oracle数据库会缓存SQL语句以便以后使用。在一些情况下,由于驱动器自身需要额外的处理和在Java应用程序和Oracle服务器间增加的网络活动,执行PreparedStatement对象实际上会花更长的时间。

然而,除了缓冲的问题之外,至少还有一个更好的原因使我们在企业应用程序中更喜欢使用PreparedStatement对象,那就是安全性。传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。

当处理公共Web站点上的用户传来的数据的时候,安全性的问题就变得极为重要。传递给PreparedStatement的字符串参数会自动被驱动器忽略。最简单的情况下,这就意味着当你的程序试着将字符串“D'Angelo”插入到VARCHAR2中时,该语句将不会识别第一个“,”,从而导致悲惨的失败。几乎很少有必要创建你自己的字符串忽略代码。

在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。
在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。

第一:
prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。
createStatement不会初始化,没有预处理,没次都是从0开始执行SQL

第二:
prepareStatement可以替换变量
在SQL语句中可以包含?,可以用ps=conn.prepareStatement("select* from Cust where ID=?");
int sid=1001;
ps.setInt(1, sid);
rs = ps.executeQuery();
可以把?替换成变量。
而Statement只能用
int sid=1001;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from Cust where ID="+sid);
来实现。

第三:
prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。
createStatement不会初始化,没有预处理,没次都是从0开始执行SQL


另一篇文章:

当初始的SQL查询被修改成另一个完全不同形式的查询的时候,就会出现SQL注入漏洞。执行这一被修改过的查询就可能会导致信息泄露或者数据被修改。防止SQL注入漏洞的主要方法是:净化并验证非受信输入,同时采用参数化查询的方法。

       //创建数据库连接的过程略
        String sql="select * from db_user where username = " + username + "and passord = " + password;
        Statement stmt = connect.createStatement();
        ResultSet rs = stmt.executeQuery(sql);

采用以上方法实现数据查询时,如果攻击者能够替代username和password中的任意字符串,它们可以使用类似下面这种方式实现SQL注入:

select * from db_user where username = '随意' and password = '' or '1' = '1';

因为’1’=’1’肯定成立,所以可以任何通过验证.更有甚者:把[‘;drop table tb_name;]作为password传入进来,则:

select * from db_user where username = '随意' and password = '';drop table tb_name;

有些数据库是不会让你成功的,但也有很多数据库就可以使这些语句得到执行。而如果你使用预编译语句。你传入的任何内容就不会和原来的语句发生任何匹配的关系。(前提是数据库本身支持预编译,但上前可能没有什么服务端数据库不支持编译了,只有少数的桌面数据库,就是直接文件访问的那些)只要全使用预编译语句,你就用不着对传入的数据做任何过滤。而如果使用普通的statement,有可能要对drop,等做费尽心机的判断和过滤。

改进方法:

        //创建数据库连接的过程略
        String sql="select * from db_user where username = ? and passord = ?";
        PreparedStatement stmt = connect.preparedStatement();
        stmt.setString(1,username);
        stme.setString(2,password);
        ResultSet rs = stmt.executeQuery(sql);

通过使用PreparedStatement类的set*()方法,可以进行强类型检查。这样可以减少SQL注入漏洞。

一、分情况使用Statement和PreparedStatement对象

JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 如果SQL语句被多次执行选用PreparedStatement是最好的.

PreparedStatement的第一次执行消耗是很高的. 它的性能体现在后面的重复执行. 使用PreparedStatement的方式来执行一个针对数据库表的查询. JDBC驱动会发送一个网络请求到数据解析和优化这个查询. 而执行时会产生另一个网络请求. 在JDBC驱动中,减少网络通讯是最终的目的. 如果我的程序在运行期间只需要一次请求, 那么就使用Statement. 对于Statement, 同一个查询只会产生一次网络到数据库的通讯.

对于使用PreparedStatement池的情况下,当使用PreparedStatement池时, 如果一个查询很特殊, 并且不太会再次执行到, 那么可以使用Statement. 如果一个查询很少会被执行,但连接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的同样情况下, 请使用Statement.

二、PreparedStatement的Batch功能

Update大量的数据时, 先构建一个INSERT语句再多次的执行, 会导致很多次的网络连接.。要减少JDBC的调用次数改善性能, 可以使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库。

初始实现:

PreparedStatement ps = conn.prepareStatement(  
   "INSERT into db_user values (?, ?, ?)");  

for (n = 0; n < 100; n++) {  

  ps.setString(name[n]);  
  ps.setLong(id[n]);  
  ps.setInt(salary[n]);  
  ps.executeUpdate();  
}  

改进实现:

//使用Batch功能
PreparedStatement ps = conn.prepareStatement(  
   "INSERT into db_user values (?, ?, ?)");  

for (n = 0; n < 100; n++) {  

  ps.setString(username[n]);  
  ps.setString(password[n]);   
  ps.addBatch();  
}  
ps.executeBatch();

在初始实现中, PreparedStatement被用来多次执行INSERT语句。 在这里,执行了100次INSERT操作, 共有101次网络往返。 其中,1次往返是预储statement, 另外100次往返执行每个迭代。 在改进实现中, 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返。 1次往返是预储statement, 另一次是执行batch命令。虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返,性能得到提高。 记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯。

注:Oracel 10G的JDBC Driver限制最大Batch size是16383条,如果addBatch超过这个限制,那么executeBatch时就会出现“无效的批值”(Invalid Batch Value) 异常。因此在如果使用的是Oracle10G,在此bug减少前,Batch size需要控制在一定的限度。


  • 11
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值