轻量级Java EE企业应用开发实战
上QQ阅读APP看书,第一时间看更新

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注入的工作。