文章目录
1、简述
Java JDBC PreparedStatement 表示预编译的SQL语句的对象,SQL语句已预编译并存储在PreparedStatement对象中。 然后可以使用该对象多次有效地执行该语句。
让我们看一下参数化查询的简单示例:
String sql = "insert into users values(?,?,?)";
如您所见,我们正在为值传递参数(?)。 它的值将通过调用PreparedStatement的setter方法进行设置。
注意:用于设置IN参数值的setter方法(setShort,setString等)必须指定与输入参数的已定义SQL类型兼容的类型。 例如,如果IN参数的SQL类型为INTEGER,则应使用setInt方法。
如果需要任意参数类型转换,则应将setObject方法与目标SQL类型一起使用。
在以下设置参数的示例中,con表示活动连接:
PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES
SET SALARY = ? WHERE ID = ?");
pstmt.setBigDecimal(1, 153833.00)
pstmt.setInt(2, 110592)
Java JDBC PreparedStatement的主要功能有:
- 易于在SQL语句中插入参数。
- 易于重新使用带有新参数值的PreparedStatement。
- 可能会提高执行语句的性能。
- 使批量更新更容易。
2、PreparedStatement 类图
通过类图,我们可知 PreparedStatement 继承于 Statement,可以看作是对 Statement 对象功能的增强。其中最终要的一条是,PreparedStatement 接口采用参数的SQL语句,您可以使用同一条语句,在每次执行时为其提供不同的值。
PreparedStatement 比 Statement 性能好,因为 使用PreparedStatement接口的SQL仅被编译一次, 当我们重复使用PreparedStatement或将其批处理方法用于执行多个查询时,它会变得更加明显。
3、为什么要使用PreparedStatement?
- PreparedStatement 帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。
- PreparedStatement 允许我们使用参数输入执行动态查询。
- PreparedStatement 提供了不同类型的setter方法,以设置查询的输入参数。
- PreparedStatement 帮助我们使用setter方法编写面向对象的代码,而使用Statement,则必须使用String Concatenation 创建查询。 如果要设置多个参数,则使用String串联编写Query看起来非常难看并且容易出错。
- 与Java数组或列表不同,PreparedStatement变量的索引从1开始。
尽管 PreparedStatement 相比于 Statement 具有一定的优势,但是 PreparedStatement 也有局限性。我们不能将其用于带有IN子句的SQL查询,因为PreparedStatement不允许我们为单个占位符(?)绑定多个值。
4、PreparedStatement 接口常用方法
下面给出了PreparedStatement接口的重要方法:
方法 | 描述 |
---|---|
public void setInt(int paramIndex, int value) | 为给定的参数索引,设置整数值 |
public void setString(int paramIndex, String value) | 为给定的参数索引,设置String值 |
public void setFloat(int paramIndex, float value) | 为给定的参数索引,设置float值 |
public void setDouble(int paramIndex, double value) | 为给定的参数索引,设置double值 |
public int executeUpdate() | 执行查询。 它用于创建,删除,插入,更新等。 |
public ResultSet executeQuery() | 执行选择查询。 它返回一个ResultSet实例。 |
5、PreparedStatement 性能
数据库解析SQL字符串并为其创建查询计划需要花费时间。 查询计划则是对数据库如何以最有效的方式执行查询的分析。
如果为每个查询或对数据库的更新提交新的完整SQL语句,则数据库必须解析SQL,并为查询创建查询计划。 通过重用现有的 PreparedStatement,您可以将SQL解析和查询计划复用于后续查询。 通过减少每次执行的解析和查询计划开销,这可以加快查询的执行速度。
PreparedStatement 的复用有以下两个方面:
- JDBC驱动程序重用 PreparedStatement。
- 数据库重用 PreparedStatement。
首先,JDBC驱动程序可以在内部缓存PreparedStatement对象,从而可以重用PreparedStatement对象。 这样可以节省少许PreparedStatement创建时间。
其次,缓存的解析和查询计划可能会使用相同的数据库跨Java应用程序(例如集群中的应用程序服务器)重用。
下图说明数据库中语句的缓存:
6、PreparedStatement 接口示例
6.1、使用 PreparedStatement
执行 PreparedStatement 就像执行一条常规语句。 要执行查询,可以使用executeQuery()或executeUpdate方法。 以下示例使用的是 executeUpdate() 方法。
executeQuery()方法返回一个ResultSet。更新数据库时使用executeUpdate()方法。 它返回一个整数,该整数告诉数据库更新中有多少记录受到影响。
//插入记录 SQL
private static final String INSERT_USERS_SQL = "INSERT INTO users (id, name, email, country, password) VALUES (?, ?, ?, ?, ?);";
//修改记录 SQL
private static final String UPDATE_USERS_SQL = "update users set name = ? where id = ?;";
public static void main(String[] argv) throws SQLException {
PreparedStatementDemo preparedStatementDemo = new PreparedStatementDemo();
//插入记录
preparedStatementDemo.insertRecord();
//修改记录
preparedStatementDemo.updateRecord();
}
/**
* 插入记录
*/
public void insertRecord() throws SQLException {
System.out.println(INSERT_USERS_SQL);
// Step 1: 创建 Connection
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT",
"root", "root");
// Step 2:使用conn创建statement对象
PreparedStatement preparedStatement = conn.prepareStatement(INSERT_USERS_SQL)) {
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "张三");
preparedStatement.setString(3, "zhangsan@qq.com");
preparedStatement.setString(4, "china");
preparedStatement.setString(5, "123456");
System.out.println(preparedStatement);
// Step 3: 执行查询
preparedStatement.executeUpdate();
} catch (SQLException e) {
// 打印SQL异常信息
printSQLException(e);
}
}
/**
* 修改记录
*/
public void updateRecord() throws SQLException {
System.out.println(UPDATE_USERS_SQL);
// Step 1: 创建 Connection
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT",
"root", "root");
// Step 2:使用conn创建statement对象
PreparedStatement preparedStatement = conn.prepareStatement(UPDATE_USERS_SQL)) {
preparedStatement.setString(1, "李四");
preparedStatement.setInt(2, 1);
// Step 3: 执行查询
preparedStatement.executeUpdate();
} catch (SQLException e) {
// 打印SQL异常信息
printSQLException(e);
}
}
public static void printSQLException(SQLException ex) {
for (Throwable e : ex) {
if (e instanceof SQLException) {
e.printStackTrace(System.err);
System.err.println("SQLState: " + ((SQLException) e).getSQLState());
System.err.println("Error Code: " + ((SQLException) e).getErrorCode());
System.err.println("Message: " + e.getMessage());
Throwable t = ex.getCause();
while (t != null) {
System.out.println("Cause: " + t);
t = t.getCause();
}
}
}
}
6.2、复用PreparedStatement
一旦创建了 PreparedStatement,就可以重复使用它。 您可以通过为参数设置新值来重用PreparedStatement。
//插入记录 SQL
private static final String INSERT_USERS_SQL = "INSERT INTO users (id, name, email, country, password) VALUES (?, ?, ?, ?, ?);";
public static void main(String[] argv) throws SQLException {
PreparedStatementDemo preparedStatementDemo = new PreparedStatementDemo();
//复用 PreparedStatement
preparedStatementDemo.reuseSqlByPreparedStatement();
}
/**
* 复用 PreparedStatement
*/
public void reuseSqlByPreparedStatement() throws SQLException {
System.out.println(INSERT_USERS_SQL);
// Step 1: 创建 Connection
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT",
"root", "root");
// Step 2:使用conn创建statement对象
PreparedStatement preparedStatement = conn.prepareStatement(INSERT_USERS_SQL)) {
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "张三");
preparedStatement.setString(3, "zhangsan@qq.com");
preparedStatement.setString(4, "china");
preparedStatement.setString(5, "123456");
System.out.println(preparedStatement);
// Step 3: 执行查询
preparedStatement.executeUpdate();
// Step 4: 复用 PreparedStatement
preparedStatement.setInt(1, 2);
preparedStatement.setString(2, "李四");
preparedStatement.setString(3, "lisi@qq.com");
preparedStatement.setString(4, "china");
preparedStatement.setString(5, "123456");
// Step 5: 执行查询
preparedStatement.executeUpdate();
} catch (SQLException e) {
// 打印SQL异常信息
printSQLException(e);
}
}
public static void printSQLException(SQLException ex) {
for (Throwable e : ex) {
if (e instanceof SQLException) {
e.printStackTrace(System.err);
System.err.println("SQLState: " + ((SQLException) e).getSQLState());
System.err.println("Error Code: " + ((SQLException) e).getErrorCode());
System.err.println("Message: " + e.getMessage());
Throwable t = ex.getCause();
while (t != null) {
System.out.println("Cause: " + t);
t = t.getCause();
}
}
}
}