JDBC Statement與PreparedStatement的區(qū)別

JDBC預(yù)編譯sql語句處理

時間:20180311


使用Statement與PreparedStatement執(zhí)行DDL與DML的區(qū)別惭墓?

/**
 * jdbc通用方法
 * @author mengjie
 *
 */
public class JdbcUtil {
    //url
    private static String url = "jdbc:mysql://localhost:3306/day16";
    //user 
    private static String user  = "root";
    //password
    private static String password = "root";
    
    /**
     *z只注冊一次驅(qū)動菠赚,靜態(tài)代碼塊 
     */
    static {
        //注冊驅(qū)動程序
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    /**
     * 獲取連接的方法
     * @throws SQLException 
     */
    public static Connection getConnection() {
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 釋放資源的方法
     */
    public static void close(Statement stmt, Connection conn) {
        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
    
    /**
     * 釋放資源的方法
     */
    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if(rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
}
/**
 * 使用PreparedStatement執(zhí)行CRUD操作
 * 執(zhí)行預(yù)編譯的sql語句
 * @author mengjie
 *
 */
public class Demo1 {
    public static void main(String[] args) {
        //insert();
        //update();
        query();
    }

    private static void query() {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet res = null;
        try{
            //1)建立連接
            conn = JdbcUtil.getConnection();
            //2) 創(chuàng)建sql
            String sql = "select * from student where id = ?";
            //3) 創(chuàng)建PreparedStatement,預(yù)編譯sql語句
            stmt = conn.prepareStatement(sql);
            //4) 給參數(shù)賦值
            /**
             * 注意參數(shù)的位置朽砰,一定要對應(yīng)字段的位置
             */
            stmt.setInt(1, 5);
            //5) 執(zhí)行sql
            res = stmt.executeQuery();
            while(res.next()) {
                int id = res.getInt("id");
                String name = res.getString("name");
                int age = res.getInt("age");
                System.out.println(id + "\t" +name+"\t"+age);
            }
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtil.close(stmt, conn);
        }
    }

    private static void update() {
        /**
         * 修改
         */
        Connection conn = null;
        PreparedStatement stmt = null;
        try{
            //1)建立連接
            conn = JdbcUtil.getConnection();
            //2) 創(chuàng)建sql
            String sql = "update student set name = ?, age = ? where id = ?";
            //3) 創(chuàng)建PreparedStatement,預(yù)編譯sql語句
            stmt = conn.prepareStatement(sql);
            //4) 給參數(shù)賦值
            /**
             * 注意參數(shù)的位置,一定要對應(yīng)字段的位置
             */
            stmt.setInt(3, 4);
            stmt.setString(1, "wanzhe");
            stmt.setInt(2, 100);
            //5) 執(zhí)行sql
            int n = stmt.executeUpdate();
            System.out.println("影響了"+ n + "行");
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtil.close(stmt, conn);
        }
    }

    private static void insert() {
        /**
         * 插入
         */
        Connection conn = null;
        PreparedStatement stmt = null;
        try {
            //1)獲取連接
            conn = JdbcUtil.getConnection();
            //2)準(zhǔn)備sql//?號是參數(shù)的占位符农猬,一個問好代表是一個參數(shù)凶异。
            String sql = "insert into student(id,name,age) values(?,?,?)";//
            //3)創(chuàng)建PreparedStatement,預(yù)編譯sql語句
            stmt = conn.prepareStatement(sql);//預(yù)編譯sql語句(sql語法和權(quán)限的檢查)棉胀,
            //4)給參數(shù)賦值
            /**
             * 參數(shù)一: 參數(shù)的位置。從1開始
             * 參數(shù)二:參數(shù)值
             */
            stmt.setInt(1, 5);
            stmt.setString(2, "里斯");
            stmt.setInt(3, 40);
            //5)發(fā)送參數(shù)到數(shù)據(jù)庫秽浇,執(zhí)行sql浮庐,
            //executeUpdate方法中并沒有參數(shù),是將參數(shù)和sql組裝起來柬焕,并執(zhí)行
            int n = stmt.executeUpdate();
            System.out.println("影響了"+ n + "行");
        }catch(Exception e){
            e.printStackTrace( );
        }finally {
            //關(guān)閉資源审残,而且不用更改close方法(此時傳遞的stmt是preparedStatement對象),不用重載
            //實現(xiàn)參數(shù)為preparedStatement對象的close方法
            //原因是statement是preparedstatement類的父類
            JdbcUtil.close(stmt, conn);
        }
    }
}

為什么要使用PreparedStatement?

1.PreparedStatement接口繼承了Statement斑举,PreparedStatement實例包含已編譯的SQL語句搅轿,所以其執(zhí)行速度要快于Statement對象。
2.作為Statement的子類富玷,PreparedStatment繼承了Statement的所有功能璧坟。三種方法execute,executeQuery和executeUpdate已被更改以使之不再需要參數(shù)赎懦。
3.在JDBC應(yīng)用中雀鹃,在任何時候都不要是使用Statement,原因如下:
??1)铲敛、代碼的可讀性和可維護(hù)性褐澎。Statement需要不斷地拼接,而PreparedStatement不會伐蒋。
??2)工三、PreparedStatement盡最大可能提高性能。DB有緩存機制先鱼,相同的預(yù)編譯語句再次被調(diào)用不會再次需要編譯俭正。
??3)、最重要的一點是極大的提高了安全性焙畔。Statement容易被SQL注入掸读,而PreparedStatement傳入的內(nèi)容和參數(shù)不會和sql語句發(fā)生任何匹配關(guān)系。

關(guān)于statement和PreparedStatement的安全性做具體分析如下:

例如用戶登陸的案例
在數(shù)據(jù)庫中操作:

--創(chuàng)建用戶表
CREATE TABLE user_list(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(20),
    PASSWORD VARCHAR(20)
)
--插入數(shù)據(jù)
INSERT INTO user_list(NAME,PASSWORD) VALUES('eric','123456');
INSERT INTO user_list(NAME,PASSWORD) VALUES('jacky','654321');
SELECT * FROM user_list;

如何判斷登陸是否成功呢?根據(jù)下列sql:

--登陸成功
SELECT * FROM user_list WHERE NAME='eric' AND PASSWORD='123456'
能成功登陸的情況

但是在數(shù)據(jù)庫sql中存在如下情況:

SELECT * FROM user_list WHERE 1=1; --1=1; 恒成立
sql恒成立的情況

登陸失敗的情況

SELECT * FROM user_list WHERE NAME='rose' AND PASSWORD='123456'

可將上述sql更改為恒成立情況儿惫,注意-- 前后的空格

SELECT * FROM user_list WHERE NAME ='rose' OR 1=1 -- ' AND PASSWORD='123456';
sql注入的情況

以上都是在客戶端中執(zhí)行的sql語句進(jìn)行的測試對比
分割線


以下都是在代碼中執(zhí)行的sql語句進(jìn)行的測試對比

分別在Statement和PreparedStatement中執(zhí)行帶有sql注入的sql澡罚,驗證Statement和PreparedStatement的安全性。

  • Statement情況
public class Demo2 {
    private static Connection conn = null;
    private static Statement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args) throws Exception {
                String username = "rose";
                String password = "123456";
        conn = JdbcUtil.getConnection();
        stmt = conn.createStatement();
        String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
        res = stmt.executeQuery(sql);
        if(res.next()) {
            System.out.println("登陸成功");
        }else {
            System.out.println("登陸失敗");
        }
    }
}

結(jié)果:
登陸失敗

  • Statement SQL注入的情況
public class Demo2 {
    private static Connection conn = null;
    private static Statement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args) throws Exception {
                String username = "rose' OR 1=1 -- ";
                String password = "123456";
        conn = JdbcUtil.getConnection();
        stmt = conn.createStatement();
        String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
        res = stmt.executeQuery(sql);
        if(res.next()) {
            System.out.println("登陸成功");
        }else {
            System.out.println("登陸失敗");
        }
    }
}

結(jié)果:
登陸成功

  • PreparedStatement情況
public class Demo2 {
    private static Connection conn = null;
    private static PreparedStatement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args){
        conn = JdbcUtil.getConnection();
        String sql = "SELECT * FROM user_list WHERE NAME = ? AND PASSWORD= ?";
        try {
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, "rose' OR 1=1 -- ");
            stmt.setString(2, "123456");
            res = stmt.executeQuery();
            if(res.next()) {
                System.out.println("登陸成功");
            }else {
                System.out.println("登陸失敗");
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.close(res,stmt,conn);
        }
    }
}

結(jié)果:
登陸失敗

解析PreparedStatement是如何防止SQL注入的肾请?

  • 執(zhí)行stmt = conn.prepareStatement(sql);(此句話叫預(yù)編譯sql)1.先將sql發(fā)送至數(shù)據(jù)庫留搔。2.發(fā)送之后驗證sql的語法。3.驗證sql中語法發(fā)現(xiàn)sql中有兩個參數(shù)NAME铛铁、PSASSWORD(該sql執(zhí)行時必須要有NAME隔显、PSASSWORD兩個個參數(shù))。
  • 執(zhí)行stmt.setString(1, "rose' OR 1=1 -- ");
    stmt.setString(2, "123456");參數(shù)賦值饵逐。
  • 執(zhí)行stmt.executeQuery();即執(zhí)行sql括眠,就將賦值的兩個參數(shù)塞進(jìn)驗證sql語法驗證的兩個參數(shù)中去。此時"rose' OR 1=1 -- "是作為NAME的參數(shù)倍权,因此回去數(shù)據(jù)庫中查找NAME名為"rose' OR 1=1 -- "的數(shù)據(jù)掷豺,顯然是查詢不到的,說明PreparedStatement傳入的內(nèi)容不會和sql語句發(fā)生任何關(guān)系
  • Statement中并不進(jìn)行sql語法驗證(參數(shù)驗證)
Statement的情況

說明PreparedStatement傳入的內(nèi)容不會和sql語句發(fā)生任何關(guān)系账锹,這也是statement中的sql(靜態(tài)sql)能被注入sql的原因萌业。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奸柬,隨后出現(xiàn)的幾起案子生年,更是在濱河造成了極大的恐慌,老刑警劉巖廓奕,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抱婉,死亡現(xiàn)場離奇詭異,居然都是意外死亡桌粉,警方通過查閱死者的電腦和手機蒸绩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铃肯,“玉大人患亿,你說我怎么就攤上這事⊙罕疲” “怎么了步藕?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挑格。 經(jīng)常有香客問我咙冗,道長,這世上最難降的妖魔是什么漂彤? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任雾消,我火速辦了婚禮灾搏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘立润。我一直安慰自己狂窑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布范删。 她就那樣靜靜地躺著蕾域,像睡著了一般。 火紅的嫁衣襯著肌膚如雪到旦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天巨缘,我揣著相機與錄音添忘,去河邊找鬼。 笑死若锁,一個胖子當(dāng)著我的面吹牛搁骑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播又固,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼仲器,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仰冠?” 一聲冷哼從身側(cè)響起乏冀,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洋只,沒想到半個月后辆沦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡识虚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年肢扯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担锤。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔚晨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肛循,到底是詐尸還是另有隱情铭腕,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布育拨,位于F島的核電站谨履,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏熬丧。R本人自食惡果不足惜笋粟,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一怀挠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧害捕,春花似錦绿淋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盾沫,卻和暖如春裁赠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赴精。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工佩捞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕾哟。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓一忱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谭确。 傳聞我的和親對象是個殘疾皇子帘营,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容