1.PreparedStatement是預編譯的,對於批量處理能夠大大提升效率. 也叫JDBC存儲過程java
2.使用 Statement 對象。在對數據庫只執行一次性存取的時侯,用 Statement 對象進行處理。PreparedStatement 對象的開銷比Statement大,對於一次性操做並不會帶來額外的好處。
3.statement每次執行sql語句,相關數據庫都要執行sql語句的編譯,preparedstatement是預編譯得, preparedstatement支持批處理mysql
四、sql
Code Fragment 1:數據庫
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′"; stmt.executeUpdate(updateString);
Code Fragment 2:緩存
PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? "); updateSales.setInt(1, 75); updateSales.setString(2, "Colombian"); updateSales.executeUpdate();
片段2和片段1的區別在於,後者使用了PreparedStatement對象,而前者是普通的Statement對象。PreparedStatement對象不只包含了SQL語句,並且大多數狀況下這個語句已經被預編譯過,於是當其執行時,只需DBMS運行SQL語句,而沒必要先編譯。當你須要執行Statement對象屢次的時候,PreparedStatement對象將會大大下降運行時間,固然也加快了訪問數據庫的速度。
這種轉換也給你帶來很大的便利,沒必要重複SQL語句的句法,而只需更改其中變量的值,即可從新執行SQL語句。選擇PreparedStatement對象與否,在於相同句法的SQL語句是否執行了屢次,並且兩次之間的差異僅僅是變量的不一樣。若是僅僅執行了一次的話,它應該和普通的對象毫無差別,體現不出它預編譯的優越性。安全
5.執行許多SQL語句的JDBC程序產生大量的Statement和PreparedStatement對象。一般認爲PreparedStatement對象比Statement對象更有效,特別是若是帶有不一樣參數的同一SQL語句被屢次執行的時候。PreparedStatement對象容許數據庫預編譯SQL語句,這樣在隨後的運行中能夠節省時間並增長代碼的可讀性。性能優化
然而,在Oracle環境中,開發人員實際上有更大的靈活性。當使用Statement或PreparedStatement對象時,Oracle數據庫會緩存SQL語句以便之後使用。在一些狀況下,因爲驅動器自身須要額外的處理和在Java應用程序和Oracle服務器間增長的網絡活動,執行PreparedStatement對象實際上會花更長的時間。
然而,除了緩衝的問題以外,至少還有一個更好的緣由使咱們在企業應用程序中更喜歡使用PreparedStatement對象,那就是安全性。傳遞給PreparedStatement對象的參數能夠被強制進行類型轉換,使開發人員能夠確保在插入或查詢數據時與底層的數據庫格式匹配。
當處理公共Web站點上的用戶傳來的數據的時候,安全性的問題就變得極爲重要。傳遞給PreparedStatement的字符串參數會自動被驅動器忽略。最簡單的狀況下,這就意味着當你的程序試着將字符串「D'Angelo」插入到VARCHAR2中時,該語句將不會識別第一個「,」,從而致使悲慘的失敗。幾乎不多有必要建立你本身的字符串忽略代碼。
在Web環境中,有惡意的用戶會利用那些設計不完善的、不能正確處理字符串的應用程序。特別是在公共Web站點上,在沒有首先經過PreparedStatement對象處理的狀況下,全部的用戶輸入都不該該傳遞給SQL語句。此外,在用戶有機會修改SQL語句的地方,如HTML的隱藏區域或一個查詢字符串上,SQL語句都不該該被顯示出來。
在執行SQL命令時,咱們有二種選擇:可使用PreparedStatement對象,也可使用Statement對象。不管多少次地使用同一個SQL命令,PreparedStatement都只對它解析和編譯一次。當使用Statement對象時,每次執行一個SQL命令時,都會對它進行解析和編譯。 服務器
先把這個SQL提交到數據庫中進行預處理,屢次使用可提升效率。
Statement不會初始化,沒有預處理,沒次都是從0開始執行SQL網絡
在SQL語句中能夠包含?,能夠用函數
ps=conn.prepareStatement("select * from Cust where ID=?"); int sid=1001; ps.setInt(1, sid); rs = ps.executeQuery();
能夠把 ? 替換成變量。
而Statement只能用
int sid=1001; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from Cust where ID="+sid);
來實現。
一、使用Statement而不是PreparedStatement對象
JDBC驅動的最佳化是基於使用的是什麼功能. 選擇PreparedStatement仍是Statement取決於你要怎麼使用它們. 對於只執行一次的SQL語句選擇Statement是最好的. 相反, 若是SQL語句被屢次執行選用PreparedStatement是最好的.
PreparedStatement的第一次執行消耗是很高的. 它的性能體如今後面的重複執行. 例如, 假設我使用Employee ID, 使用prepared的方式來執行一個針對Employee表的查詢. JDBC驅動會發送一個網絡請求到數據解析和優化這個查詢. 而執行時會產生另外一個網絡請求.在JDBC驅動中,減小網絡通信是最終的目的. 若是個人程序在運行期間只須要一次請求, 那麼就使用Statement. 對於Statement, 同一個查詢只會產生一次網絡到數據庫的通信.
對於使用PreparedStatement池的狀況下, 本指導原則有點複雜. 當使用PreparedStatement池時, 若是一個查詢很特殊, 而且不太會再次執行到, 那麼可使用Statement. 若是一個查詢不多會被執行,但鏈接池中的Statement池可能被再次執行, 那麼請使用PreparedStatement. 在不是Statement池的一樣狀況下, 請使用Statement.
二、使用PreparedStatement的Batch功能
Update大量的數據時, 先Prepare一個INSERT語句再屢次的執行, 會致使不少次的網絡鏈接. 要減小JDBC的調用次數改善性能, 你可使用PreparedStatement的AddBatch()方法一次性發送多個查詢給數據庫. 例如, 讓咱們來比較一下下面的例子.
例 1: 屢次執行PreparedStatement,屢次數據庫請求(網絡請求)
PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)"); for (n = 0; n < 100; n++) { ps.setString(name[n]); ps.setLong(id[n]); ps.setInt(salary[n]); ps.executeUpdate(); }
例 2: 使用Batch,以此請求執行多條
PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)"); for (n = 0; n < 100; n++) { ps.setString(name[n]); ps.setLong(id[n]); ps.setInt(salary[n]); ps.addBatch(); } ps.executeBatch();
在例 1中, PreparedStatement被用來屢次執行INSERT語句. 在這裏, 執行了100次INSERT操做, 共有101次網絡往返.
其中,1次往返是預儲PreparedStatement, 另外100次往返執行每一個迭代.
在例2中, 當在100次INSERT操做中使用addBatch()方法時, 只有兩次網絡往返.
1次往返是預儲PreparedStatement, 另外一次是執行batch命令. 雖然Batch命令會用到更多的數據庫的CPU週期, 可是經過減小網絡往返,性能獲得提升.記住, JDBC的性能最大的增進是減小JDBC驅動與數據庫之間的網絡通信.次數
注:Oracel 10G的JDBC Driver限制最大Batch size是16383條,若是addBatch超過這個限制,那麼executeBatch時就會出現「無效的批值」(Invalid Batch Value) 異常。所以在若是使用的是Oracle10G,在此bug減小前,Batch size須要控制在必定的限度。
一樣MySQL 5.5.28 批量執行的數據最大限度是多少不清楚,但本身試了1w,2w,3w 都沒問題,記得在url 後面添加:rewriteBatchedStatements=true 表示批量插入,若是不添加的話即便使用addbatch() ,executeBatch() 在後臺入庫的地方仍是不會一次請求入庫而是屢次請求入庫。
三、選擇合適的光標類型
的光標類型以最大限度的適用你的應用程序. 本節主要討論三種光標類型的性能問題.
對於從一個表中順序讀取全部記錄的狀況來講, Forward-Only型的光標提供了最好的性能. 獲取表中的數據時, 沒有哪一種方法比使用Forward-Only型的光標更快. 但無論怎樣, 當程序中必須按無次序的方式處理數據行時, 這種光標就沒法使用了.
對於程序中要求與數據庫的數據同步以及要可以在結果集中先後移動光標, 使用JDBC的Scroll-Insensitive型光標是較理想的選擇. 此類型的光標在第一次請求時就獲取了全部的數據(當JDBC驅動採用'lazy'方式獲取數據時或許是不少的而不是所有的數據)而且儲存在客戶端. 所以, 第一次請求會很是慢, 特別是請求長數據時會理嚴重. 而接下來的請求並不會形成任何網絡往返(當使用'lazy'方法時或許只是有限的網絡交通) 而且處理起來很快. 由於第一次請求速度很慢, Scroll-Insensitive型光標不該該被使用在單行數據的獲取上. 當有要返回長數據時, 開發者也應避免使用Scroll-Insensitive型光標, 由於這樣可能會形成內存耗盡. 有些Scroll-Insensitive型光標的實現方式是在數據庫的臨時表中緩存數據來避免性能問題, 但多數仍是將數據緩存在應用程序中.
Scroll-Sensitive型光標, 有時也稱爲Keyset-Driven光標, 使用標識符, 像數據庫的ROWID之類. 當每次在結果集移動光標時, 會從新該標識符的數據. 由於每次請求都會有網絡往返, 性能可能會很慢. 不管怎樣, 用無序方式的返回結果行對性能的改善是沒有幫助的.
如今來解釋一下這個, 來看這種狀況. 一個程序要正常的返回1000行數據到程序中. 在執行時或者第一行被請求時, JDBC驅動不會執行程序提供的SELECT語句. 相反, 它會用鍵標識符來替換SELECT查詢, 例如, ROWID. 而後修改過的查詢都會被驅動程序執行,跟着會從數據庫獲取全部1000個鍵值. 每一次對一行結果的請求都會使JDBC驅動直接從本地緩存中找到相應的鍵值, 而後構造一個包含了'WHERE ROWID=?'子句的最佳化查詢, 再接着執行這個修改過的查詢, 最後從服務器取得該數據行.
當程序沒法像Scroll-Insensitive型光標同樣提供足夠緩存時, Scroll-Sensitive型光標能夠被替代用來做爲動態的可滾動的光標.
四、使用有效的getter方法
JDBC提供多種方法從ResultSet中取得數據, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 這是由於JDBC驅動必須對要取得的值的類型做額外的處理以映射爲特定的對象. 因此就對特定的數據類型使用相應的方法.
要更進一步的改善性能, 應在取得數據時提供字段的索引號, 例如, getString(1), getLong(2), 和getInt(3)等來替代字段名. 若是沒有指定字段索引號, 網絡交通不會受影響, 但會使轉換和查找的成本增長. 例如, 假設你使用getString("foo") ... JDBC驅動可能會將字段名轉爲大寫(若是須要), 而且在到字段名列表中逐個比較來找到"foo"字段. 若是能夠, 直接使用字段索引, 將爲你節省大量的處理時間.
例如, 假設你有一個100行15列的ResultSet, 字段名不包含在其中. 你感興趣的是三個字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (長整型), 和SALARY (整型). 若是你指定getString(「EmployeeName」), getLong(「EmployeeNumber」), 和getInt(「Salary」), 查詢旱每一個字段名必須被轉換爲metadata中相對應的大小寫, 而後才進行查找. 若是你使用getString(1), getLong(2), 和getInt(15). 性能就會有顯著改善.
五、獲取自動生成的鍵值
有許多數據庫提供了隱藏列爲表中的每行記錄分配一個惟一鍵值. 很典型, 在查詢中使用這些字段類型是取得記錄值的最快的方式, 由於這些隱含列一般反應了數據在磁盤上的物理位置. 在JDBC3.0以前, 應用程序只可在插入數據後經過當即執行一個SELECT語句來取得隱含列的值.
例 3: JDBC3.0以前
//插入行 int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')"); // 如今爲新插入的行取得磁盤位置 - rowid ResultSet rs = stmt.executeQuery("select rowid from LocalGeniusList where name = 'Karen'");
這種取得隱含列的方式有兩個主要缺點. 第一, 取得隱含列是在一個獨立的查詢中, 它要透過網絡送到服務器後再執行. 第二, 由於不是主鍵, 查詢條件可能不是表中的惟一性ID. 在後面一個例子中, 可能返回了多個隱含列的值, 程序沒法知道哪一個是最後插入的行的值.
(譯者:因爲不一樣的數據庫支持的程度不一樣,返回rowid的方式各有差別。在SQL Server中,返回最後插入的記錄的id能夠用這樣的查詢語句:SELECT @IDENTITY )
JDBC3.0規範中的一個可選特性提供了一種能力, 能夠取得剛剛插入到表中的記錄的自動生成的鍵值.
例 4: JDBC3.0以後
int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')", // 插入行並返回鍵值 Statement.RETURN_GENERATED_KEYS); ResultSet rs = stmt.getGeneratedKeys (); // 獲得生成的鍵值
如今, 程序中包含了一個惟一性ID, 能夠用來做爲查詢條件來快速的存取數據行, 甚至於表中沒有主鍵的狀況也能夠.
這種取得自動生成的鍵值的方式給JDBC的開發者提供了靈活性, 而且使存取數據的性能獲得提高.
六、選擇合適的數據類型
接收和發送某些數據可能代價昂貴. 當你設計一個schema時, 應選擇能被最有效地處理的數據類型. 例如, 整型數就比浮點數或實數處理起來要快一些. 浮點數的定義是按照數據庫的內部規定的格式, 一般是一種壓縮格式. 數據必須被解壓和轉換到另外種格式, 這樣它才能被數據的協議處理.
七、獲取ResultSet
因爲數據庫系統對可滾動光標的支持有限, 許多JDBC驅動程序並無實現可滾動光標. 除非你確信數據庫支持可滾動光標的結果集, 不然不要調用rs.last()和rs.getRow()方法去找出數據集的最大行數. 由於JDBC驅動程序模擬了可滾動光標, 調用rs.last()致使了驅動程序透過網絡移到了數據集的最後一行. 取而代之, 你能夠用ResultSet遍歷一次計數或者用SELECT查詢的COUNT函數來獲得數據行數.
一般狀況下,請不要寫那種依賴於結果集行數的代碼, 由於驅動程序必須獲取全部的數據集以便知道查詢會返回多少行數據.
在JDBC應用中,若是你已是稍有水平開發者,你就應該始終以PreparedStatement代替Statement.也就是說,在任什麼時候候都不要使用Statement.基於如下的緣由:
雖然用PreparedStatement來代替Statement會使代碼多出幾行,但這樣的代碼不管從可讀性仍是可維護性上來講.都比直接用Statement的代碼高不少檔次:
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')"); perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)"); perstmt.setString(1,var1); perstmt.setString(2,var2); perstmt.setString(3,var3); perstmt.setString(4,var4); perstmt.executeUpdate();
不用我多說,對於第一種方法.別說其餘人去讀你的代碼,就是你本身過一段時間再去讀,都會以爲傷心.
每一種數據庫都會盡最大努力對預編譯語句提供最大的性能優化.由於預編譯語句有可能被重複調用.因此語句在被DB的編譯器編譯後的執行代碼被緩存下來,那麼下次調用時只要是相同的預編譯語句就不須要編譯,只要將參數直接傳入編譯過的語句執行代碼中(至關於一個涵數)就會獲得執行.這並非說只有一個 Connection中屢次執行的預編譯語句被緩存,而是對於整個DB中,只要預編譯的語句語法和緩存中匹配.那麼在任什麼時候候就能夠不須要再次編譯而能夠直接執行.而statement的語句中,即便是相同一操做,而因爲每次操做的數據不一樣因此使整個語句相匹配的機會極小,幾乎不太可能匹配.好比:insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');即便是相同操做但由於數據內容不同,因此整個個語句自己不能匹配,沒有緩存語句的意義.事實是沒有數據庫會對普通語句編譯後的執行代碼緩存.這樣每執行一次都要對傳入的語句編譯一次.
固然並非因此預編譯語句都必定會被緩存,數據庫自己會用一種策略,好比使用頻度等因素來決定何時再也不緩存已有的預編譯結果.以保存有更多的空間存儲新的預編譯語句.
即便到目前爲止,仍有一些人連基本的惡義SQL語法都不知道
String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";
若是咱們把[' or '1' = '1]做爲varpasswd傳入進來.用戶名隨意,看看會成爲何?
select * from tb_name = '隨意' and passwd = '' or '1' = '1';
由於'1'='1'確定成立,因此能夠任何經過驗證.
更有甚者:把[';drop table tb_name;]做爲varpasswd傳入進來
select * from tb_name = '隨意' and passwd = '';drop table tb_name;
有些數據庫是不會讓你成功的,但也有不少數據庫就可使這些語句獲得執行.
而若是你使用預編譯語句.你傳入的任何內容就不會和原來的語句發生任何匹配的關係.(前提是數據庫自己支持預編譯,但上前可能沒有什麼服務端數據庫不支持編譯了,只有少數的桌面數據庫,就是直接文件訪問的那些)只要全使用預編譯語句,你就用不着對傳入的數據作任何過濾.而若是使用普通的statement, 有可能要對drop,;等作費盡心機的判斷和過濾.
上面的幾個緣由,還不足讓你在任什麼時候候都使用PreparedStatement嗎?
總結: 上面是三篇文章,三篇文章詳細介紹了statement 和preparestatement 兩個對象的使用以及效率、安全問題。在實際項目中若是可以使用preparestatement 仍是建議使用preparestatement 緣由有3:
1)、上面說了 若是sql中只有數值在變則效率高
2)、preparestatement 具備防sql注入
3)、代碼可讀性比較好
實例:下面這個比喻很好,很明確的說明了批量添加,而且從中也能夠看出在批量添加的時候PreparedStatement爲何比Statement快的緣由~
Statement和PreparedStatement的區別就很少廢話了,直接說PreparedStatement最重要的addbatch()結構的使用.
PreparedStatement 的addBatch和executeBatch實現批量添加
1.創建連接
Connection connection = getConnection();
2.不自動 Commit (瓜子不是一個一個吃,所有剝開放桌子上,而後一口舔了)
connection.setAutoCommit(false);
3.預編譯SQL語句,只編譯一回哦,效率高啊.(發明一個剝瓜子的方法,之後不要總想怎麼剝瓜子好.就這樣剝.)
PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");
4.來一個剝一個,而後放桌子上
//記錄1 statement.setInt(1, 1); statement.setString(2, "Cujo"); statement.addBatch(); //記錄2 statement.setInt(1, 2); statement.setString(2, "Fred"); statement.addBatch(); //記錄3 statement.setInt(1, 3); statement.setString(2, "Mark"); statement.addBatch(); //批量執行上面3條語句. 一口吞了,很爽 int [] counts = statement.executeBatch(); //Commit it 嚥下去,到肚子(DB)裏面 connection.commit(); statement 對象的addBatch 和 executeBatch 來實現批量添加 stmt.addBatch("update TABLE1 set 題目="盛夏話足部保健1" where id="3407""); stmt.addBatch("update TABLE1 set 題目="夏季預防中暑膳食1" where id="3408""); stmt.addBatch("INSERT INTO TABLE1 VALUES("11","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("12","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("13","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("14","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("15","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("16","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("17","12","13","","")"); stmt.addBatch("INSERT INTO TABLE1 VALUES("18","12","13","","")"); int [] updateCounts=stmt.executeBatch(); cn.commit();
實例:批量添加
public static void insertData(List<Map<String,String>> list,Logger log){ //獲取的數據 List <Map<String,String>> nlist= list; String upsql="update hrd_staff set position =? where id=?"; Iterator<Map<String,String>> iter= nlist.iterator(); Connection con= Utils.getCon(); int count=0; try { //在皮臉添加的時候注意事務提交方式 con.setAutoCommit(false); //PreparedStatement方法的使用 PreparedStatement pstm = con.prepareStatement(upsql); while(iter.hasNext()){ count++; Map<String,String> map= iter.next(); String jon_name= map.get("job_name"); String uid= map.get("uid"); pstm.setString(1,jon_name); pstm.setString(2,uid); //添加到緩存中 pstm.addBatch(); // 若是數據量很大,不能一次性批量添加因此咱們要分批次添加,這裏就是300條一次 if(count%300==0){ //持久化 int []res=pstm.executeBatch(); //提交事務,持久化數據 con.commit(); pstm.clearBatch(); log.info("300整除插入結果: "+res.length); } } //小於300條的在這裏持久化 int []ress= pstm.executeBatch(); //事務提交持久化 con.commit(); pstm.clearBatch(); log.info("插入數據結果:"+ress.length); } catch (SQLException e) { try { con.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } e.printStackTrace(); }finally{ try { if(null!=con){ con.close(); con.setAutoCommit(true); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
這裏除了下面說的url中的批量設置外,咱們也要注意事務的設置,不能設置爲自動提交,要批量添加後在提交事務
總結:
addBatch() 就是把你的處理內容添加到批處理單元中。即添加到了batch中。你能夠循環加入不少,數據庫都不會處理,直到調用以下代碼executeBatch() 此時,數據庫把剛纔加到batch中的命令批量處理。
使用批量插入的好處: , 當在100次INSERT操做中使用addBatch()方法時, 只有兩次網絡往返. 1次往返是預儲statement, 另外一次是執行batch命令. 雖然Batch命令會用到更多的數據庫的CPU週期, 可是經過減小網絡往返,性能獲得提升. 記住, JDBC的性能最大的增進是減小JDBC驅動與數據庫之間的網絡通信. 若是沒有使用批處理則網絡往返101次這樣會耗不少時間,天然效率也就通常
這裏要注意:在mysql 下使用批量執行的時候要在,url 後面添加手動設置支持批量添加 實例以下:
String url="jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";
// 默認狀況下rewriteBatchedStatements 的值爲false 也就是批量添加功能是關閉的,若是使用則要手動開啓!
還有就是事務的設置,不能使自動提交,要批量添加後才提交!!!