如何使用 PreparedStatement 来避免 SQL 注入,并提高性能?

发布时间 2023-12-02 08:46:52作者: 于山上之明月

✨前言✨
本篇文章主要如何使用 PreparedStatement 来避免 SQL 注入,并提高性能?

?欢迎点赞 ? 收藏 ⭐留言评论 ?私信必回哟?
?博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言


@

?一,什么是 PreparedStatement ?

  preparedStatement是一种预编译的SQL语句,它可以在执行时 动态地设置参数,从而提高SQL语句的执行效率和安全性。与普通的SQL语句不同,preparedStatement在执行前已经被编译成二进制代码,因此可以避免SQL注入攻击。


在Java中,使用preparedStatement可以通过以下步骤实现

  1. 创建Connection对象,连接到数据库。

  2. 使用Connection对象创建preparedStatement对象,预编译SQL语句。

  3. 设置SQL语句中的参数。

  4. 执行SQL语句,获取结果集。

  5. 处理结果集,关闭连接。


例如:

以下代码演示关键部分代码如何使用 preparedStatement 查询 数据库中的用户信息:

//编写sql语句
String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; 
//根据连接字符串连接数据库
Connection conn = DriverManager.getConnection(url, username, password); 
//创建PreparedStatement 对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//传入参数
pstmt.setString(1, "john"); 
pstmt.setString(2, "password123"); 
//执行方式
ResultSet rs = pstmt.executeQuery(); 
while (rs.next()) {     
 String username = rs.getString("username");    
 String password = rs.getString("password"); 
    // 处理结果集 } rs.close(); pstmt.close(); conn.close(); 
}
//释放资源
rs.close();
pstmt.close();
conn.close();

?二,为什么要使用PreparedStatement ?

  使用 PreparedStatement 时,不需要拼接 SQL 语句,因此可以避免因字符串拼接而产生的 SQL 注入问题。Prepared Statement 可以对参数进行转义,从而避免输入的参数导致的 SQL 异常。Prepared Statement 可以预编译 SQL 语句,并将编译后的语句保存到数据库服务器的缓存中,因此在执行多次相同的 SQL 语句时,可以大幅提高性能。


1,通过PreparedStatement提升性能

  Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句。Statement每执行一次都要对传入的SQL语句编译一次,效率较差。

  某些情况下,SQL语句只是其中的参数有所不同,其余子句完全相同,适用于PreparedStatement。PreparedStatement的另外一个好处就是预防sql注入攻击

  PreparedStatement是接口,继承自Statement接口。

  使用PreparedStatement时,SQL语句已提前编译,三种常用方法 execute、 executeQuery 和 executeUpdate 已被更改,以使之不再需要参数。

  PreparedStatement 实例包含已事先编译的 SQL 语句,SQL 语句可有一个或多个 IN 参数,IN参数的值在 SQL 语句创建时未被指定。该语句为每个 IN 参数保留一个问号(“?”)作为占位符。

  每个问号的值必须在该语句执行之前,通过适当的setInt或者setString 等方法提供。

  由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
通常批量处理时使用PreparedStatement。


做一个小实验:分别向数据库插入1000条记录。分别记录执行时间,然后进行比较。
使用Statement的执行效率 insert User_1表

/**
* // 使用Statement的 执行效率
* @throws Exception
*/
@Test
public void test1() throws Exception{
  //c3p0 的数据源
   ComboPooledDataSource dataSource = new ComboPooledDataSource();
   Connection conn = dataSource.getConnection();
   Statement state = conn.createStatement();
   long time1 = System.nanoTime();//获取纳秒级时间
   for (int i = 0; i < 3000; i++) {
        String sql = String.format("insert into user_1 values(%s,'%s',%s)",
               i,"Test",20);
        state.executeUpdate(sql);
   }
   System.out.println("插入完毕");
   long time2 = System.nanoTime();//获取纳秒级时间
   System.out.println("耗费时长:"+(time2-time1));
   conn.close();
   state.close();
}

测试数据

在这里插入图片描述


使用预编译PreparedStatement SQL提高执行效率 insert User_2表

/**
* 使用预编译PreparedStatement SQL提高执行效率 
*/
@Test
public void test2() throws Exception{
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    Connection conn = dataSource.getConnection();
    String sql = "insert into user_2 values(?,?,?)";
    PreparedStatement preparedStatement = conn.prepareStatement(sql);
    long time1 = System.nanoTime();//获取纳秒级时间
    for (int i = 0; i < 3000; i++) {
        preparedStatement.setInt(1,i);
        preparedStatement.setString(2,"Test");
        preparedStatement.setInt(3,20);
        preparedStatement.execute();
}
    System.out.println("插入完毕");
    long time2 = System.nanoTime();//获取纳秒级时间
    System.out.println("耗费时长:"+(time2-time1));
    conn.close();
}

测试数据

在这里插入图片描述


2、通过PreparedStatement防止SQL Injection(注入)

  对JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement无效,因为PreparedStatement不允许在插入参数时改变SQL语句的逻辑结构。
使用预编译的语句对象时,用户传入的任何数据不会和原SQL语句发生匹配关系,无需对输入的数据做过滤。如果用户将 ”or 1 = 1” 传入赋值给占位符,
下述SQL语句将无法执行:

 select * from t where username = ? and password = ?;

  PreparedStatement是Statement的子类,表示预编译的SQL语句的对象。在使用PreparedStatement对象执行SQL命令时,命令被数据库编译和解析,并放入命令 缓冲区。缓冲区中的预编译SQL命令可以重复使用。


?三,Statement和PreparedStatement的区别

1、PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高

2、使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。

3、statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement预编译得, preparedstatement支持批处理 。

4、执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。(使用PrepareStatement对象执行sql时,sql被数据库进行解析和编译,然后被放到命令缓冲区,每当执行同一个PrepareStatement对象时,它就会被解析一次,但不会被再次编译。在缓冲区可以发现预编译的命令,并且可以重用)

5、 PreparedStatement 可以规避 Statement弊端:①拼串 ②sql注入问题

6、PreparedStatement 可以实现操作Blob类型、Clob类型的数据

联系:

1、PreparedStatement继承自Statement

2、PrerapedStatement和Statement都是接口

3、PreParedStatement和Statement都可以实现对数据表的CRUD操作:增删改查


✨最后✨

总结不易,希望uu们不要吝啬你们的?哟(^U^)ノ~YO!!
如有问题,欢迎评论区批评指正?