5.3 使用PreparedStatement
PreparedStatement接口扩展了Statement,添加了为语句中包含的参数标记设置值的功能。
PreparedStatement对象表示已经预编译的SQL语句,这样SQL语句只需要编译一次就可以被执行多次。参数标记(由SQL字符串中的“?”表示)用于指定语句的输入值,这些值可能在运行时发生变化。
5.3.1 创建PreparedStatement对象
PreparedStatement的实例以与Statement对象相同的方式创建,除了在创建语句时提供SQL命令:
Connection conn = ds.getConnection(user, passwd); PreparedStatement ps = conn.prepareStatement("INSERT INTO BOOKLIST" + "(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)");
与createStatement一样,方法prepareStatement定义了一个构造函数,该构造函数可用于指定由该PreparedStatement生成的结果集的特征。
Connection conn = ds.getConnection(user, passwd); PreparedStatement ps = conn.prepareStatement( "SELECT AUTHOR, TITLE FROM BOOKLIST WHERE ISBN = ?", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
PreparedStatement接口定义setter方法,这些方法用于替换预编译SQL字符串中每个参数标记的值。方法的名称遵循set模式。
例如,方法setString用于指定期望字符串的参数标记的值。这些setter方法中的每一个至少采用两个参数:第一个始终是一个Int值,等于要设置的参数的序号位置从1开始;第二个和任何剩余参数指定要分配给参数的值。
PreparedStatement ps = conn.prepareStatement("INSERT INTO BOOKLIST" + "(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)"); ps.setString(1, "Way, Lau"); ps.setString(2, "We"); ps.setLong(3, 140185852L);
必须为PreparedStatement对象中的每个参数标记提供一个值,然后才能执行它。如果没有为参数标记提供值,那么用于执行PreparedStatement对象(executeQuery、executeUpdate和execute)的方法将抛出SQLException。
为PreparedStatement对象的参数标记设置的值在执行时不会重置,可以调用clearParameters方法来显式清除已设置的值。使用不同的值设置参数将使用新值替换先前的值。
5.3.2 为什么使用PreparedStatement
建议开发者始终以PreparedStatement代替Statement,换言之,在任何时候都不要直接使用Statement。PreparedStatement具有以下优势:
1.提升代码的可读性和可维护性
虽然用PreparedStatement代替Statement会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说都比直接用Statement的代码档次高。
2.提高性能
PreparedStatement实例包含已编译的SQL语句,这就是使语句“准备好”。包含于PreparedStatement对象中的SQL语句可具有一个或多个IN参数。IN参数的值在SQL语句创建时未被指定。相反,该语句为每个IN参数保留一个问号(?)作为参数标记。每个问号的值必须在该语句执行之前通过适当的set方法来提供。由于PreparedStatement对象已预编译过,其执行速度要快于Statement对象,因此多次执行的SQL语句经常创建为PreparedStatement对象以提高效率。
3.提高安全性
即使到目前为止,仍有一些人连基本的SQL语法都不知道,比如下面的例子:
String sql ="select * from tb_name where name= '" + varname+"' and passwd='"+ varpasswd+"'";
如果我们把“' or '1' = '1”作为varpasswd传入进来,用户名随意,则最终语句如下:
select*from tb_name ='t_user'and passwd =''or'1'='1';
因为'1'='1'肯定成立,所以这个语句肯定能通过验证,而不管用户名和密码是否合法。更有甚者把“;drop table tb_name;”作为varpasswd传入进来,SQL语句将变成:
select*from tb_name ='t_user'and passwd ='';droptable tb_name;
上面就是SQL注入的例子。
而如果使用PreparedStatement,那么传入的任何内容都不会和原来的语句发生任何匹配的关系。只要全部使用PreparedStatement语句,就不用对传入的数据做任何过滤。而如果使用普通的Statement,就需要额外做很多防SQL注入的工作。