1. 程式人生 > 實用技巧 >JDBC | 第三章: JDBC之SQL預編譯與與防注入CRUD操作

JDBC | 第三章: JDBC之SQL預編譯與與防注入CRUD操作

在java的JDBC中預編譯執行SQL主要是用到PreparedStatement物件SQL 語句被預編譯並存儲在 PreparedStatement 物件中。然後可以使用此物件多次高效地執行該語句可以通過呼叫 Connection 物件的 preparedStatement() 方法獲取 PreparedStatement 物件 PreparedStatement 物件所執行的 SQL 語句中,引數用問號(?)來表示,呼叫 PreparedStatement 物件的 setXXX() 方法來設定這些引數. setXXX() 方法有兩個引數,第一個引數是要設定的 SQL 語句中的引數的索引(從 1 開始),第二個是設定的 SQL 語句中的引數的值,注意用setXXX方式設定時,需要與資料庫中的欄位型別對應,例如mysql中欄位為varchar,就需要使用setString方法,如果為Date型別,就需要使用

setDate方法來設定具體sql的引數。簡單來說就是,預編譯的SQL語句不是有具體數值的語句,而是用(?)來代替具體資料,然後在執行的時候再呼叫setXX()方法把具體的資料傳入。同時,這個語句只在第一次執行的時候編譯一次,然後儲存在快取中。之後執行時,只需從快取中抽取編譯過了的程式碼以及新傳進來的具體資料,即可獲得完整的sql命令。這樣一來就省下了後面每次執行時語句的編譯時間。

使用預編譯分4步走

1:定義預編譯的sql語句,其中待填入的引數用 ? 佔位。注意,?無關型別,不需要加分號之類。其具體資料型別在下面setXX()時決定。

2:建立預編譯Statement,並把sql語句傳入。此時sql語句已與此preparedStatement繫結。所以第4步執行語句時無需再把sql語句作為引數傳入execute()

3:填入具體引數。通過setXX(問號下標,數值)來為sql語句填入具體資料。注意:問號下標從1開始,setXX與數值型別有關,字串就是setString(index,str).

4: 注意,前面建立preparedstatement時已經把sql語句傳入了,此時執行不需再把sql語句傳入,這是與一般statement執行sql語句所不同之處。

新增操作

  public int ins() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //這裡引數用?佔位符表示
        String sql = "insert into student (name, age, addr, hobby) values (?,?,?,?)";
        try {
            //獲取資料連線
            connection = basicUse.getConnection();

            //獲取傳送sql指令執行sql進行預編譯
            preparedStatement = connection.prepareStatement(sql);

            //設定sql語句引數索引從1開始
            preparedStatement.setObject(1, "Tom");
            preparedStatement.setObject(2, 24);
            preparedStatement.setObject(3, "上海");
            preparedStatement.setObject(4, "籃球");

            System.out.println("執行sql" + sql);
            //執行成功返回1
            int success = preparedStatement.executeUpdate(); //注意這裡不需要在放入sql
            return success;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            //執行完資料庫操作後記得關閉資料庫連線資源
            try {

                preparedStatement.close();
                connection.close();

            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

查詢操作

 public void select() {
        Connection connection = null;
         PreparedStatement preparedStatement = null;
        ResultSet rs = null;
        String sql = "select * from user limit 0,1";
        try {
            //獲取資料連線
            connection = basicUse.getConnection();
            //獲取傳送sql指令執行sql物件
            preparedStatement = connection.prepareStatement(sql);
            //返回查詢結果集用於儲存資料庫查詢內容
            rs = preparedStatement.executeQuery();
            //遍歷結果集拿到資料
            while (rs.next()) {
                System.out.println("id" + "\t" + rs.getString("id"));
                System.out.println("name" + "\t" + rs.getString("name"));
                System.out.println("age" + "\t" + rs.getString("age"));
                System.out.println("email" + "\t" + rs.getString("email"));
                System.out.println("manager_id" + "\t" + rs.getString("manager_id"));
                System.out.println("create_time" + "\t" + rs.getString("create_time"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //執行完資料庫操作後記得關閉資料庫連線資源
            try{
                rs.close();
                preparedStatement.close();
                connection.close();

            }catch (SQLException e){
                e.printStackTrace();
            }

        }
    }

更新操作

 public int update() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //這裡引數用?佔位符表示
        String sql = "update student set hobby=? where id = 11";
        try {
            //獲取資料連線
            connection = basicUse.getConnection();

            //獲取傳送sql指令執行sql進行預編譯
            preparedStatement = connection.prepareStatement(sql);

            //設定sql語句引數索引從1開始
            preparedStatement.setObject(1, "足球");


            System.out.println("執行sql" + sql);
            //執行成功返回1
            int success = preparedStatement.executeUpdate(); //注意這裡不需要在放入sql
            return success;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            //執行完資料庫操作後記得關閉資料庫連線資源
            try {

                preparedStatement.close();
                connection.close();

            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

刪除操作

    public int del() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //這裡引數用?佔位符表示
        String sql = "delete from student where id=?";
        try {
            //獲取資料連線
            connection = basicUse.getConnection();

            //獲取傳送sql指令執行sql進行預編譯
            preparedStatement = connection.prepareStatement(sql);

            //設定sql語句引數索引從1開始
            preparedStatement.setObject(1, 11);


            System.out.println("執行sql" + sql);
            //執行成功返回1
            int success = preparedStatement.executeUpdate(); //注意這裡不需要在放入sql
            return success;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            //執行完資料庫操作後記得關閉資料庫連線資源
            try {

                preparedStatement.close();
                connection.close();

            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

批量操作

 public int[] updateBatch() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        //這裡引數用?佔位符表示
        String sql = "update student set hobby=? where id = 11";
        try {
            //獲取資料連線
            connection = basicUse.getConnection();

            //獲取傳送sql指令執行sql進行預編譯
            preparedStatement = connection.prepareStatement(sql);

            //批量執行sql
            for (int i = 12; i <= 16; i++) {
                //設定sql語句引數索引從1開始
                preparedStatement.setObject(1, i);
                preparedStatement.addBatch();
            }
            System.out.println("執行sql" + sql);
            //執行成功返回更新計數的陣列
            int[] success = preparedStatement.executeBatch(); //注意這裡不需要在放入sql
            return success;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //執行完資料庫操作後記得關閉資料庫連線資源
            try {

                preparedStatement.close();
                connection.close();

            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

使用預編譯的好處

1:PreparedStatement比 Statement 更快
使用 PreparedStatement 最重要的一點好處是它擁有更佳的效能優勢,SQL語句會預編譯在資料庫系統中。執行計劃同樣會被快取起來,它允許資料庫做引數化查詢。使用預處理語句比普通的查詢更快,因為它做的工作更少(資料庫對SQL語句的分析,編譯,優化已經在第一次查詢前完成了)。

2:PreparedStatement可以防止SQL注入式攻擊
SQL 注入攻擊:SQL 注入是利用某些系統沒有對使用者輸入的資料進行充分的檢查,而在使用者輸入資料中注入非法的 SQL 語句段或命令,從而利用系統的 SQL 引擎完成惡意行為的做法。

比如:某個網站的登入驗證SQL查詢程式碼為:
strSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"
惡意填入:
userName = "1' OR '1'='1"; passWord = "1' OR '1'='1";
那麼最終SQL語句變成了:
strSQL = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"
因為WHERE條件恆為真,這就相當於執行:
strSQL = "SELECT * FROM users;"
因此可以達到無賬號密碼亦可登入網站。
如果惡意使用者要是更壞一點,SQL語句變成:
strSQL = "SELECT * FROM users WHERE name = 'any_value' and pw = ''; DROP TABLE users"
這樣一來,雖然沒有登入,但是資料表都被刪除了。

使用PreparedStatement的引數化的查詢可以阻止大部分的SQL注入。在使用引數化查詢的情況下,資料庫系統不會將引數的內容視為SQL指令的一部分來處理,而是在資料庫完成SQL指令的編譯後,才套用引數執行,因此就算引數中含有破壞性的指令,也不會被資料庫所執行。因為對於引數化查詢來說,查詢SQL語句的格式是已經規定好了的,需要查的資料也設定好了,缺的只是具體的那幾個資料而已。所以使用者能提供的只是資料,而且只能按需提供,無法更進一步做出影響資料庫的其他舉動來。

完整專案案例
點選這裡 github