专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

你真的懂JDBC的PrepareStatement吗

JDBC API的设计目的是使简单的事情保持简单。这意味着JDBC简化了日常数据库任务

如何使用JDBC进行java应用程序与数据库交互

Java应用程序通过JDBC与数据库进行交互来管理数据,需要四步:创建连接、创建Statement、执行SQL语句查询、处理结果

  • 连接数据库
  • 创建Statement对象
  • 发送查询或者更新语句到数据库
  • 检索并处理从数据库接收到的结果,以响应您的查询
public void connectToAndQueryDatabase(String username, String password) {
        // 创建连接
    Connection con = DriverManager.getConnection(
                         "jdbc:myDriver:myDatabase",
                         username,
                         password);
        // 发送查询语句
    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
    // 处理收到的结果
    while (rs.next()) {
        int x = rs.getInt("a");
        String s = rs.getString("b");
        float f = rs.getFloat("c");
    }
}

PrepareStatement的使用

JDBC API提供了三种不同类型的Statement:

  • Statement:用于实现不带参数的简单SQL语句
  • PreparedStatement:用于预编译可能包含输入参数的SQL语句,并且提供了占位符参数的功能,可以防止SQL注入
  • CallableStatement:用于执行可能同时包含输入和输出参数的存储过程

在日常开发中,对于SQL语句的执行(不包含存储过程)一般都是使用PreparedStatement进行操作。

@Test
public void test() throws SQLException {
  // 创建连接
  Connection connection = DriverManager.getConnection("jdbc:mysql://IP:PORT/mybatisdemo?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true", "用户", "密码");
  // 首次预编译,将发送预编译请求到MySQL服务端
  PreparedStatement preparedStatement = connection.prepareStatement("select * from blog where name = ?");
  preparedStatement.setString(1, "woshishui or '1' = '1'");

  // 执行查询
  preparedStatement.execute();
  // 处理结果
  ResultSet resultSet = preparedStatement.getResultSet();
  while (resultSet.next()) {
    long id = resultSet.getLong(1);
    String name = resultSet.getString(2);
    Blog blog = new Blog();
    blog.setId(id);
    blog.setName(name);
    System.out.println(blog);
  }

  // 关闭Statement,以便进行Statement的缓存
  preparedStatement.close();
  // 此时,由于之前已经执行过预编译了,直接从缓存中拿到预编译好的对象
  preparedStatement = connection.prepareStatement("select * from blog where name = ?");
  preparedStatement.setString(1, "woshishen");
  // 执行查询
  preparedStatement.execute();
  // 处理结果
  resultSet = preparedStatement.getResultSet();
  while (resultSet.next()) {
    long id = resultSet.getLong(1);
    String name = resultSet.getString(2);
    Blog blog = new Blog();
    blog.setId(id);
    blog.setName(name);
    System.out.println(blog);
  }
}

上面我们提到过PrepareStatement可以支持SQL预编译、防SQL注入,那么预编译发生在什么时候?转义又发生在什么时候呢?

我们通过wireshark抓包可以看出,当执行 connection.prepareStatement 时,客户端会向服务端发送预编译SQL的请求命令,之后由服务端进行预编译处理后,返回一个 Statement ID ,之后客户端将通过传递这个 Statement ID 和参数组来执行SQL。

104_1.png

104_2.png

104_3.png

而防止SQL注入的方法,是通过对参数进行转义来实现。具体可以通过打开MySQL的 general_log 可以看出。如果开启了 useServerPrepStmts=true 那么由服务端来执行转义,否则客户端直接转义了。

104_4.png

总结

PrepareStatement的预编译和防止SQL注入涉及到两个参数:

  • useServerPrepStmts=true
  • cachePrepStmts=true

如果建立连接时,没有使用这两个参数的话,默认情况下都为false。即不开启服务端预编译,以及不缓存预编译SQL(都没有进行服务端的预编译请求了,自然就没有缓存的步骤了)。

如果只使用了 useServerPrepStmts=true 那么每次调用 Connection.prepareStatement 时,都将向服务端发送预编译SQL的请求,之后通过传递 Statement ID 和参数组进行SQL调用。因此打开了 useServerPrepStmts=true 那么建议同时打开 cachePrepStmts=true 以便提高效率,避免同一个SQL多次执行进行了多次预编译操作。

只有调用 PrepareStatement.close() 才会对Statement对象进行缓存,否则下一次调用 Connection.prepareStatement 时将重新发起预编译请求,造成不必要的消耗

参考文档

未经允许不得转载:搜云库技术团队 » 你真的懂JDBC的PrepareStatement吗

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们