PreparedStatement,是
java.sql包中的接口,继承了
Statement,并与之在两方面有所不同,包含已编译的 SQL 语句。
技术原理
该 PreparedStatement
接口继承Statement,并与之在两方面有所不同:
PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为
占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
由于 PreparedStatement 对象已
预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
作为 Statement 的子类,PreparedStatement 继承了 Statement 的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数
占位符的值。同时,
三种方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要参数。这些方法的 Statement 形式(接受 SQL 语句参数的形式)不应该用于 PreparedStatement 对象。
创建对象
以下的
代码段(其中 con 是 Connection 对象)创建包含带两个 IN 参数占位符的 SQL 语句的 PreparedStatement 对象:
传递参数
在执行 PreparedStatement 对象之前,必须设置每个 ? 参数的值。这可通过调用 setXXX 方法来完成,其中 XXX 是与该参数相应的类型。例如,如果参数具有
Java 类型 long,则使用的方法就是 setLong。setXXX 方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为 123456789,第二个参数设为 100000000:
pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);
一旦设置了给定语句的
参数值,就可用它多次执行该语句,直到调用clearParameters 方法清除它为止。在连接的缺省模式下(启用自动提交),
当语句完成时将自动提交或还原该语句。
如果基本数据库和
驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个 PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用PreparedStatement 对象代替 Statement 对象来提高性能是没有意义的。
利用 pstmt(前面创建的 PreparedStatement 对象),以下代码例示了如何设置两个参数
占位符for (int i = 0; i < 10; i++) {
pstmt.setInt(2, i);
int rowCount = pstmt.executeUpdate();
}
参数的类型
setXXX 方法中的 XXX 是 Java 类型。它是一种隐含的 JDBC 类型(一般 SQL 类型),因为驱动程序将把 Java 类型映射为相应的 JDBC 类型(遵循该 JDBCGuide中§8.6.2 “映射 Java 和 JDBC 类型”表中所指定的映射),并将该 JDBC 类型发送给数据库。例如,以下
代码段将 PreparedStatement 对象 pstmt 的第二个参数设置为 44,Java 类型为 short:
pstmt.setShort(2, 44);
驱动程序将 44 作为 JDBC
SMALLINT 发送给数据库,它是 Java short 类型的
标准映射。
程序员的责任是确保将每个 IN 参数的 Java 类型映射为与数据库所需的 JDBC
数据类型兼容的 JDBC 类型。不妨考虑数据库需要 JDBC SMALLINT 的情况。如果使用方法 setByte ,则
驱动程序将 JDBC
TINYINT 发送给数据库。这是可行的,因为许多数据库可从一种相关的
类型转换为另一种类型,并且通常 TINYINT 可用于SMALLINT 适用的任何地方。
应用示例
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字符串中包含问号(?),这些问号标明变量的位置,然后提供变量的值,最后
执行语句,例如:
preparedstatement ps = connection.preparestatement(sql);
ps.setint(1,id);
ps.setstring(2,name);
resultset rs = ps.executequery();
使用preparedstatement的另一个优点是
字符串不是动态创建的。下面是一个动态创建字符串的例子:
这允许jvm(javavirtual machine,
java虚拟机)和驱动/数据库缓存语句和字符串并提高性能。prepared
statement也提供数据库无关性。当显示声明的sql越少,那么潜在的
sql语句的数据库
依赖性就越小。由于preparedstatement具备很多优点,开发者可能通常都使用它,只有在完全是因为性能原因或者是在一行sql语句中没有变量的时候才使用通常的statement。一个完整的preparedstatement的例子:
package jstarproject;
import java.sql.*;
public class mypreparedstatement {
public mypreparedstatement()
{
}
public void query() throws sqlexception{
connection conn = this.getconnection();
preparedstatement pstmt = conn.preparestatement(strsql);
resultset rs = pstmt.executequery();
while(rs.next()){
}
rs.close();
pstmt.close();
conn.close();
}
private connection getconnection() throws sqlexception{
// class.
connection conn = null;
try {
class.forname(db_driver);
}
catch (classnotfoundexception ex) {}
return conn;
}
//main
public static void main(
string[] args) throws sqlexception {
mypreparedstatement jdbctest1 = new mypreparedstatement();
jdbctest1.query();
}
}
优点
在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任何时候都不要使用Statement.
基于以下的原因:
虽然用PreparedStatement来代替Statement会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说.都比直接用Statement的代码高很多档次:
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
不用我多说,对于第一种方法.别说其他人去读你的代码,就是你自己过一段时间再去读,都会觉得伤心.
二.PreparedStatement尽
最大可能提高性能.
每一种数据库都会尽最大努力对
预编译语句提供最大的
性能优化.因为预编译语句有可能被重复调用.所以语句在被DB的
编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行.这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以直接执行.而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.比如:
insertintotb_name(col1,col2)values('11','22');
insertintotb_name(col1,col2)values('11','23');
即使是相同操作但因为数据内容不一样,所以整个个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.
当然并不是所有预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句.
三.最重要的一点是极大地提高了安全性.
如果我们把['or'1'='1]作为varpasswd传入进来.
用户名随意,看看会成为什么?
select*fromtb_name='随意'andpasswd=''or'1'='1';
因为'1'='1'肯定成立,所以可以任何通过验证.更有甚者:
把[';droptabletb_name;]作为varpasswd传入进来,则:
select*fromtb_name='随意'andpasswd='';droptabletb_name;有些数据库是不会让你成功的,但也有很多数据库就可以使这些语句得到执行.
而如果你使用预编译语句.你传入的任何内容就不会和原来的语句发生任何匹配的关系.只要全使用预编译语句,你就用不着对传入的数据做任何过虑.而如果使用普通的statement,有可能要对drop,;等做费尽心机的判断和过虑.
服务器
J2EE服务器
PreparedStatement和J2EE服务器,当我们使用J2EE服务器的时候,事情会变得更加复杂.
通常情况下,一个预先准备好的语句(preparedstatement)是和一个单独的数据库连接相关联的.当连接关闭时,语句就被丢弃了.一般来说,一个
胖客户端应用程序在得到一个数据库连接后会一直保持到
程序结束.它会使用两种方法创建所有的语句:急切创建(eagerly)或者懒惰创建(
lazily). Eagerly是说,当程序启动时全部创建.Lazily是说随用随创建.急切的方法会在程序启动时有些延时,但是一旦程序启动以后,运行很好.懒惰的方法启动很快,但是当程序
运行时,预先准备的语句在第一次使用是创建.这就会造成性能
不平衡,知道所有的语句都准备好了,但是最终程序会和急切方法一样快.哪一种最好要看你需要的是
快速启动还是均衡的性能.
一个J2EE应用程序所带来的问题就是它不能像这样工作.它只在一个请求的
生存时间中保持一个连接.这意味着在他处理每一个请求时都会重新创建语句,就不象胖客户端只创建一次,而不是每个请求都创建那样有效,当J2EE服务器给你的程序一个连接时,并不是一个真正的连接,而是一个经过包装的.你可以通过查看那个连接的类的名字来检验一下.它不是一个数据库的JDBC连接,是你的服务器创建的一个类.通常,如果你调用一个连接的
close方法,那么
jdbc驱动程序会关闭这个连接.我们希望的是当J2EE应用程序调用close的时候,连接会返回到
连接池中.我们通过设计一个代理的jdbc连接类来做这些,但看起来就象是实际的连接.当我们调用这个连接的任何方法时,代理类就会把请求前递给实际的连接.
但是,当我们调用类似close的方法时,并不调用实际连接的close方法,只是简单地把连接返回给连接池,然后把
代理连接标记为无效,这样当它被应用程序重新使用时,我们会得到异常.包装是非常有用的,因为它帮助J2EE应用程序服务器实现者比较聪明地加上预先准备语句的支持.当程序调用Connection.prepareStatement时,由
驱动程序返回一个PreparedStatement对象.当应用程序得到它时,保存这个句柄,并且在请求完成时,关闭请求之前关闭这个句柄.但是,在连接返回到连接池之后,以后被同样或者另一个应用程序重用时,那么,我们就理论上希望同样的PreparedStatement返回给应用程序.
J2EEPreparedStatement缓冲J2EEPreparedStatement缓冲由J2EE服务器内部的连接池管理器使用一个缓冲区来实现.J2EE服务器在连接池中保存一个所有数据库的预先准备语句的一个列表.当一个程序调用一个连接的prepareStatement方法时,服务器先检查这个语句是否已经有了,如果是,相应的PreparedStatement就在缓冲区内,就返回给应用程序,如果不是,请求就会传递给jdbc驱动程序,请求/预先准备语句对象就会加入到缓冲区里.对于每一个连接我们需要一个缓冲区,因为这是jdbc驱动程序的工作要求. 任何返回的preparedStatement都是针对这个连接的.如果我们要利用缓冲区的优势,要使用和前面相同的规则.我们需要使用参数话的查询,这样它们就会和已经在缓冲区的某一个匹配.大多数应用程序服务器都允许你调整缓冲区的大小.
总结
总之, 对于预先准备语句,我们应该使用参数化的查询。这样允许数据库重用已经存在的访问方案,从而减轻数据库的负担。这样的缓冲区是这个数据库范围的,所以你可以安排你所有的应用程序,使用相似的参数化的 SQL,就会提高这样的缓冲区方案的效率,因为一个应用程序 可以使用另一个应用程序的语句。一个
应用服务器的优势也在于此,因为访问数据库的逻辑应该集中在
数据访问层上(OR映射,实体bean或者直接JDBC),最后, 预先准备语句的正确使用也让你利用应用程序服务器的预先准备语句的缓冲区的好处。提高你的应用程序的性能,因为应用程序通过对以前的预先准备语句的重用减少 JDBC 驱动程序调用的次数。这样使它能和
胖客户端的效率竞争,并且去掉了不能保持一个长期连接的坏处。如果你使用参数化的预先准备语句, 就可以提高数据库和你的
服务器端的代码的效率。这些提高都会允许你的应用程序提高性能。