我们在上一章介绍了Statement接口的用法;开发人员通过使用Statement接口能够在数据库中查询、添加、修改和删除数据。为了进一步提高数据操作的性能和安全性,JDBC框架还提供了PreparedStatement接口。一般来说,如果需要反复执行同一条SQL语句的话(只是参数的值不同),PreparedStatement能够提供更好的性能。
PreparedStatement接口继承自Statement接口,因此,PreparedStatement支持Statement接口的大部分功能。与Statement接口不同的是,PreparedStatement具备以下优势。
PreparedStatement对象是从java.sql.Connection接口创建出来的。如下面的代码示例所示,开发人员需要使用Connection::prepareStatement()方法创建PreparedStatement对象。在调用该方法时,需要传入一个SQL语句;在该SQL语句中可以直接设置变量的取值,或者使用问号?作为占位符(Place Holder),其值可在后续操作中设置。
在这段代码示例中,我们首先创建了一个字符串对象query,表示查询语句。在查询语句的where子句(where clause)中,我们直接指定查询女学生(gender = 'female'),但是将分数的值留在后续确定。因此,在"grade > ?"的条件中使用了占位符"?"。
当创建出PreparedStatement对象后,这个对象代表的是已经编译了的查询语句(pre-compiled query statement)。我们在示例中执行了两个查询,分别将分数设置在90分和85分,时间设置在2020年。在这个过程中,PreparedStatement是可以重复使用的。在第一次查询时,我们通过调用setInt()方法设置了分数和年份。setInt()方法的第一个参数是查询语句中参数的序号。例如,setInt(1, 90)的意义是将查询语句中第一个参数的值设置为90;即将分数的值设置为90。setInt(2, 2020)的意义是将查询语句中第二个参数的值设置为2020。依次类推。参数的序号从1开始。
当运行第二个查询时,我们只需要修改分数的取值就可以了,因为年份的取值不变,已经在第一次的查询中设置为2020了。所以,在第二次查询中不需要再次设置。executeQuery()方法返回一个ResultSet对象,用于表示查询的结果。我们已在前一章介绍Statement接口时详细讲解了ResultSet对象的使用方法,此处不再累述。
...
// 查询成绩高于某一阈值的所有女学生的姓名
String query = "SELECT name FROM student where gender = 'female' and grade > ? and year = ? ";
try (PreparedStatement stmt = con.prepareStatement(query)) {
// 查询在2020年成绩高于90分的所有女学生的姓名
stmt.setInt(1, 90);
stmt.setInt(2, 2020);
ResultSet rs1 = stmt.executeQuery();
...
// 查询在2020年成绩高于85分的所有女学生的姓名
stmt.setInt(1, 85);
ResultSet rs2 = stmt.executeQuery();
...
}
另一种常见的使用场景是将查询的参数存放在一个数组或者Map中,通过一个循环来设置其值。
...
// 查询成绩高于某一阈值的所有女学生的姓名
String query = "SELECT name FROM student where gender = 'female' and grade > ? and year = ? ";
Object[] paramsInArray = new Object[2];
paramsInArray[0] = Integer.valueOf(90);
paramsInArray[1] = Integer.valueOf(2020);
try (PreparedStatement stmt = con.prepareStatement(query)) {
// 查询在2020年成绩高于90分的所有女学生的姓名
for (int i = 0; i < paramsInArray.length; i++) {
stmt.setObject(i+1, paramsInArray[i]);
}
ResultSet rs = stmt.executeQuery();
...
}
Map<Integer, Object> paramsInMap = new HashMap<Integer, Object>();
paramsInMap[1] = Integer.valueOf(90);
paramsInMap[2] = Integer.valueOf(2020);
try (PreparedStatement stmt = con.prepareStatement(query)) {
// 查询在2020年成绩高于90分的所有女学生的姓名
// 关键字var在Java 11中引入,会自动检查变量的类型
for (var entry: paramsInMap.entrySet()) {
stmt.setObject(entry.key(), entry.value());
}
ResultSet rs = stmt.executeQuery();
...
}
与数据查询操作类似,PreparedStatement接口也能运行Insert、Delete和Update语句。开发人员也能在语句中使用占位符暂时替代真实的值。PreparedStatement常常用于执行多个Insert语句或者Update语句。
例如,在下面的示例中,我们向数据库新增三名学生信息。
...
String insertStatement = "INSERT INTO student VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = con.prepareStatement(insertStatement)) {
/*
* Students:
* id, name, gender, grade
* 0001, Adam, Male, 90
* 0002, Amy, Female, 85
* 0003, David, Male, 80
*/
stmt.setInt(1, 0001);
stmt.setString(2, "Adam");
stmt.setString(3, "Male");
stmt.setInt(4, 90);
stmt.execute();
stmt.setInt(1, 0002);
stmt.setString(2, "Amy");
stmt.setString(3, "Female");
stmt.setInt(4, 85);
stmt.execute();
stmt.setInt(1, 0003);
stmt.setString(2, "David");
stmt.setString(3, "Male");
stmt.setInt(4, 80);
stmt.execute();
}
在插入新数据之后,可能出于某些原因,我们需要更改一些信息,如下面的例子所示。
...
String updateStatement = "UPDATE student SET grade = ? WHERE id = ?";
try (PreparedStatement stmt = con.prepareStatement(updateStatement)) {
// 将学号为0001的学生的分数修改为95
stmt.setInt(1, 95);
stmt.setInt(2, 0001);
stmt.execute();
// 将学号为0002的学生的分数修改为90
stmt.setInt(1, 90);
stmt.setInt(2, 0002);
stmt.execute();
// 将学号为0003的学生的分数修改为85
stmt.setInt(1, 85);
stmt.setInt(2, 0003);
stmt.execute();
}
如果我们需要删除一些数据的话,也可以使用PreparedStatement接口,如下面的例子所示。
...
String deleteStatement = "DELETE FROM student WHERE id = ?";
try (PreparedStatement stmt = con.prepareStatement(deleteStatement)) {
// 删除学号为0001的学生的记录
stmt.setInt(1, 0001);
stmt.execute();
// 删除学号为0002的学生的记录
stmt.setInt(1, 0002);
stmt.execute();
// 删除学号为0003的学生的记录
stmt.setInt(1, 0003);
stmt.execute();
}
Statement接口支持使用addBatch()和executeBatch()来批处理执行大量的SQL语句。这种批处理方式支持运行大量的不同的SQL语句。这些SQL语句可以是查询、新增(Insert)、删除(Delete)或者更新(Update)语句。虽然PreparedStatement继承自Statement,但是,PreparedStatement不支持这种批处理。
PreparedStatement的性能优势在于执行多个结构相同的SQL语句。这些SQL语句结构和语义相同;它们只是使用的参数的值不同而已。因为在第一次运行这些语句时,PreparedStatement预编译了这些语句的结构,所以,在后续使用中,可以节省SQL语句校验的时间。
本章介绍了PreparedStatement的基本使用方法。在批量运行相同结构的SQL语句时,PreparedStatement能提升SQL运行的性能。另外,PreparedStatement接口能帮助开发人员预防SQL注入攻击。
注册用户登陆后可留言