1. 程式人生 > 實用技巧 >Statement和preparedStatement之間的區別?

Statement和preparedStatement之間的區別?

根據JDBC模擬使用者功能的實現: loginUser.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mydatabase
user=root
password=333

package com.java.JDBC;


import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;


/*
實現功能:
    1 需求:模擬使用者登入功能的實現。
    2 業務描述:
        程式執行的時候,提供一個輸入的入口,可以讓使用者輸入使用者名稱和密碼。
        使用者輸入使用者名稱和密碼之後,提交資訊,java程式收集使用者資訊
        Java程式連線資料庫驗證使用者名稱和密碼是否合法。
        合法:顯示登陸成功。
        不合法:顯示登陸失敗。
    3 資料的準備
        在實際開發中,表的設計會使用專業的建模工具,我們這裡安裝一個建模工具:PowerDesigner
        使用PD工具來進行資料庫表的設計。(參見user-login.sql指令碼)
        
    4 當前程式存在的問題:
        使用者名稱:fdsa
        密碼:fdsa' or '1'='1
        登入成功
        這種現象被稱為SQL注入(安全隱患)。(黑客經常使用)
        
    5 導致SQL注入的根本原因是什麼?
        select * from t_user where loginName = 'fdsa' and loginpwd = 'fdsa' or '1'='1';
        使用者輸入的資訊中含有sql語句的關鍵字,並且這些關鍵字參與sql語句的編譯過程,導致sql語句的原意被扭曲,從而達到sql注入。
    
    
*/ public class JDBCTest06 { public static void main(String[] args) { // 初始化一個介面 Map<String,String> userLoginInfo = initUI(); // 驗證使用者名稱和密碼 boolean loginSuccess = login(userLoginInfo); System.out.println(loginSuccess == true ? "登陸成功" : "登陸失敗"); } /**
* 使用者登入 * @param userLoginInfo 使用者登入資訊 * @return true表示成功 false表示失敗 */ private static boolean login(Map<String, String> userLoginInfo) { // JDBC程式碼 Connection conn = null; Statement stmt = null; ResultSet rs = null; boolean result = false
; ResourceBundle rb = ResourceBundle.getBundle("loginUser"); String driver = rb.getString("driver"); String url = rb.getString("url"); String user = rb.getString("user"); String password = rb.getString("password"); String loginName = userLoginInfo.get("loginName"); String loginpwd = userLoginInfo.get("password"); try { // 1 註冊驅動 // Class.forName("com.mysql.jdbc.Driver"); Class.forName(driver); // 2 獲取連線 // conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","333"); conn = DriverManager.getConnection(url,user,password); // 3 獲取資料庫操作物件 stmt = conn.createStatement(); // 4 執行sql語句 String sql = "select * from t_user where loginName = '" + loginName + "' and loginpwd = '" + loginpwd +"'"; // 以上正好完成了sql語句的拼接,以下程式碼的含義是,傳送sql語句給DBMS進行sql編譯。 // 正好將使用者提供的“非法資訊”編譯進去。導致了原sql語句的含義被扭曲了。 System.out.println(sql); rs = stmt.executeQuery(sql); // 5 處理返回結果集 if (rs.next()){ result = true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 6 釋放資源 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } return result; } /** * 初始化使用者介面 * @return 使用者輸入的使用者名稱和密碼等登陸資訊 */ private static Map<String, String> initUI() { Scanner scanner = new Scanner(System.in); System.out.print("使用者名稱: "); String username = scanner.nextLine(); System.out.print("密碼: "); String password = scanner.nextLine(); Map<String,String> userLoginInfo = new HashMap<>(); userLoginInfo.put("loginName",username); userLoginInfo.put("password",password); return userLoginInfo; } }

案例7:如何解決JDBC注入問題及Statement和PreparedStatement之間的區別?

package com.java.JDBC;


import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/*
1 解決sql注入問題
    只要使用者提供的資訊不參與sql語句的編譯過程,問題就解決了。
    即使使用者提供的資訊中心含有sql語句的關鍵字,但是沒有參與編譯,不起作用。
    要想使用者資訊不參與sql語句的編譯,必須使用java.sql.PreparedStatement
    PreparedStatement介面繼承了java.sql.Statement
    PreparedStatement是屬於預編譯的資料庫作業系統
    PreparedStatement原理是:預先對sql語句的框架進行編譯,然後再給sql語句傳“值”
    
2 解決sql注入的關鍵是什麼?
    使用者提供的資訊中即使含有sql語句的關鍵字,但是這些關鍵字並沒有參與編譯,不起作用。
    
3 對比一下Statement和PreparedStatement?
    - Statement存在sql注入問題,PreparedStatement解決了sql注入問題。
    - Statement是編譯一次執行一次,PreparedStatement是編譯一次,可執行n次。PreparedStatement效率較高一些。
    - PreparedStatement會在編譯階段做型別的安全檢查。
    
    綜上所述:PreparedStatement使用較多,只有極少數的情況下需要使用Statement


4 什麼情況下必須使用Statement呢?
    業務方面要求必須支援SQL注入的時候。
    Statement支援sql注入,凡是業務方面要求是需要進行sql語句拼接的,必須使用Statement。
    
*/
public class JDBCTest07 {
    public static void main(String[] args) {
        // 初始化一個介面
        Map<String,String> userLoginInfo = initUI();
        // 驗證使用者名稱和密碼
        boolean loginSuccess = login(userLoginInfo);
        System.out.println(loginSuccess == true ? "登陸成功" : "登陸失敗");
    }
    /**
     * 使用者登入
     * @param userLoginInfo 使用者登入資訊
     * @return true表示成功  false表示失敗
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        // JDBC程式碼
        Connection conn = null;
        PreparedStatement ps = null; // 這裡使用PreparedStatement
        ResultSet rs = null;
        boolean result = false;
        ResourceBundle rb = ResourceBundle.getBundle("loginUser");
        
        String driver = rb.getString("driver");
        String url = rb.getString("url");
        String user = rb.getString("user");
        String password = rb.getString("password");
        
        String loginName = userLoginInfo.get("loginName");
        String loginpwd = userLoginInfo.get("password");
        
        try {
            // 1 註冊驅動
            // Class.forName("com.mysql.jdbc.Driver");
            Class.forName(driver);
            
            // 2 獲取連線
            // conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","333");
            conn = DriverManager.getConnection(url,user,password);
            
            // 3 獲取預編譯的資料庫操作物件
            // sql語句的框子,其中一個?,表示一個佔位符,一個?將來接收一個“值”,注意:佔位符不能使用單引號括起來
            String sql = "select * from t_user where loginName = ? and loginpwd = ?";
            
            // 程式執行到此處,會發送slq語句框子給DBMS,然後DBMS進行sql語句的預先編譯。
            ps = conn.prepareStatement(sql);
            
            // 給佔位符?傳值(第1個問號下標是1,第2個問號下標是2,JDBC中所有下標從1開始)
            ps.setString(1,loginName);
            ps.setString(2,loginpwd);
            
            // 4 執行sql語句
            rs = ps.executeQuery();
            
            // 5 處理返回結果集
            if (rs.next()){
                result = true;
            }
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 6 釋放資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
    
    /**
     * 初始化使用者介面
     * @return 使用者輸入的使用者名稱和密碼等登陸資訊
     */
    private static Map<String, String> initUI() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("使用者名稱: ");
        String username = scanner.nextLine();
        
        System.out.print("密碼: ");
        String password = scanner.nextLine();
        
        Map<String,String> userLoginInfo = new HashMap<>();
        userLoginInfo.put("loginName",username);
        userLoginInfo.put("password",password);
        return userLoginInfo;
    }
}

案例8:使用Statement可以對查詢結果集進行升序和降序,但是用PreparedStatement不行,因為他會預編譯。
package com.java.JDBC;


import java.sql.*;
import java.util.Scanner;


public class JDBCTest08 {
    public static void main(String[] args) {
        /*// 使用者在控制檯輸入desc就是降序,輸入asc就是升序
        Scanner s = new Scanner(System.in);
        System.out.println("請輸入desc或asc,desc表示降序,asc表示升序");
        System.out.print("請輸入: ");
        String keyWords = s.nextLine();
        
        // 執行sql
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 獲取資料庫連線
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","333");
            // 獲取資料庫物件
            String sql = "select ename from emp order by ename ?";
            ps = conn.prepareStatement(sql);
            // 執行sql語句
            ps.setString(1,keyWords);
            // 處理查詢結果集
            rs = ps.executeQuery();
            // 遍歷結果集
            while (rs.next()){
                String ename = rs.getString("ename");
                System.out.println(ename);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }*/
        
        
        // 使用者在控制檯輸入desc就是降序,輸入asc就是升序
        Scanner s = new Scanner(System.in);
        System.out.println("請輸入desc或asc,desc表示降序,asc表示升序");
        System.out.print("請輸入: ");
        String keyWords = s.nextLine();
    
        // 執行sql
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 註冊驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 獲取資料庫連線
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase","root","333");
            // 獲取資料庫物件
            stmt = conn.createStatement();
            // 執行sql語句
            String sql = "select ename from emp order by ename " + keyWords;
            // 處理查詢結果集
            rs = stmt.executeQuery(sql);
            // 遍歷結果集
            while (rs.next()){
                String ename = rs.getString("ename");
                System.out.println(ename);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}