【前言】在上一篇文章中,我們說到了JDBC在操作資料庫的同時,需要建立執行對象。對象分為:Statement和PrepareStatement兩種,那麼這兩者之間,又有何種淵源呢。
剛一開始,不瞭解的時候,去網上看,上邊將PrepareStatement的逼格吹得很高,比如說:在JDBC應用中,如果你已經是稍有水平的開發人員,你就應該始終以PrepareStatement代替Statement。也就是說,在任何時候都不要使用Statement。看到這裡,小編內心不禁暗暗的下定決心,以後也要使用PrepareStatement。
那麼,在真實的開發中,它們兩者又是孰優孰劣呢。
上一篇,在JDBC建立對象並執行SQL語句的時候,我們可以知道,兩者在這方面的區別:
<span style="font-family:Microsoft YaHei;font-size:14px;"> //建立語句執行對象 //需要執行的sql語句 String sql = "select user_name,password from t_user where user_id=?"; //使用Statement對象 Statement st=conn.createStatement(); //使用PrepareStatement對象 PreparedStatement pst = conn.createStatement(sql); //執行語句 //使用Statement對象 st.executeQuery("sql"); //使用PrepareStatement對象 pst.executeQuery(); </span>
可以看到,兩者在使用時,使用sql的位置不同。PrepareStatement採用了先行編譯的方式,而Statement採用的是執行時建立。
在JDBC的API中,Statement要求開發人員付出大量的時間和精力,在使用Statement擷取JDBC訪問時,所具有的一個共同的問題就是輸入適當的格式的日期和時間戳記。而這個問題,通過採用PrepareStatement可以自動解決。同時,PrepareStatement的另外一個優點字串不是動態建立的。
OK,說到這裡,我們可以初步斷定,網上把PrepareStatement的逼格吹得那麼高,還是有一定的緣由的。那麼,為什麼要說:在JDBC應用中,如果你已經是稍有水平的開發人員,你就應該始終以PrepareStatement代替Statement。難道使用PrepareStatement僅僅就是為了逼格高一些嗎。顯然不是吧,下邊小編就給您好好的嘮嘮。
JDBC驅動的最佳化基於使用的是什麼功能。選擇PrepareStatement還是Statement取決於你要怎樣使用它們。對於執行一次的SQL語句,選擇Statement是最好的;相反,如果SQL語句需要被多次執行,那麼我們可以選用PrepareStatement。
1、PrepareStatement 提高效能
PrepareStatement屬於先行編譯的方式,每一種資料庫都會盡最大努力對先行編譯語句提供最大的效能最佳化。因為先行編譯語句有可能被重複調用,所以sql語句在被資料庫的編譯器編譯後,執行代碼被緩衝下來,那麼下次調用的時候,只要相同的先行編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中,就會執行。當然,這並不是說只有一個Connection中多次執行的先行編譯語句被緩衝,而是對整個資料庫中。只要先行編譯的語句和緩衝中相匹配,那麼在任何時候就可以 再次進行編譯就可以直接執行。
而在Statement的語句中,即使是相同的操作,由於每次操作的資料不同,所以整個語句相匹配的機會極小,幾乎不太可能完全符合。比如:
<span style="font-family:Microsoft YaHei;font-size:14px;">insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');</span>
即使是相同的操作,但是因為插入的資料內容不同,所以整個語句本身並不能匹配,沒有緩衝語句的意義。
當然,並不是所有的先行編譯語句都一定會被緩衝,資料庫本身會使用一定的策略。比如使用訪問頻率等指標,來決定什麼時候不在緩衝已有的先行編譯結果,以儲存更多的儲存空間來儲存新的先行編譯語句。
2、提高代碼的可讀性和可維護性
一個資料庫插入的Demo
<span style="font-family:Microsoft YaHei;font-size:14px;">//執行一個插入操作//StatementStatement st; st.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')"); //PrepareStatement PreparedStatement pst; pst = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)"); pst.setString(1,var1); pst.setString(2,var2); pst.setString(3,var3); pst.setString(4,var4); pst.executeUpdate();</span>
雖然,使用PrepareStatement會比Statement多幾行代碼,但是這樣的改變,無論是從代碼可讀性還是可維護性上來講,都是值得的。
3、安全性
PrepareStatement防止了SQL注入,提高了程式的安全性。
<span style="font-family:Microsoft YaHei;font-size:14px;">String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";</span>
在這裡,如果我們把[‘or’1'='1]作為varpasswd傳入進來,使用者名稱隨意填寫,那麼就會產生SQL注入問題。
<span style="font-family:Microsoft YaHei;font-size:14px;">select * from tb_name = '隨意' and passwd = '' or '1' = '1';</span>
因為‘1’=‘1’肯定成立,所以可以通過任何驗證者。
更進一步,把[';drop table tb_name;]作為varpasswd傳入進來
<span style="font-family:Microsoft YaHei;font-size:14px;">select * from tb_name = '隨意' and passwd = '';drop table tb_name;</span>
在某些資料庫上,這些語句完全可以執行。
當然,如果使用先行編譯語句,傳入的任何內容就不會和原來的語句發生任何匹配的關係。使用PrepareStatement,先行編譯語句的話,就不用對傳入的資料做任何考慮;如果使用Statement,就需要對drop等做判斷,防止SQL注入。
【尾巴】
遇到不懂的問題,上網去查,站在巨人的肩膀上,不失為一個很好的方法。但是,對於巨人們對問題的理解,我們還需要進一步的去消化,只有這樣,我們才能夠成為巨人。