1. 程式人生 > >三、使用PreparedStatement進行sql語句預處理

三、使用PreparedStatement進行sql語句預處理

PreparedStatement

public interface PreparedStatement extends Statement;可以看到PreparedStatement是Statement的子介面,我們在執行查詢或者更新資料表資料的時候,拼寫SQL語句是一個很費力並且容易出錯的事情,PreparedStatement可以簡化這樣的一個過程.

PreParedStatement
1).why?我們為什麼要使用它
使用Statement需要進行拼寫SQl語句,辛苦並且容易出錯,之前使用Statement的SQL語句的形式是這樣的

String sql = "insert into examstudent" + " values("
+ student.getFlowId() + "," + student.getType() + ",'"
+ student.getIdCard() + "','" + student.getExamCard() + "','"
+ student.getStudentName() + "','" + student.getLocation()
+ "'," + student.getGrade() + ")";

使用PreparedStatement:是Statement的子介面,可以傳入帶佔位符的SQL語句,提供了補充佔位符變數的方法

PreparedStatement ps=conn.preparedStatement(sql);

可以看到將sql作為引數傳入了,就不需要我們在費力拼寫了。

2)變成了這樣的形式

String sql="insert into examstudent values(?,?,?,?,?,?,?)";

可以呼叫PreparedStatement的setXxx(int index,Object val)設定佔位符的值,其中index的值從1開始

執行SQl語句:excuteQuery()或者excuteUpdate()就可以完成查詢或者資料的更新.【注意】:此時函式的引數位置不需要傳入SQL語句,注意同使用Statement的update函式的差別

@Test
    public void testPreparedStatement() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            // 連線資料庫
            connection = JDBCTools.getConnection();
            // 使用佔位符的SQl語句
            String sql = "insert into customers(name,email,birth)"
                    + "values(?,?,?)";
            // 使用preparedStatement的setXxx方法設定每一個位置上的值
            preparedStatement = connection.prepareStatement(sql);
            // 設定name欄位
            preparedStatement.setString(1, "ATGUIGU");
            // 設定email欄位
            preparedStatement.setString(2, "
[email protected]
"); // 設定birth欄位 preparedStatement.setDate(3, new Date(new java.util.Date().getTime())); // 執行更新操作 preparedStatement.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { // 釋放資源 JDBCTools.release(null, preparedStatement, connection); } }

使用PreparedStatement執行SQl(更新操作:插入、刪除、更新,但不包括select查詢操作),JDBCTools中的通用函式update更改成下面的形式:這裡使用了可變引數,而不是使用陣列

public static void update(String sql,Object ...args){
        /**
         * 執行SQL語句,使用PreparedStatement
         */
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        try {
            connection=JDBCTools.getConnection();
            preparedStatement=connection.prepareStatement(sql);
            for(int i=0;i<args.length;i++){
                preparedStatement.setObject(i+1, args[i]);
            }
            preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCTools.release(null, preparedStatement, connection);
        }
    }

使用PreparedStatement的好處:

1).提高程式碼的可讀性和可維護性;

2).最大程度的提高效能:JDBC驅動的最佳化是基於使用的是什麼功能. 選擇PreparedStatement還是Statement取決於你要怎麼使用它們. 對於只執行一次的SQL語句選擇Statement是最好的. 相反, 如果SQL語句被多次執行選用PreparedStatement是最好的.PreparedStatement的第一次執行消耗是很高的. 它的效能體現在後面的重複執行(快取的作用). 例如, 假設我使用Employee ID, 使用prepared的方式來執行一個針對Employee表的查詢. JDBC驅動會發送一個網路請求到資料解析和優化這個查詢. 而執行時會產生另一個網路請求. 在JDBC驅動中,減少網路通訊是最終的目的. 如果我的程式在執行期間只需要一次請求, 那麼就使用Statement. 對於Statement, 同一個查詢只會產生一次網路到資料庫的通訊.當使用PreparedStatement池時, 如果一個查詢很特殊, 並且不太會再次執行到, 那麼可以使用Statement. 如果一個查詢很少會被執行,但連線池中的Statement池可能被再次執行, 那麼請使用PreparedStatement. 在不是Statement池的同樣情況下, 請使用Statement.

3).可以防止SQL注入

SQL注入指的是通過構建特殊的輸入作為引數傳入Web應用程式,而這些輸入大都是SQL語法裡的一些組合,通過執行SQL語句進而執行攻擊者所要的操作,其主要原因是程式沒有細緻地過濾使用者輸入的資料,致使非法資料侵入系統。

比如我們新建一個數據表users,表中有兩個欄位username和password;

我們在圖形化介面SQLyog的sql語句的查詢介面輸入這樣的查詢語句:select * from users where username='a' or password='and password=' or '1'='1';

執行該語句,會得到我們表中的資料:

我們可以分析一下這條語句:where的後面,通過多個欄位的組合作為查詢過濾的條件。

欄位一:username='a'

欄位二:password='and password='

欄位三:'1'='1'

因為用邏輯連線符OR來連線的三個欄位,只要有一個為真就可以將查詢工作完成.

下面我們看下具體的程式碼實現:

@Test
    public void testSQLinjection() {
        String username = "a' or password =";
        String password = " or '1'='1";
        String sql = "select * from users where username='" + username
                + "' AND " + "password='" + password + "'";
        System.out.println(sql);
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            if (resultSet.next()) {
                System.out.println("登陸成功");
            } else {
                System.out.println("不匹配");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(resultSet, statement, connection);
        }
    }

執行結果:

select * from users where username='a' or password =' AND password=' or '1'='1'
登陸成功

可以看到我們的SQl語句中都沒有明確我們要查的欄位的名,但是還是獲取了查詢的結果(SQL語句太能混了)

於是,我們用了PreparedStatement就可以解決SQL注入的問題。

@Test
    public void testSQLinjection2() {
        String username = "a' or password =";
        String password = " or '1'='1";
        String sql = "select * from users where username=?" + " and password=?";
        System.out.println(sql);
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, username);
            preparedStatement.setString(2, password);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                System.out.println("登陸成功");
            } else {
                System.out.println("不匹配");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.release(resultSet, preparedStatement, connection);
        }
    }

執行結果:

select * from users where username=? and password=?
不匹配

可以看到:再次使用偽裝後的SQL語句已經不能獲取我們資料表中的資訊,我們這裡在sql語句中使用了佔位符。因此使用PreparedStatement可以結解決這裡的SQL注入的問題。