2.4.2 使用PreparedStatement对象执行动态SQL
动态SQL实际上就是带参数的SQL。通过PreparedStatement对象可以执行动态的SQL。由于动态SQL没有参数名,只有参数索引,因此,PreparedStatement接口的getXxx方法和setXxx方法只能通过参数索引来确定参数。PreparedStatement对象和Statement对象的使用方法类似,所不同的是Connection对象使用prepareStatement方法创建PreparedStatement对象。在创建PreparedStatement对象时,必须使用prepareStatement方法指定一个动态SQL。下面是一个执行动态SQL语句的例子,代码如下:
public class DynamicSQL
{
public static void main(String[] args) throws Exception
{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost/mydb?characterEncoding=UTF8",
"root", "1234");
String selectData = "SELECT name, author FROM t_books WHERE id = ?";
// 获得PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(selectData);
pstmt.setInt(1, 1); // 赋参数值
ResultSet rs = pstmt.executeQuery();
// 输出返回的结果集
while (rs.next())
{
System.out.println("书名:" + rs.getString("name"));
System.out.println("作者:" + rs.getString("author"));
System.out.println("---------------------");
}
pstmt.close(); // 关闭PreparedStatement对象
conn.close();
}
}
既然有了Statement对象,为什么JDBC又要引入PreparedStatement呢?其主要的原因有如下四点:
1. 提高代码可读性和可维护性
虽然使用PreparedStatement对象从表面上看要比使用Statement对象多好几行代码,但如果使用的SQL是通过字符串连接生成的,那么使用Statement对象 的代码的可读性就会变得很差,如下面代码如示:
使用Statement对象的代码
stmt.executeQuery("SELECT id, name, isbn, author,price FROM t_books WHERE id >" + id + " and name like '%" + subname + "%' and author = '" + author + "'");
使用PreparedStatement对象的代码
pstmt = conn.prepareStatement ("SELECT id, name, isbn, author,price FROM t_books WHERE id >? and name like ? and author = ?");
pstmt.setString(1, id);
pstmt.setString(2, subname);
pstmt.setString(3, author);
pstmt.executeQuery();
从上面的代码可以看出,使用PreparedStatment对象的代码虽然多了几行,但显得更整洁。
2. 有助于提高性能
由于PreparedStatement对象在创建时就指定了动态的SQL,因此,这些SQL被DBMS编译后缓存了起来,等下次再执行相同的预编译语句时,就无需对其再编译,只需将参数值传入即可执行。由于动态SQL使用了“?”作为参数值占位符,因此,预编译语句的匹配几率要比Statement对象所使用的SQL语句大的多,所以,在多次调用同一条预编译语句时,PreparedStatement对象的性能要比Statement对象高得多。
3. 提高可复用性
动态SQL和存储过程十分类似,可以只写一次,然后只通过传递参数进行多次调用。这在执行需要不同条件的SQL时非常有用。
4. 提高安全性
在前面的章节讲过,execute、executeQuery和executeUpdate方法都可以执行多条SQL语句。那么这就存在一个安全隐患。如果使用Statement对象,并且SQL通过变量进行传递,就可能会受到SQL注入攻击,看下面的代码:
String author = "阿斯利森";
String selectData = "SELECT * FROM jdbcdemo.t_books where author = '" + author + "'";
stmt.executeQuery(selectData);
上面的代码并没有任何问题,但如果将author的值改为如下的字符串,就会在执行完SELECT语句后,将t_booksale删除。
"';drop table jdbcdemo.t_booksale;";
而如果使用PreparedStatement对象,就不会发生这样的事情。因此,在程序中应尽量使用PreparedStatement对象,并且在连接数据库时,除非必要,否则在连接字符串中不要加“allowMultiQueries=true”来打开执行多条SQL语句的功能。