大数据必学Java基础(九十五):预编译语句对象

举报
Lansonli 发表于 2022/12/15 15:47:08 2022/12/15
【摘要】 预编译语句对象一、使用预编译语句对象防止注入攻击package com.lanson.test2;import com.lanson.entity.Account;import java.sql.*;import java.util.Scanner;/** * @Author: Lansonli * @Description: MircoMessage:Mark_7001 */public ...

预编译语句对象

一、使用预编译语句对象防止注入攻击

package com.lanson.test2;
import com.lanson.entity.Account;
import java.sql.*;
import java.util.Scanner;
/**
 * @Author: Lansonli
 * @Description: MircoMessage:Mark_7001
 */
public class TestInjection2 {
    private static String driver ="com.mysql.cj.jdbc.Driver";
    private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
    private static String user="root";
    private static String password="root";
    public static void main(String[] args) {
        Scanner sc =new Scanner(System.in);
        System.out.println("请输入用户名");
        String username=sc.next();
        System.out.println("请输入密码");
        String pwd =sc.next();
        Account account = getAccount(username, pwd);
        System.out.println(null!= account?"登录成功":"登录失败");
        sc.close();
    }
    public static Account getAccount(String username,String pwd){
        Connection connection = null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        Account account =null;
        try{
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user,password);
            /*
            * 1使用PreparedStatement语句对象防止注入攻击
            * 2PreparedStatement 可以使用 ? 作为参数的占位符
            * 3使用?作为占位符,即使是字符串和日期类型,也不使用单独再添加 ''
            * 4connection.createStatement();获得的是普通语句对象 Statement
            * 5connection.prepareStatement(sql);可以获得一个预编译语句对象PreparedStatement
            * 6如果SQL语句中有?作为参数占位符号,那么要在执行CURD之前先设置参数
            * 7通过set***(问号的编号,数据) 方法设置参数
            * */
            String sql="select * from account where username = ? and password = ?";
            preparedStatement = connection.prepareStatement(sql);//这里已经传入SQL语句
            //设置参数
            preparedStatement.setString(1,username );
            preparedStatement.setString(2,pwd );
            //执行CURD
            resultSet = preparedStatement.executeQuery();// 这里不需要再传入SQL语句
            while(resultSet.next()){
                int aid = resultSet.getInt("aid");
                String usernamea = resultSet.getString("username");
                String pwda = resultSet.getString("password");
                double money = resultSet.getDouble("money");
                account=new Account(aid,usernamea,pwda,money);
                System.out.println(account);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(null != resultSet){
                try {
                    resultSet.close();
                } catch (SQLException e) {e.printStackTrace();
                }
            }
            if(null != preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(null != connection){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return account;
    }
}


prepareStatment对象在set***方法上,会对单引号进行转译处理,也就是说?中的数据的单引号 ‘ 会被转义成 \’,这样就单引号就不会破坏sql语句的结构。

SELECT *   FROM users WHERE  userName =  ?    AND password =  ?
preparedStatement.setString(1,"xiaoming");
preparedStatement.setString(2,'anything'   OR 'x'='x');

// 会被转义为

SELECT *   FROM users WHERE  userName = 'xiaoming'   AND password =  'anything\'   OR\'x\'=\'x\''

// 而不是

SELECT *   FROM users WHERE  userName =  'xiaoming' AND password =  'anything' OR 'x'='x'


说白了就是把值当中的所有单引号给转义了,这就达到了防止sql注入的目的。mysql驱动的PreparedStatement实现类的setString()方法内部做了单引号的转义,而Statement不能防止sql注入,就是因为它没有把单引号做转义,而是简单粗暴的直接拼接字符串,所以达不到防止sql注入的目的。

二、预编译

当客户端发送一条sql语句给DBMS时,MySQL的执行流程如下图

sql命令的执行流程如下:

1. 客户端向服务器端发送SQL命令。

2. 服务器端连接模块连接并验证。

3. 缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行。

4. 解析器解析SQL为解析树,如果出现错误,报SQL解析错误,如果正确,向下传递。

5. 预处理器对解析树继续处理,处理成新的解析树。

6. 优化器根据开销自动选择最优执行计划,生成执行计划。

7. 执行器执行执行计划,访问存储引擎接口。

8. 存储引擎访问物理文件并返回结果。

9. 如果开启缓存,缓存管理器把结果放入到查询缓存中。

10. 返回结果给客户端。

当客户发送一条SQL语句给DBMS后,DBMS总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。

预编译语句PreparedStatement 是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS,由DBMS首先进行编译后再执行。预编译语句和Statement不同,在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译。当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译。预编译的SQL语句处理性能稍微高于普通的传递变量的办法。

例如:我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。

三、预编译如何开启

我们可以通过设置URL中的参数来控制预编译是否开启

useServerPrepStmts是否开启预编译

cachePrepStmts 是否启用预编译缓存

"jdbc:mysql://localhost:3306/mydb?*****&useServerPrepStmts=true&cachePrepStmts=true";


值得注意的是,我们的Connector/J 5.0.5及之后useServerPrepStmts默认false,就是默认没有开启预编译,之前默认为true,,cachePrepStmts 一直默认为false。需要我们手动设置才可以启用预编译,在开启预编译的同时要同时开启预编译缓存才能带来些许的性能提升。

Statement和PreparedStatment的关系和区别

关系:public interface PreparedStatement extends Statement

区别

PreparedStatment安全性高,可以避免SQL注入。

PreparedStatment简单不繁琐,不用进行字符串拼接。

PreparedStatment性能高,用在执行多个相同数据库DML操作时,可以减少sql语句的编译次数。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。