statement 、prepareStatement的用法和解释

prepareStatement 的用法和解释

1.PreparedStatement是预编译的,对于批量处理能够大大提升效率. 也叫JDBC存储过程java

2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement 对象的开销比Statement大,对于一次性操做并不会带来额外的好处。
3.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得, preparedstatement支持批处理mysql

四、sql

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语句是否执行了屡次,并且两次之间的差异仅仅是变量的不一样。若是仅仅执行了一次的话,它应该和普通的对象毫无差别,体现不出它预编译的优越性。安全

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提交到数据库中进行预处理,屡次使用可提升效率。  
Statement不会初始化,没有预处理,没次都是从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);  

来实现。

深刻理解statement 和prepareStatement

一、使用Statement而不是PreparedStatement对象
JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement仍是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 若是SQL语句被屡次执行选用PreparedStatement是最好的.

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

对于使用PreparedStatement池的状况下, 本指导原则有点复杂. 当使用PreparedStatement池时, 若是一个查询很特殊, 而且不太会再次执行到, 那么可使用Statement. 若是一个查询不多会被执行,但链接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的一样状况下, 请使用Statement.

二、使用PreparedStatement的Batch功能
Update大量的数据时, 先Prepare一个INSERT语句再屡次的执行, 会致使不少次的网络链接. 要减小JDBC的调用次数改善性能, 你可使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库. 例如, 让咱们来比较一下下面的例子.

例 1: 屡次执行PreparedStatement,屡次数据库请求(网络请求)

PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)");  
for (n = 0; n < 100; n++) {  
  ps.setString(name[n]);  
  ps.setLong(id[n]);  
  ps.setInt(salary[n]);  
  ps.executeUpdate();  
} 

 例 2: 使用Batch,以此请求执行多条

PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {   
	ps.setString(name[n]);  
	ps.setLong(id[n]);  
	ps.setInt(salary[n]);  
	ps.addBatch();  
}  
ps.executeBatch();  

  在例 1中, PreparedStatement被用来屡次执行INSERT语句. 在这里, 执行了100次INSERT操做, 共有101次网络往返.

   其中,1次往返是预储PreparedStatement, 另外100次往返执行每一个迭代.

在例2中, 当在100次INSERT操做中使用addBatch()方法时, 只有两次网络往返.

   1次往返是预储PreparedStatement, 另外一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 可是经过减小网络往返,性能获得提升.记住, JDBC的性能最大的增进是减小JDBC驱动与数据库之间的网络通信.次数


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

       一样MySQL 5.5.28 批量执行的数据最大限度是多少不清楚,但本身试了1w,2w,3w 都没问题,记得在url 后面添加:rewriteBatchedStatements=true 表示批量插入,若是不添加的话即便使用addbatch() ,executeBatch() 在后台入库的地方仍是不会一次请求入库而是屡次请求入库。

三、选择合适的光标类型
的光标类型以最大限度的适用你的应用程序. 本节主要讨论三种光标类型的性能问题.
对于从一个表中顺序读取全部记录的状况来讲, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪一种方法比使用Forward-Only型的光标更快. 但无论怎样, 当程序中必须按无次序的方式处理数据行时, 这种光标就没法使用了.

对于程序中要求与数据库的数据同步以及要可以在结果集中先后移动光标, 使用JDBC的Scroll-Insensitive型光标是较理想的选择. 此类型的光标在第一次请求时就获取了全部的数据(当JDBC驱动采用'lazy'方式获取数据时或许是不少的而不是所有的数据)而且储存在客户端. 所以, 第一次请求会很是慢, 特别是请求长数据时会理严重. 而接下来的请求并不会形成任何网络往返(当使用'lazy'方法时或许只是有限的网络交通) 而且处理起来很快. 由于第一次请求速度很慢, Scroll-Insensitive型光标不该该被使用在单行数据的获取上. 当有要返回长数据时, 开发者也应避免使用Scroll-Insensitive型光标, 由于这样可能会形成内存耗尽. 有些Scroll-Insensitive型光标的实现方式是在数据库的临时表中缓存数据来避免性能问题, 但多数仍是将数据缓存在应用程序中.

Scroll-Sensitive型光标, 有时也称为Keyset-Driven光标, 使用标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会从新该标识符的数据. 由于每次请求都会有网络往返, 性能可能会很慢. 不管怎样, 用无序方式的返回结果行对性能的改善是没有帮助的.

如今来解释一下这个, 来看这种状况. 一个程序要正常的返回1000行数据到程序中. 在执行时或者第一行被请求时, JDBC驱动不会执行程序提供的SELECT语句. 相反, 它会用键标识符来替换SELECT查询, 例如, ROWID. 而后修改过的查询都会被驱动程序执行,跟着会从数据库获取全部1000个键值. 每一次对一行结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 而后构造一个包含了'WHERE ROWID=?'子句的最佳化查询, 再接着执行这个修改过的查询, 最后从服务器取得该数据行.

当程序没法像Scroll-Insensitive型光标同样提供足够缓存时, Scroll-Sensitive型光标能够被替代用来做为动态的可滚动的光标. 

四、使用有效的getter方法
JDBC提供多种方法从ResultSet中取得数据, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 这是由于JDBC驱动必须对要取得的值的类型做额外的处理以映射为特定的对象. 因此就对特定的数据类型使用相应的方法.

要更进一步的改善性能, 应在取得数据时提供字段的索引号, 例如, getString(1), getLong(2), 和getInt(3)等来替代字段名. 若是没有指定字段索引号, 网络交通不会受影响, 但会使转换和查找的成本增长. 例如, 假设你使用getString("foo") ... JDBC驱动可能会将字段名转为大写(若是须要), 而且在到字段名列表中逐个比较来找到"foo"字段. 若是能够, 直接使用字段索引, 将为你节省大量的处理时间.

例如, 假设你有一个100行15列的ResultSet, 字段名不包含在其中. 你感兴趣的是三个字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (长整型), 和SALARY (整型). 若是你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查询旱每一个字段名必须被转换为metadata中相对应的大小写, 而后才进行查找. 若是你使用getString(1), getLong(2), 和getInt(15). 性能就会有显著改善.

五、获取自动生成的键值
有许多数据库提供了隐藏列为表中的每行记录分配一个惟一键值. 很典型, 在查询中使用这些字段类型是取得记录值的最快的方式, 由于这些隐含列一般反应了数据在磁盘上的物理位置. 在JDBC3.0以前, 应用程序只可在插入数据后经过当即执行一个SELECT语句来取得隐含列的值.

例 3: JDBC3.0以前

//插入行  
int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')");  
// 如今为新插入的行取得磁盘位置 - rowid  
ResultSet rs = stmt.executeQuery("select rowid from LocalGeniusList where name = 'Karen'");  

这种取得隐含列的方式有两个主要缺点. 第一, 取得隐含列是在一个独立的查询中, 它要透过网络送到服务器后再执行. 第二, 由于不是主键, 查询条件可能不是表中的惟一性ID. 在后面一个例子中, 可能返回了多个隐含列的值, 程序没法知道哪一个是最后插入的行的值.

(译者:因为不一样的数据库支持的程度不一样,返回rowid的方式各有差别。在SQL Server中,返回最后插入的记录的id能够用这样的查询语句:SELECT @IDENTITY )

JDBC3.0规范中的一个可选特性提供了一种能力, 能够取得刚刚插入到表中的记录的自动生成的键值. 

例 4: JDBC3.0以后

int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')",  
// 插入行并返回键值  
Statement.RETURN_GENERATED_KEYS);  
ResultSet rs = stmt.getGeneratedKeys ();  
// 获得生成的键值  

如今, 程序中包含了一个惟一性ID, 能够用来做为查询条件来快速的存取数据行, 甚至于表中没有主键的状况也能够.

这种取得自动生成的键值的方式给JDBC的开发者提供了灵活性, 而且使存取数据的性能获得提高.

六、选择合适的数据类型
接收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 一般是一种压缩格式. 数据必须被解压和转换到另外种格式, 这样它才能被数据的协议处理.

七、获取ResultSet
因为数据库系统对可滚动光标的支持有限, 许多JDBC驱动程序并无实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 不然不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 由于JDBC驱动程序模拟了可滚动光标, 调用rs.last()致使了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你能够用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来获得数据行数.

一般状况下,请不要写那种依赖于结果集行数的代码, 由于驱动程序必须获取全部的数据集以便知道查询会返回多少行数据.
 

 

preparestatement 防止sql注入

在JDBC应用中,若是你已是稍有水平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任什么时候候都不要使用Statement.基于如下的缘由:

一、代码的可读性和可维护性

虽然用PreparedStatement来代替Statement会使代码多出几行,但这样的代码不管从可读性仍是可维护性上来讲.都比直接用Statement的代码高不少档次:

stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();


不用我多说,对于第一种方法.别说其余人去读你的代码,就是你本身过一段时间再去读,都会以为伤心.

二、PreparedStatement尽最大可能提升性能

每一种数据库都会尽最大努力对预编译语句提供最大的性能优化.由于预编译语句有可能被重复调用.因此语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不须要编译,只要将参数直接传入编译过的语句执行代码中(至关于一个涵数)就会获得执行.这并非说只有一个 Connection中屡次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任什么时候候就能够不须要再次编译而能够直接执行.而statement的语句中,即便是相同一操做,而因为每次操做的数据不一样因此使整个语句相匹配的机会极小,几乎不太可能匹配.好比:insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');即便是相同操做但由于数据内容不同,因此整个个语句自己不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.
固然并非因此预编译语句都必定会被缓存,数据库自己会用一种策略,好比使用频度等因素来决定何时再也不缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句.

三、最重要的一点是极大地提升了安全性

即便到目前为止,仍有一些人连基本的恶义SQL语法都不知道

String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";

若是咱们把[' or '1' = '1]做为varpasswd传入进来.用户名随意,看看会成为何?
 

select * from tb_name = '随意' and passwd = '' or '1' = '1';

由于'1'='1'确定成立,因此能够任何经过验证.

更有甚者:把[';drop table tb_name;]做为varpasswd传入进来

select * from tb_name = '随意' and passwd = '';drop table tb_name;

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


上面的几个缘由,还不足让你在任什么时候候都使用PreparedStatement吗?

总结: 上面是三篇文章,三篇文章详细介绍了statement 和preparestatement 两个对象的使用以及效率、安全问题。在实际项目中若是可以使用preparestatement  仍是建议使用preparestatement  缘由有3:

1)、上面说了 若是sql中只有数值在变则效率高

2)、preparestatement 具备防sql注入

3)、代码可读性比较好

实例:下面这个比喻很好,很明确的说明了批量添加,而且从中也能够看出在批量添加的时候PreparedStatement为何比Statement快的缘由~

Statement和PreparedStatement的区别就很少废话了,直接说PreparedStatement最重要的addbatch()结构的使用.

PreparedStatement  的addBatch和executeBatch实现批量添加

1.创建连接   

Connection connection = getConnection();

2.不自动 Commit (瓜子不是一个一个吃,所有剥开放桌子上,而后一口舔了)

connection.setAutoCommit(false);

3.预编译SQL语句,只编译一回哦,效率高啊.(发明一个剥瓜子的方法,之后不要总想怎么剥瓜子好.就这样剥.)

PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");  

 4.来一个剥一个,而后放桌子上

//记录1
statement.setInt(1, 1); 
statement.setString(2, "Cujo"); 
statement.addBatch();   

//记录2
statement.setInt(1, 2); 
statement.setString(2, "Fred"); 
statement.addBatch();   

//记录3
statement.setInt(1, 3); 
statement.setString(2, "Mark"); 
statement.addBatch();   

//批量执行上面3条语句. 一口吞了,很爽
int [] counts = statement.executeBatch();   

//Commit it 咽下去,到肚子(DB)里面
connection.commit();



statement 对象的addBatch 和 executeBatch 来实现批量添加
stmt.addBatch("update  TABLE1 set 题目="盛夏话足部保健1"   where id="3407"");
stmt.addBatch("update  TABLE1 set 题目="夏季预防中暑膳食1" where id="3408""); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("11","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("12","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("13","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("14","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("15","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("16","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("17","12","13","","")"); 
stmt.addBatch("INSERT INTO  TABLE1  VALUES("18","12","13","","")"); 

int [] updateCounts=stmt.executeBatch(); 
cn.commit();

实例:批量添加

public static void insertData(List<Map<String,String>> list,Logger log){  
    //获取的数据  
    List <Map<String,String>> nlist= list;  
    String upsql="update   hrd_staff  set position =?  where id=?";  
    Iterator<Map<String,String>> iter= nlist.iterator();  
    Connection con= Utils.getCon();  
    int count=0;  
    try {  
        //在皮脸添加的时候注意事务提交方式  
        con.setAutoCommit(false);  
        //PreparedStatement方法的使用  
        PreparedStatement pstm = con.prepareStatement(upsql);  
        while(iter.hasNext()){  
            count++;  
            Map<String,String> map= iter.next();  
            String jon_name= map.get("job_name");  
            String uid= map.get("uid");  
            pstm.setString(1,jon_name);  
            pstm.setString(2,uid);  
            //添加到缓存中  
            pstm.addBatch();  
            // 若是数据量很大,不能一次性批量添加因此咱们要分批次添加,这里就是300条一次  
            if(count%300==0){  
                //持久化  
                int []res=pstm.executeBatch();  
                //提交事务,持久化数据  
                con.commit();  
                pstm.clearBatch();  
                log.info("300整除插入结果: "+res.length);  
            }  
        }  
        //小于300条的在这里持久化  
        int []ress= pstm.executeBatch();  
        //事务提交持久化  
        con.commit();  
        pstm.clearBatch();  
        log.info("插入数据结果:"+ress.length);  
    } catch (SQLException e) {  
        try {  
            con.rollback();  
        } catch (SQLException e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        }  
        e.printStackTrace();  
    }finally{  
        try {  
            if(null!=con){  
            con.close();  
            con.setAutoCommit(true);  
            }  
        } catch (SQLException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
}

 

这里除了下面说的url中的批量设置外,咱们也要注意事务的设置,不能设置为自动提交,要批量添加后在提交事务 

总结:

addBatch() 就是把你的处理内容添加到批处理单元中。即添加到了batch中。你能够循环加入不少,数据库都不会处理,直到调用以下代码executeBatch() 此时,数据库把刚才加到batch中的命令批量处理。

 

使用批量插入的好处: , 当在100次INSERT操做中使用addBatch()方法时, 只有两次网络往返. 1次往返是预储statement, 另外一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 可是经过减小网络往返,性能获得提升. 记住, JDBC的性能最大的增进是减小JDBC驱动与数据库之间的网络通信. 若是没有使用批处理则网络往返101次这样会耗不少时间,天然效率也就通常

 

这里要注意:在mysql 下使用批量执行的时候要在,url 后面添加手动设置支持批量添加 实例以下:

 String url="jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";

// 默认状况下rewriteBatchedStatements 的值为false 也就是批量添加功能是关闭的,若是使用则要手动开启!

还有就是事务的设置,不能使自动提交,要批量添加后才提交!!!

相关文章
相关标签/搜索