JDBC API 4.2(六):PreparedStatement 接口源码分析

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();
                }
            }
        }
    }
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值