您好,登錄后才能下訂單哦!
這篇文章主要是詳解Java連接數(shù)據(jù)庫(kù)JDBC技術(shù)中的prepareStatement,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。
一、prepareStatement 的用法和解釋
1.PreparedStatement是預(yù)編譯的,對(duì)于批量處理可以大大提高效率. 也叫JDBC存儲(chǔ)過(guò)程
2.使用 Statement 對(duì)象。在對(duì)數(shù)據(jù)庫(kù)只執(zhí)行一次性存取的時(shí)侯,用 Statement 對(duì)象進(jìn)行處理。PreparedStatement 對(duì)象的開(kāi)銷比Statement大,對(duì)于一次性操作并不會(huì)帶來(lái)額外的好處。
3.statement每次執(zhí)行sql語(yǔ)句,相關(guān)數(shù)據(jù)庫(kù)都要執(zhí)行sql語(yǔ)句的編譯,preparedstatement是預(yù)編譯得, preparedstatement支持批處理
4、
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的區(qū)別在于,后者使用了PreparedStatement對(duì)象,而前者是普通的Statement對(duì)象。PreparedStatement對(duì)象不僅包含了SQL語(yǔ)句,而且大多數(shù)情況下這個(gè)語(yǔ)句已經(jīng)被預(yù)編譯過(guò),因而當(dāng)其執(zhí)行時(shí),只需DBMS運(yùn)行SQL語(yǔ)句,而不必先編譯。當(dāng)你需要執(zhí)行Statement對(duì)象多次的時(shí)候,PreparedStatement對(duì)象將會(huì)大大降低運(yùn)行時(shí)間,當(dāng)然也加快了訪問(wèn)數(shù)據(jù)庫(kù)的速度。
這種轉(zhuǎn)換也給你帶來(lái)很大的便利,不必重復(fù)SQL語(yǔ)句的句法,而只需更改其中變量的值,便可重新執(zhí)行SQL語(yǔ)句。選擇PreparedStatement對(duì)象與否,在于相同句法的SQL語(yǔ)句是否執(zhí)行了多次,而且兩次之間的差別僅僅是變量的不同。如果僅僅執(zhí)行了一次的話,它應(yīng)該和普通的對(duì)象毫無(wú)差異,體現(xiàn)不出它預(yù)編譯的優(yōu)越性。
5.執(zhí)行許多SQL語(yǔ)句的JDBC程序產(chǎn)生大量的Statement和PreparedStatement對(duì)象。通常認(rèn)為PreparedStatement對(duì)象比Statement對(duì)象更有效,特別是如果帶有不同參數(shù)的同一SQL語(yǔ)句被多次執(zhí)行的時(shí)候。PreparedStatement對(duì)象允許數(shù)據(jù)庫(kù)預(yù)編譯SQL語(yǔ)句,這樣在隨后的運(yùn)行中可以節(jié)省時(shí)間并增加代碼的可讀性。
然而,在Oracle環(huán)境中,開(kāi)發(fā)人員實(shí)際上有更大的靈活性。當(dāng)使用Statement或PreparedStatement對(duì)象時(shí),Oracle數(shù)據(jù)庫(kù)會(huì)緩存SQL語(yǔ)句以便以后使用。在一些情況下,由于驅(qū)動(dòng)器自身需要額外的處理和在Java應(yīng)用程序和Oracle服務(wù)器間增加的網(wǎng)絡(luò)活動(dòng),執(zhí)行PreparedStatement對(duì)象實(shí)際上會(huì)花更長(zhǎng)的時(shí)間。
然而,除了緩沖的問(wèn)題之外,至少還有一個(gè)更好的原因使我們?cè)谄髽I(yè)應(yīng)用程序中更喜歡使用PreparedStatement對(duì)象,那就是安全性。傳遞給PreparedStatement對(duì)象的參數(shù)可以被強(qiáng)制進(jìn)行類型轉(zhuǎn)換,使開(kāi)發(fā)人員可以確保在插入或查詢數(shù)據(jù)時(shí)與底層的數(shù)據(jù)庫(kù)格式匹配。
當(dāng)處理公共Web站點(diǎn)上的用戶傳來(lái)的數(shù)據(jù)的時(shí)候,安全性的問(wèn)題就變得極為重要。傳遞給PreparedStatement的字符串參數(shù)會(huì)自動(dòng)被驅(qū)動(dòng)器忽略。最簡(jiǎn)單的情況下,這就意味著當(dāng)你的程序試著將字符串“D'Angelo”插入到VARCHAR2中時(shí),該語(yǔ)句將不會(huì)識(shí)別第一個(gè)“,”,從而導(dǎo)致悲慘的失敗。幾乎很少有必要?jiǎng)?chuàng)建你自己的字符串忽略代碼。
在Web環(huán)境中,有惡意的用戶會(huì)利用那些設(shè)計(jì)不完善的、不能正確處理字符串的應(yīng)用程序。特別是在公共Web站點(diǎn)上,在沒(méi)有首先通過(guò)PreparedStatement對(duì)象處理的情況下,所有的用戶輸入都不應(yīng)該傳遞給SQL語(yǔ)句。此外,在用戶有機(jī)會(huì)修改SQL語(yǔ)句的地方,如HTML的隱藏區(qū)域或一個(gè)查詢字符串上,SQL語(yǔ)句都不應(yīng)該被顯示出來(lái)。
在執(zhí)行SQL命令時(shí),我們有二種選擇:可以使用PreparedStatement對(duì)象,也可以使用Statement對(duì)象。無(wú)論多少次地使用同一個(gè)SQL命令,PreparedStatement都只對(duì)它解析和編譯一次。當(dāng)使用Statement對(duì)象時(shí),每次執(zhí)行一個(gè)SQL命令時(shí),都會(huì)對(duì)它進(jìn)行解析和編譯。
第一:
prepareStatement會(huì)先初始化SQL,先把這個(gè)SQL提交到數(shù)據(jù)庫(kù)中進(jìn)行預(yù)處理,多次使用可提高效率。
Statement不會(huì)初始化,沒(méi)有預(yù)處理,沒(méi)次都是從0開(kāi)始執(zhí)行SQL
第二:
prepareStatement可以替換變量
在SQL語(yǔ)句中可以包含?,可以用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);
來(lái)實(shí)現(xiàn)。
二、深入理解statement 和prepareStatement
1、使用Statement而不是PreparedStatement對(duì)象
JDBC驅(qū)動(dòng)的最佳化是基于使用的是什么功能. 選擇PreparedStatement還是Statement取決于你要怎么使用它們. 對(duì)于只執(zhí)行一次的SQL語(yǔ)句選擇Statement是最好的. 相反, 如果SQL語(yǔ)句被多次執(zhí)行選用PreparedStatement是最好的.
PreparedStatement的第一次執(zhí)行消耗是很高的. 它的性能體現(xiàn)在后面的重復(fù)執(zhí)行. 例如, 假設(shè)我使用Employee ID, 使用prepared的方式來(lái)執(zhí)行一個(gè)針對(duì)Employee表的查詢. JDBC驅(qū)動(dòng)會(huì)發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求到數(shù)據(jù)解析和優(yōu)化這個(gè)查詢. 而執(zhí)行時(shí)會(huì)產(chǎn)生另一個(gè)網(wǎng)絡(luò)請(qǐng)求.在JDBC驅(qū)動(dòng)中,減少網(wǎng)絡(luò)通訊是最終的目的. 如果我的程序在運(yùn)行期間只需要一次請(qǐng)求, 那么就使用Statement. 對(duì)于Statement, 同一個(gè)查詢只會(huì)產(chǎn)生一次網(wǎng)絡(luò)到數(shù)據(jù)庫(kù)的通訊.
對(duì)于使用PreparedStatement池的情況下, 本指導(dǎo)原則有點(diǎn)復(fù)雜. 當(dāng)使用PreparedStatement池時(shí), 如果一個(gè)查詢很特殊, 并且不太會(huì)再次執(zhí)行到, 那么可以使用Statement. 如果一個(gè)查詢很少會(huì)被執(zhí)行,但連接池中的Statement池可能被再次執(zhí)行, 那么請(qǐng)使用PreparedStatement. 在不是Statement池的同樣情況下, 請(qǐng)使用Statement.
2、使用PreparedStatement的Batch功能
Update大量的數(shù)據(jù)時(shí), 先Prepare一個(gè)INSERT語(yǔ)句再多次的執(zhí)行, 會(huì)導(dǎo)致很多次的網(wǎng)絡(luò)連接. 要減少JDBC的調(diào)用次數(shù)改善性能, 你可以使用PreparedStatement的AddBatch()方法一次性發(fā)送多個(gè)查詢給數(shù)據(jù)庫(kù). 例如, 讓我們來(lái)比較一下下面的例子.
例 1: 多次執(zhí)行PreparedStatement,多次數(shù)據(jù)庫(kù)請(qǐng)求(網(wǎng)絡(luò)請(qǐng)求)
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,以此請(qǐng)求執(zhí)行多條
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被用來(lái)多次執(zhí)行INSERT語(yǔ)句. 在這里, 執(zhí)行了100次INSERT操作, 共有101次網(wǎng)絡(luò)往返.
其中,1次往返是預(yù)儲(chǔ)PreparedStatement, 另外100次往返執(zhí)行每個(gè)迭代.
在例2中, 當(dāng)在100次INSERT操作中使用addBatch()方法時(shí), 只有兩次網(wǎng)絡(luò)往返.
1次往返是預(yù)儲(chǔ)PreparedStatement, 另一次是執(zhí)行batch命令. 雖然Batch命令會(huì)用到更多的數(shù)據(jù)庫(kù)的CPU周期, 但是通過(guò)減少網(wǎng)絡(luò)往返,性能得到提高.記住, JDBC的性能最大的增進(jìn)是減少JDBC驅(qū)動(dòng)與數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)通訊.次數(shù)
注:Oracel 10G的JDBC Driver限制最大Batch size是16383條,如果addBatch超過(guò)這個(gè)限制,那么executeBatch時(shí)就會(huì)出現(xiàn)“無(wú)效的批值”(Invalid Batch Value) 異常。因此在如果使用的是Oracle10G,在此bug減少前,Batch size需要控制在一定的限度。
同樣mysql 5.5.28 批量執(zhí)行的數(shù)據(jù)最大限度是多少不清楚,但自己試了1w,2w,3w 都沒(méi)問(wèn)題,記得在url 后面添加:rewriteBatchedStatements=true 表示批量插入,如果不添加的話即使使用addbatch() ,executeBatch() 在后臺(tái)入庫(kù)的地方還是不會(huì)一次請(qǐng)求入庫(kù)而是多次請(qǐng)求入庫(kù)。
3、選擇合適的光標(biāo)類型
的光標(biāo)類型以最大限度的適用你的應(yīng)用程序. 本節(jié)主要討論三種光標(biāo)類型的性能問(wèn)題.
對(duì)于從一個(gè)表中順序讀取所有記錄的情況來(lái)說(shuō), Forward-Only型的光標(biāo)提供了最好的性能. 獲取表中的數(shù)據(jù)時(shí), 沒(méi)有哪種方法比使用Forward-Only型的光標(biāo)更快. 但不管怎樣, 當(dāng)程序中必須按無(wú)次序的方式處理數(shù)據(jù)行時(shí), 這種光標(biāo)就無(wú)法使用了.
對(duì)于程序中要求與數(shù)據(jù)庫(kù)的數(shù)據(jù)同步以及要能夠在結(jié)果集中前后移動(dòng)光標(biāo), 使用JDBC的Scroll-Insensitive型光標(biāo)是較理想的選擇. 此類型的光標(biāo)在第一次請(qǐng)求時(shí)就獲取了所有的數(shù)據(jù)(當(dāng)JDBC驅(qū)動(dòng)采用'lazy'方式獲取數(shù)據(jù)時(shí)或許是很多的而不是全部的數(shù)據(jù))并且儲(chǔ)存在客戶端. 因此, 第一次請(qǐng)求會(huì)非常慢, 特別是請(qǐng)求長(zhǎng)數(shù)據(jù)時(shí)會(huì)理嚴(yán)重. 而接下來(lái)的請(qǐng)求并不會(huì)造成任何網(wǎng)絡(luò)往返(當(dāng)使用'lazy'方法時(shí)或許只是有限的網(wǎng)絡(luò)交通) 并且處理起來(lái)很快. 因?yàn)榈谝淮握?qǐng)求速度很慢, Scroll-Insensitive型光標(biāo)不應(yīng)該被使用在單行數(shù)據(jù)的獲取上. 當(dāng)有要返回長(zhǎng)數(shù)據(jù)時(shí), 開(kāi)發(fā)者也應(yīng)避免使用Scroll-Insensitive型光標(biāo), 因?yàn)檫@樣可能會(huì)造成內(nèi)存耗盡. 有些Scroll-Insensitive型光標(biāo)的實(shí)現(xiàn)方式是在數(shù)據(jù)庫(kù)的臨時(shí)表中緩存數(shù)據(jù)來(lái)避免性能問(wèn)題, 但多數(shù)還是將數(shù)據(jù)緩存在應(yīng)用程序中.
Scroll-Sensitive型光標(biāo), 有時(shí)也稱為Keyset-Driven光標(biāo), 使用標(biāo)識(shí)符, 像數(shù)據(jù)庫(kù)的ROWID之類. 當(dāng)每次在結(jié)果集移動(dòng)光標(biāo)時(shí), 會(huì)重新該標(biāo)識(shí)符的數(shù)據(jù). 因?yàn)槊看握?qǐng)求都會(huì)有網(wǎng)絡(luò)往返, 性能可能會(huì)很慢. 無(wú)論怎樣, 用無(wú)序方式的返回結(jié)果行對(duì)性能的改善是沒(méi)有幫助的.
現(xiàn)在來(lái)解釋一下這個(gè), 來(lái)看這種情況. 一個(gè)程序要正常的返回1000行數(shù)據(jù)到程序中. 在執(zhí)行時(shí)或者第一行被請(qǐng)求時(shí), JDBC驅(qū)動(dòng)不會(huì)執(zhí)行程序提供的SELECT語(yǔ)句. 相反, 它會(huì)用鍵標(biāo)識(shí)符來(lái)替換SELECT查詢, 例如, ROWID. 然后修改過(guò)的查詢都會(huì)被驅(qū)動(dòng)程序執(zhí)行,跟著會(huì)從數(shù)據(jù)庫(kù)獲取所有1000個(gè)鍵值. 每一次對(duì)一行結(jié)果的請(qǐng)求都會(huì)使JDBC驅(qū)動(dòng)直接從本地緩存中找到相應(yīng)的鍵值, 然后構(gòu)造一個(gè)包含了'WHERE ROWID=?'子句的最佳化查詢, 再接著執(zhí)行這個(gè)修改過(guò)的查詢, 最后從服務(wù)器取得該數(shù)據(jù)行.
當(dāng)程序無(wú)法像Scroll-Insensitive型光標(biāo)一樣提供足夠緩存時(shí), Scroll-Sensitive型光標(biāo)可以被替代用來(lái)作為動(dòng)態(tài)的可滾動(dòng)的光標(biāo).
4、使用有效的getter方法
JDBC提供多種方法從ResultSet中取得數(shù)據(jù), 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 這是因?yàn)镴DBC驅(qū)動(dòng)必須對(duì)要取得的值的類型作額外的處理以映射為特定的對(duì)象. 所以就對(duì)特定的數(shù)據(jù)類型使用相應(yīng)的方法.
要更進(jìn)一步的改善性能, 應(yīng)在取得數(shù)據(jù)時(shí)提供字段的索引號(hào), 例如, getString(1), getLong(2), 和getInt(3)等來(lái)替代字段名. 如果沒(méi)有指定字段索引號(hào), 網(wǎng)絡(luò)交通不會(huì)受影響, 但會(huì)使轉(zhuǎn)換和查找的成本增加. 例如, 假設(shè)你使用getString("foo") ... JDBC驅(qū)動(dòng)可能會(huì)將字段名轉(zhuǎn)為大寫(xiě)(如果需要), 并且在到字段名列表中逐個(gè)比較來(lái)找到"foo"字段. 如果可以, 直接使用字段索引, 將為你節(jié)省大量的處理時(shí)間.
例如, 假設(shè)你有一個(gè)100行15列的ResultSet, 字段名不包含在其中. 你感興趣的是三個(gè)字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (長(zhǎng)整型), 和SALARY (整型). 如果你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查詢旱每個(gè)字段名必須被轉(zhuǎn)換為metadata中相對(duì)應(yīng)的大小寫(xiě), 然后才進(jìn)行查找. 如果你使用getString(1), getLong(2), 和getInt(15). 性能就會(huì)有顯著改善.
5、獲取自動(dòng)生成的鍵值
有許多數(shù)據(jù)庫(kù)提供了隱藏列為表中的每行記錄分配一個(gè)唯一鍵值. 很典型, 在查詢中使用這些字段類型是取得記錄值的最快的方式, 因?yàn)檫@些隱含列通常反應(yīng)了數(shù)據(jù)在磁盤上的物理位置. 在JDBC3.0之前, 應(yīng)用程序只可在插入數(shù)據(jù)后通過(guò)立即執(zhí)行一個(gè)SELECT語(yǔ)句來(lái)取得隱含列的值.
例 3: JDBC3.0之前
//插入行 int rowcount = stmt.executeUpdate ( "insert into LocalGeniusList (name) values ('Karen')"); // 現(xiàn)在為新插入的行取得磁盤位置 - rowid ResultSet rs = stmt.executeQuery ( "select rowid from LocalGeniusList where name = 'Karen'");
這種取得隱含列的方式有兩個(gè)主要缺點(diǎn). 第一, 取得隱含列是在一個(gè)獨(dú)立的查詢中, 它要透過(guò)網(wǎng)絡(luò)送到服務(wù)器后再執(zhí)行. 第二, 因?yàn)椴皇侵麈I, 查詢條件可能不是表中的唯一性ID. 在后面一個(gè)例子中, 可能返回了多個(gè)隱含列的值, 程序無(wú)法知道哪個(gè)是最后插入的行的值.
(譯者:由于不同的數(shù)據(jù)庫(kù)支持的程度不同,返回rowid的方式各有差異。在SQL Server中,返回最后插入的記錄的id可以用這樣的查詢語(yǔ)句:SELECT @IDENTITY )
JDBC3.0規(guī)范中的一個(gè)可選特性提供了一種能力, 可以取得剛剛插入到表中的記錄的自動(dòng)生成的鍵值.
例 4: JDBC3.0之后
int rowcount = stmt.executeUpdate ( "insert into LocalGeniusList (name) values ('Karen')", // 插入行并返回鍵值 Statement.RETURN_GENERATED_KEYS); ResultSet rs = stmt.getGeneratedKeys (); // 得到生成的鍵值
現(xiàn)在, 程序中包含了一個(gè)唯一性ID, 可以用來(lái)作為查詢條件來(lái)快速的存取數(shù)據(jù)行, 甚至于表中沒(méi)有主鍵的情況也可以.
這種取得自動(dòng)生成的鍵值的方式給JDBC的開(kāi)發(fā)者提供了靈活性, 并且使存取數(shù)據(jù)的性能得到提升.
6、選擇合適的數(shù)據(jù)類型
接收和發(fā)送某些數(shù)據(jù)可能代價(jià)昂貴. 當(dāng)你設(shè)計(jì)一個(gè)schema時(shí), 應(yīng)選擇能被最有效地處理的數(shù)據(jù)類型. 例如, 整型數(shù)就比浮點(diǎn)數(shù)或?qū)崝?shù)處理起來(lái)要快一些. 浮點(diǎn)數(shù)的定義是按照數(shù)據(jù)庫(kù)的內(nèi)部規(guī)定的格式, 通常是一種壓縮格式. 數(shù)據(jù)必須被解壓和轉(zhuǎn)換到另外種格式, 這樣它才能被數(shù)據(jù)的協(xié)議處理.
7、獲取ResultSet
由于數(shù)據(jù)庫(kù)系統(tǒng)對(duì)可滾動(dòng)光標(biāo)的支持有限, 許多JDBC驅(qū)動(dòng)程序并沒(méi)有實(shí)現(xiàn)可滾動(dòng)光標(biāo). 除非你確信數(shù)據(jù)庫(kù)支持可滾動(dòng)光標(biāo)的結(jié)果集, 否則不要調(diào)用rs.last()和rs.getRow()方法去找出數(shù)據(jù)集的最大行數(shù). 因?yàn)镴DBC驅(qū)動(dòng)程序模擬了可滾動(dòng)光標(biāo), 調(diào)用rs.last()導(dǎo)致了驅(qū)動(dòng)程序透過(guò)網(wǎng)絡(luò)移到了數(shù)據(jù)集的最后一行. 取而代之, 你可以用ResultSet遍歷一次計(jì)數(shù)或者用SELECT查詢的COUNT函數(shù)來(lái)得到數(shù)據(jù)行數(shù).
通常情況下,請(qǐng)不要寫(xiě)那種依賴于結(jié)果集行數(shù)的代碼, 因?yàn)轵?qū)動(dòng)程序必須獲取所有的數(shù)據(jù)集以便知道查詢會(huì)返回多少行數(shù)據(jù).
三、preparestatement 防止sql注入
在JDBC應(yīng)用中,如果你已經(jīng)是稍有水平開(kāi)發(fā)者,你就應(yīng)該始終以PreparedStatement代替Statement.也就是說(shuō),在任何時(shí)候都不要使用Statement.基于以下的原因:
1、代碼的可讀性和可維護(hù)性.雖然用PreparedStatement來(lái)代替Statement會(huì)使代碼多出幾行,但這樣的代碼無(wú)論從可讀性還是可維護(hù)性上來(lái)說(shuō).都比直接用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();
不用我多說(shuō),對(duì)于第一種方法.別說(shuō)其他人去讀你的代碼,就是你自己過(guò)一段時(shí)間再去讀,都會(huì)覺(jué)得傷心.
2、PreparedStatement盡最大可能提高性能.每一種數(shù)據(jù)庫(kù)都會(huì)盡最大努力對(duì)預(yù)編譯語(yǔ)句提供最大的性能優(yōu)化.因?yàn)轭A(yù)編譯語(yǔ)句有可能被重復(fù)調(diào)用.所以語(yǔ)句在被DB的編譯器編譯后的執(zhí)行代碼被緩存下來(lái),那么下次調(diào)用時(shí)只要是相同的預(yù)編譯語(yǔ)句就不需要編譯,只要將參數(shù)直接傳入編譯過(guò)的語(yǔ)句執(zhí)行代碼中(相當(dāng)于一個(gè)涵數(shù))就會(huì)得到執(zhí)行.這并不是說(shuō)只有一個(gè) Connection中多次執(zhí)行的預(yù)編譯語(yǔ)句被緩存,而是對(duì)于整個(gè)DB中,只要預(yù)編譯的語(yǔ)句語(yǔ)法和緩存中匹配.那么在任何時(shí)候就可以不需要再次編譯而可以直接執(zhí)行.而statement的語(yǔ)句中,即使是相同一操作,而由于每次操作的數(shù)據(jù)不同所以使整個(gè)語(yǔ)句相匹配的機(jī)會(huì)極小,幾乎不太可能匹配.比如:insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');即使是相同操作但因?yàn)閿?shù)據(jù)內(nèi)容不一樣,所以整個(gè)個(gè)語(yǔ)句本身不能匹配,沒(méi)有緩存語(yǔ)句的意義.事實(shí)是沒(méi)有數(shù)據(jù)庫(kù)會(huì)對(duì)普通語(yǔ)句編譯后的執(zhí)行代碼緩存.這樣每執(zhí)行一次都要對(duì)傳入的語(yǔ)句編譯一次.
當(dāng)然并不是所以預(yù)編譯語(yǔ)句都一定會(huì)被緩存,數(shù)據(jù)庫(kù)本身會(huì)用一種策略,比如使用頻度等因素來(lái)決定什么時(shí)候不再緩存已有的預(yù)編譯結(jié)果.以保存有更多的空間存儲(chǔ)新的預(yù)編譯語(yǔ)句.
3、最重要的一點(diǎn)是極大地提高了安全性.
即使到目前為止,仍有一些人連基本的惡義SQL語(yǔ)法都不知道.String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";如果我們把[' or '1' = '1]作為varpasswd傳入進(jìn)來(lái).用戶名隨意,看看會(huì)成為什么?
select * from tb_name = '隨意' and passwd = '' or '1' = '1';因?yàn)?#39;1'='1'肯定成立,所以可以任何通過(guò)驗(yàn)證.更有甚者:把[';drop table tb_name;]作為varpasswd傳入進(jìn)來(lái),則:select * from tb_name = '隨意' and passwd = '';drop table tb_name;有些數(shù)據(jù)庫(kù)是不會(huì)讓你成功的,但也有很多數(shù)據(jù)庫(kù)就可以使這些語(yǔ)句得到執(zhí)行.
而如果你使用預(yù)編譯語(yǔ)句.你傳入的任何內(nèi)容就不會(huì)和原來(lái)的語(yǔ)句發(fā)生任何匹配的關(guān)系.(前提是數(shù)據(jù)庫(kù)本身支持預(yù)編譯,但上前可能沒(méi)有什么服務(wù)端數(shù)據(jù)庫(kù)不支持編譯了,只有少數(shù)的桌面數(shù)據(jù)庫(kù),就是直接文件訪問(wèn)的那些)只要全使用預(yù)編譯語(yǔ)句,你就用不著對(duì)傳入的數(shù)據(jù)做任何過(guò)慮.而如果使用普通的statement, 有可能要對(duì)drop,;等做費(fèi)盡心機(jī)的判斷和過(guò)慮.
上面的幾個(gè)原因,還不足讓你在任何時(shí)候都使用PreparedStatement嗎?
總結(jié): 上面是三篇文章,三篇文章詳細(xì)介紹了statement 和preparestatement 兩個(gè)對(duì)象的使用以及效率、安全問(wèn)題。在實(shí)際項(xiàng)目中如果能夠使用preparestatement 還是建議使用preparestatement 原因有3:
1)、上面說(shuō)了 如果sql中只有數(shù)值在變則效率高
2)、preparestatement 具有防sql注入
3)、代碼可讀性比較好
實(shí)例:下面這個(gè)比喻很好,很明確的說(shuō)明了批量添加,并且從中也可以看出在批量添加的時(shí)候PreparedStatement為什么比Statement快的原因~
Statement和PreparedStatement的區(qū)別就不多廢話了,直接說(shuō)PreparedStatement最重要的addbatch()結(jié)構(gòu)的使用.
PreparedStatement 的addBatch和executeBatch實(shí)現(xiàn)批量添加
1.建立鏈接
Connection connection =getConnection();
2.不自動(dòng) Commit (瓜子不是一個(gè)一個(gè)吃,全部剝開(kāi)放桌子上,然后一口舔了)
connection.setAutoCommit(false);
3.預(yù)編譯SQL語(yǔ)句,只編譯一回哦,效率高啊.(發(fā)明一個(gè)剝瓜子的方法,以后不要總想怎么剝瓜子好.就這樣剝.)
PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");
4.來(lái)一個(gè)剝一個(gè),然后放桌子上
//記錄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(); //批量執(zhí)行上面3條語(yǔ)句. 一口吞了,很爽 int [] counts = statement.executeBatch(); //Commit it 咽下去,到肚子(DB)里面 connection.commit(); statement 對(duì)象的addBatch 和 executeBatch 來(lái)實(shí)現(xiàn)批量添加 stmt.addBatch("update TABLE1 set 題目="盛夏話足部保健1" where"); stmt.addBatch("update TABLE1 set 題目="夏季預(yù)防中暑膳食1" where"); 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();
實(shí)例:批量添加
public static void insertData(List<Map<String,String>> list,Logger log){ //獲取的數(shù)據(jù) 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 { //在皮臉添加的時(shí)候注意事務(wù)提交方式 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(); // 如果數(shù)據(jù)量很大,不能一次性批量添加所以我們要分批次添加,這里就是300條一次 if(count%300==0){ //持久化 int []res=pstm.executeBatch(); //提交事務(wù),持久化數(shù)據(jù) con.commit(); pstm.clearBatch(); log.info("300整除插入結(jié)果: "+res.length); } } //小于300條的在這里持久化 int []ress= pstm.executeBatch(); //事務(wù)提交持久化 con.commit(); pstm.clearBatch(); log.info("插入數(shù)據(jù)結(jié)果:"+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(); } } }
這里除了下面說(shuō)的url中的批量設(shè)置外,我們也要注意事務(wù)的設(shè)置,不能設(shè)置為自動(dòng)提交,要批量添加后在提交事務(wù)
總結(jié):
addBatch() 就是把你的處理內(nèi)容添加到批處理單元中。即添加到了batch中。你可以循環(huán)加入很多,數(shù)據(jù)庫(kù)都不會(huì)處理,直到調(diào)用如下代碼executeBatch() 此時(shí),數(shù)據(jù)庫(kù)把剛才加到batch中的命令批量處理。
使用批量插入的好處: , 當(dāng)在100次INSERT操作中使用addBatch()方法時(shí), 只有兩次網(wǎng)絡(luò)往返. 1次往返是預(yù)儲(chǔ)statement, 另一次是執(zhí)行batch命令. 雖然Batch命令會(huì)用到更多的數(shù)據(jù)庫(kù)的CPU周期, 但是通過(guò)減少網(wǎng)絡(luò)往返,性能得到提高. 記住, JDBC的性能最大的增進(jìn)是減少JDBC驅(qū)動(dòng)與數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)通訊. 如果沒(méi)有使用批處理則網(wǎng)絡(luò)往返101次這樣會(huì)耗很多時(shí)間,自然效率也就一般
這里要注意:在mysql 下使用批量執(zhí)行的時(shí)候要在,url 后面添加手動(dòng)設(shè)置支持批量添加 實(shí)例如下:
String url="jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";
// 默認(rèn)情況下rewriteBatchedStatements 的值為false 也就是批量添加功能是關(guān)閉的,如果使用則要手動(dòng)開(kāi)啟!
還有就是事務(wù)的設(shè)置,不能使自動(dòng)提交,要批量添加后才提交!??!
看完上述內(nèi)容,是不是對(duì)詳解Java連接數(shù)據(jù)庫(kù)JDBC技術(shù)中的prepareStatement有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。