1. 程式人生 > >JDBC介面———PreparedStatement預處理

JDBC介面———PreparedStatement預處理

PreparedStatement
    * 它是Statement介面的子介面;
    * 強大之處:
        - 防SQL攻擊;
        - 提高程式碼的可讀性、可維護性;
        - 提高效率!
    * 學習PreparedStatement的用法:
        - 給出SQL模板!
        - 呼叫Connection的PreparedStatement prepareStatement(String sql模板),得到PreparedStatement物件pstmt;
        - 呼叫pstmt的setXxx()系列方法sql模板中的?賦值!如:pstmt.setString(1,"zhangsan");
        - 呼叫pstmt的executeUpdate()或executeQuery(),但它的方法都沒有引數。
    * 預處理的原理
        - 伺服器的工作:
            校驗sql語句的語法!
            編譯:一個與函式相似的東西!
            執行:呼叫函式,
        - PreparedStatement:
            前提:連線的資料庫必須支援預處理!幾乎沒有不支援的!
            每個pstmt都與一個sql模板繫結在一起,先把sql模板給資料庫,資料庫先進行校驗,再進行編譯。
                執行時只是把引數傳遞過去而已!
            若二次執行時,就不用再次校驗語法,也不用再次編譯!直接執行!
目錄
    1、什麼是SQL攻擊
    2、演示SQL攻擊
    3、防止SQL攻擊
    4、PreparedStatement是什麼?
    5、PreparedStatement的使用
--------------------------------------------------------------------
1、什麼是SQL攻擊
    在需要使用者輸入的地方,使用者輸入的是SQL語句的片段,終端使用者輸入的SQL片段與我們DAO中寫的SQL語句合成一個完整的SQL語句!
    例如使用者在登入時輸入的使用者名稱和密碼都是為SQL語句的片段!

2、演示SQL攻擊
    首先我們需要建立一張使用者表,用來儲存使用者的資訊。
        CREATE TABLE user(
            uid    CHAR(32) PRIMARY KEY,
            username   VARCHAR(30) UNIQUE KEY NOT NULL,
            PASSWORD   VARCHAR(30)
        );
        INSERT INTO user VALUES('U_1001', 'zs', 'zs');
        SELECT * FROM user;
    現在使用者表中只有一行記錄,就是zs。
    下面我們寫一個login()方法!
    public boolean login(String username, String password) throws Exception {
        /*
        * 一、得到Connection
        * 二、得到Statement
        * 三、得到ResultSet
        * 四、rs.next()返回的是什麼,我們就返回什麼
        */

        // 準備四大引數
        String driverClassName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mydb3";
        String mysqlUsername = "root";
        String mysqlPassword = "123";

        // 載入驅動類
        Class.forName(driverClassName);
        // 得到Connection
        Connection con = DriverManager.getConnection(url, mysqlUsername, mysqlPassword);
        // 得到Statement
        Statement stmt = con.createStatement();
        // 給出sql語句,該sql受到攻擊後,查詢結果與事實不符
        String sql="select * from t_user where username='" + username + "' and password='" + password + "'";
        //得到ResultSet,
        ResultSet rs = stmt.executeQuery(sql);
        //返回boolean型別的:結果集是否有第一行資料。只要表裡有資料,就返回true,就能登入,嚴重問題。
        return rs.next();
    }

    SQL注入測試
    @Test
    public void fun1() throws Exception {
        String username = "a' or 'a'='a";
        String password = "a' or 'a'='a";
        boolean bool = login(username, password);
        System.out.println(bool);
    }
    這行當前會使我們登入成功!因為是輸入的使用者名稱和密碼是SQL語句片段,最終與我們的login()方法中的SQL語句組合在一起!
    我們來看看組合在一起的SQL語句:
        select * from t_user where username='a' or 'a'='a' and password='a' or 'a'='a';
    很明顯,使用者名稱和密碼都不對,卻可以查詢出所有資料,可以登入。

3、防止SQL攻擊
    * 過濾使用者輸入的資料中是否包含非法字元;
    * 分步交驗!先使用使用者名稱來查詢使用者,如果查詢到了,再比較密碼;
    * 使用PreparedStatement。

4、PreparedStatement是什麼?
    PreparedStatement叫預編譯宣告!
    PreparedStatement是Statement的子介面,你可以使用PreparedStatement來替換Statement。
    PreparedStatement的好處:
        * 防止SQL攻擊; [不只它可以防!]
        * 提高程式碼的可讀性,以可維護性;
        * 提高效率。 [很重要!]

5、PreparedStatement的使用
    * 使用Connection的prepareStatement(String sql):即建立它時就讓它與一條SQL模板繫結;
    * 呼叫PreparedStatement的setXXX()系列方法為問號設定值
    * 呼叫executeUpdate()或executeQuery()方法,但要注意,呼叫沒有引數的方法;
    ---------------------------------------
        // 載入驅動類
        Class.forName("com.mysql.jdbc.Driver");
        // 得到Connection
        Connection con = DriverManager.getConnection(url, mysqlUsername, mysqlPassword);
        /*
        * 一、得到PreparedStatement
        * 1. 給出sql模板:所有的引數使用佔位符? 來替代
        * 2. 呼叫Connection方法,得到PreparedStatement
        */
        String sql = "select * from t_user where username=? and password=?";
        PreparedStatement pstmt = con.prepareStatement(sql);
        /*
        * 二、為引數賦值
        */
        pstmt.setString(1, "zhangsan");//給第1個問號賦值,值為username
        pstmt.setString(2, "123");//給第2個問號賦值,值為password
        ResultSet rs = pstmt.executeQuery();//呼叫查詢方法,向資料庫傳送查詢語句,沒有引數。

        rs.close(); //立即釋放此 ResultSet 物件的資料庫和 JDBC 資源,而不是等待該物件自動關閉時發生此操作。
        pstmt.clearParameters(); // 再次使用時需要把當前引數值清除。

        pstmt.setString(1, "liSi");
        pstmt.setString(2, "123");
        pstmt.executeQuery();
    ---------------------------------------
    在使用Connection建立PreparedStatement物件時需要給出一個SQL模板,所謂SQL模板就是有"?"的SQL語句,其中"?"就是引數。
    在得到PreparedStatement物件後,呼叫它的setXXX()方法為"?"賦值,這樣就可以得到把模板變成一條完整的SQL語句,
    然後再呼叫PreparedStatement物件的executeQuery()方法獲取ResultSet物件。
    注意:
    Statement的 ResultSet executeQuery(String sql)、int executeUpdate(String sql)方法是需要引數(SQL語句)的;
    PreparedStatement物件獨有的 ResultSet executeQuery()、int executeUpdate() 方法是沒有引數的。
    因為在建立PreparedStatement物件時已經讓它與一條SQL模板繫結在一起了,
    所以在呼叫它的executeQuery()和executeUpdate()方法時就不再需要引數了。
    PreparedStatement最大的好處就是在於重複使用同一模板,給予其不同的引數來重複的使用它。這才是真正提高效率的原因。
    所以,建議大家在今後的開發中,無論什麼情況,都去需要PreparedStatement,而不是使用Statement。