Java 开发:PreparedStatement的用法

jdbc(java database connectivity,java数据库连接)的api中的主要的四个类之一的java.sql.statement要求开发者付出大量的时间和精力。

在使用statement获取jdbc访问时所具有的一个共通的问题是输入适当格式的日期和时间戳:2002-02-05 20:56 或者 02/05/02 8:56 pm。
通过使用java.sql.preparedstatement,这个问题可以自动解决。一个preparedstatement是从java.sql.connection对象和所提供的sql字符串得到的,sql字符串中包含问号(?),这些问号标明变量的位置,然后提供变量的值,最后执行语句,例如:
stringsql = "select * from people p where p.id = ? and p.name = ?";
preparedstatement ps = connection.preparestatement(sql);
ps.setint(1,id);
ps.setstring(2,name);
resultset rs = ps.executequery();
使用preparedstatement的另一个优点是字符串不是动态创建的。下面是一个动态创建字符串的例子:
stringsql = "select * from people p where p.i = "+id;

这允许jvm(javavirtual machine,java虚拟机)和驱动/数据库缓存语句和字符串并提高性能。
preparedstatement也提供数据库无关性。当显示声明的sql越少,那么潜在的sql语句的数据库依赖性就越小。
由于preparedstatement具备很多优点,开发者可能通常都使用它,只有在完全是因为性能原因或者是在一行sql语句中没有变量的时候才使用通常的statement。
一个完整的preparedstatement的例子:
package jstarproject;
import java.sql.*;
public class mypreparedstatement {
private final string db_driver="com.microsoft.jdbc.sqlserver.sqlserverdriver";
private final string url = "jdbc:microsoft:sqlserver://127.0.0.1:1433;databasename=pubs";
public mypreparedstatement()
{
}
public void query() throws sqlexception{
connection conn = this.getconnection();
string strsql = "select emp_id from employee where emp_id = ?";
preparedstatement pstmt = conn.preparestatement(strsql);
pstmt.setstring(1,"pma42628m");
resultset rs = pstmt.executequery();

while(rs.next()){
string fname = rs.getstring("emp_id");
system.out.println("the fname is " + fname);
}
rs.close();
pstmt.close();
conn.close();
}
private connection getconnection() throws sqlexception{
// class.
connection conn = null;
try {
class.forname(db_driver);
conn = drivermanager.getconnection(url,"sa","sa");
}
catch (classnotfoundexception ex) {}
return conn;
}
//main
public static void main(string[] args) throws sqlexception {
mypreparedstatement jdbctest1 = new mypreparedstatement();
jdbctest1.query();
}
}


为什么要始终使用PreparedStatement代替Statement?为什么要始终使用PreparedStatement代替Statement?


在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吗?

 

237人阅读 评论(1) 收藏 举报

PreparedStatement是如何大幅度提高性能的

作者: Billy Newport
本文讲述了如何正确的使用 prepared statements 。为什么它可以让你的应用程序运行的更快,和同样的让数据库操作变的更快。
为什么Prepared Statements非常重要?如何正确的使用它?
数据库有着非常艰苦的工作。它们接受来自众多并发的客户端所发出的 SQL 查询,并尽可能快的执行查询并返回结果。处理 statements 是一个开销昂贵的操作,不过现在有了 Prepared Statements 这样的方法,可以将这种开销降到最低。可是这种优化需要开发者来完成。所以本文会为大家展示如何正确的使用 Prepared Statements 才能使数据库操作达到最优化。
数据库是如何执行一个statement的?
显然,我不会在这里写出很多的细节,我们只关注最关键的部分。当一个数据库收到一个 statement 后,数据库引擎会先解析 statement ,然后检查其是否有语法错误。一旦 statement 被正确的解析,数据库会选出执行 statement 的最优途径。遗憾的是这个计算开销非常昂贵。数据库会首先检查是否有相关的索引可以对此提供帮助,不管是否会将一个表中的全部行都读出来。数据库对数据进行统计,然后选出最优途径。当决创建查询方案后,数据库引擎会将它执行。
存取方案( Access Plan )的生成会占用相当多的 CPU 。理想的情况是,当我们多次发送一个 statement 到数据库,数据库应该对 statement 的存取方案进行重用。如果方案曾经被生成过的话,这将减少 CPU 的使用率。
Statement Caches
数据库已经具有了类似的功能。它们通常会用如下方法对 statement 进行缓存。使用 statement 本身作为 key 并将存取方案存入与 statement 对应的缓存中。这样数据库引擎就可以对曾经执行过的 statements 中的存取方案进行重用。举个例子,如果我们发送一条包含 SELECT a, b FROM t WHERE c = 2 statement 到数据库,然后首先会将存取方案进行缓存。当我们再次发送相同的 statement 时,数据库会对先前使用过的存取方案进行重用,这样就降低了 CPU 的开销。
注意,这里使用了整个 statement key 。也就是说,如果我们发送一个包含 SELECT a, b FROM t WHERE c = 3 statement 的话,缓存中不会没有与之对应的存取方案。这是因为“ c=3 ”与曾经被缓存过的“ c=2 ”不同。所以,举个例子:
for (int i = 0; i < 1000; i++) {
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = " + i);
ResultSet rs = Ps.executeQuery();
rs.close();
ps.close();
}
在这里缓存不会被使用,因为每一次迭代都会发送一条包含不同 SQL 语句的 statement 给数据库。并且每一次迭代都会生成一个新的存取方案。现在让我们来看看下一段代码:
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = ?");
for (int i = 0; i < 1000; i++) {
ps.setInt(1, i);
ResultSet rs = ps.executeQuery();
rs.close();
ps.close();
}
这样就具有了更好的效率,这个 statement 发送给数据库的是一条带有参数“?”的 SQL 语句。这样每次迭代会发送相同的 statement 到数据库,只是参数“ c=? ”不同。这种方法允许数据库重用 statement 的存取方案,这样就具有了更好的效率。这可以让你的应用程序速度更快,并且使用更少的 CPU ,这样数据库服务器就可以为更多的人提供服务。
PreparedStatementJ2EE服务器
当我们使用 J2EE 服务器时事情会变的比较复杂。通常,一个 perpared statement 会同一个单独的数据库连接相关联。当数据库连接被关闭时 prepared statement 也会被丢弃。通常,一个胖客户端会获取一个数据库连接并将其一直保持到退出。它会用“饿汉” (eagerly) 或“懒汉” (lazily) 方式创建所有的 parepared statements 。“饿汉”方式会在应用启动时创建一切。“懒汉”方式意味着只有在使用的时候才去创建。“饿汉”方式会使应用程序在启动的时候梢有延迟,但一旦启动后就会运行的相当理想。“懒汉”方式使应用程序启动速度非常快(但不会做任何准备工作),当需要使用 prepared statement 的时候再去创建。这样,在创建全部 statement 的过程中,性能是非常不稳定的,但一旦创建了所有 statement 后,它会像“饿汉”式应用程序一样具有很好的运行效果。请根据你的需要来选择最好的方式,是快速启动?还是前后一致的性能。
J2EE 应用的问题是它不会像这样工作,连接只会在请求期间被保持。那意味着必须每一次请求的时候都创建 prepared statement 。这远没有胖客户端那种一直保持 prepared statement 的执行性能好。 J2EE 厂商已经注意到了这个问题,并且提供了连接池 (ConnectionPool) 以避免这种问题。
J2EE 服务器提供了一个连接给你的应用程序时,其实它并没有给你真正的数据库连接,你只是获得了一个包装器( Wrapper )。你可以去看看你所获得的连接的类名以证实这一点。它并不是一个 JDBC 连接,而是一个由应用服务器创建的类。所有的 JDBC 操作都会被应用服务器的连接池管理器所代理。所有的 JDBC ResultSets statements CallableStatements preparedStatements 等都会被包装并以一个“代理对象”( Proxy Object )的形式返回给应用程序。当你关闭了连接,这些对象会被标记为失效,并被垃圾回收器所回收。
通常,如果你对一个数据库连接执行 close ,那这个连接会被 JDBC 驱动程序关闭。但我们需要在 J2EE 服务器执行 close 的时候数据库连接会被返回连接池。我们可以创建一个像真正的连接一样的 JDBC Connection 代理类来解决这个问题。它有一个对真正连接的引用。当我们执行一个连接上的方法时,代理会将操作转给真正的连接。但是,当我们对一个连接执行 close 时,这个连接并不会关闭,而是会送回连接池,并可以被其他请求所使用。一个已被准备过的 prepared statement 也会因此而得到重用。
J2EE PreparedStatement Cache
J2EE 服务器的连接池管理器已经实现了缓存的使用。 J2EE 服务器保持着连接池中每一个连接准备过的 prepared statement 列表。当我们在一个连接上调用 preparedStatement 时,应用服务器会检查这个 statement 是否曾经准备过。如果是,这个 PreparedStatement 会被返回给应用程序。如果否,调用会被转给 JDBC 驱动程序,然后将新生成的 statement 对象存入连接缓存。
每个连接都有一个缓存的原因是因为: JDBC 驱动程序就是这样工作的。任何 prepared statement 都是由指定的连接所返回的。
如果我们想利用这个缓存的优势,那就如前面所说的,使用参数化的查询语句可以在缓存中找到曾经使用过的 statement 。大部分应用服务器允许你调整 prepared statements 缓存的大小。
摘要
我们绝对应该使用包含参数化的查询语句的 prepared statement 。这样数据库就会重用准备过的存取方案。缓存适用于整个数据库,所以,如果你安排所有的应用程序使用相同的参数化 SQL 语句,然后你的其他应用程序就可以重用被准备过的 prepared statement 。这是应用服务器的一个优势,因为所有的数据库操作都集中在数据库操作层( Database Access Layer ,包括 O/R 映射,实体 Bean JDBC 等)。
第二,正确的使用 prepared statement 也是利用 prepared statement 的缓存优势的关键。由于应用程序可以重用准备过的 prepared statement ,也就减少了调用 JDBC 驱动程序的次数,从而提高了应用程序的性能。这样就拥有了可以与胖客户端比肩的效率,却又不需要总维持一个连接。
使用参数化的 prepared statement ,你的应用程序会具有更好的性能。

 

你可能感兴趣的:(Java 开发:PreparedStatement的用法)