try { threads[i].join();
//等待所有線程運(yùn)行結(jié)束
} catch(InterruptedException e){
// ignore } } System.out.println(“Finally, John's balance is:” + acc.getBalance());}}
注意,上面在Account的deposit和withdraw方法中之所以要把對amount的運(yùn)算使用一個臨時變量首先存儲,sleep一段時間,然后,再賦值給amount,是為了模擬真實(shí)運(yùn)行時的情況。因?yàn)樵谡鎸?shí)系統(tǒng)中,賬戶信息肯定是存儲在持久媒介中,比如RDBMS中,此處的睡眠的時間相當(dāng)于比較耗時的數(shù)據(jù)庫操作,最后把臨時變量tmp的值賦值給amount相當(dāng)于把a(bǔ)mount的改動寫入數(shù)據(jù)庫中。運(yùn)行AccountTest,結(jié)果如下(每一次結(jié)果都會不同): E:javaexerin>java AccountTest Finally, John's balance is:3900.0 E:javaexerin>java AccountTest Finally, John's balance is:4900.0 E:javaexerin>java AccountTest Finally, John's balance is:4700.0
E:javaexerin>java AccountTest
Finally, John's balance is:3900.0
E:javaexerin>java AccountTest
Finally, John's balance is:3900.0
E:javaexerin>java AccountTest
Finally, John's balance is:5200.0
為什么會出現(xiàn)這樣的問題?這就是多線程中的同步的問題。在我們的程序中,Account中的amount會同時被多個線程所訪問,這就是一個競爭資源,通常稱作競態(tài)條件。對于這樣的多個線程共享的資源我們必須進(jìn)行同步,以避免一個線程的改動被另一個線程所覆蓋。在我們這個程序中,Account中的amount是一個競態(tài)條件,所以所有對amount的修改訪問都要進(jìn)行同步,我們將deposit()和withdraw()方法進(jìn)行同步,修改為:
public synchronized void deposit(float amt){ float tmp = amount;tmp += amt;try {
Thread.sleep(1);
//模擬其它處理所需要的時間,比如刷新數(shù)據(jù)庫等
} catch(InterruptedException e){
// ignore } amount = tmp;}
public synchronized void withdraw(float amt){ float tmp = amount;tmp-= amt;try {
Thread.sleep(1);
//模擬其它處理所需要的時間,比如刷新數(shù)據(jù)庫等
} catch(InterruptedException e){ // ignore } amount = tmp;}
此時,再運(yùn)行,我們就能夠得到正確的結(jié)果了。Account中的getBalance()也訪問了amount,為什么不對getBalance()同步呢?因?yàn)間etBalance()并不會修改amount的值,所以,同時多個線程對它訪問不會造成數(shù)據(jù)的混亂。
String StringBuffer 代碼示例
public class Test{
public static void stringReplace(String text){
text=text+”c”;//1 }
public static void bufferReplace(StringBuffer text){ text=text.append(“c”);//2 }
public static void main(String args[]){ String textString=new String(“java”);
StringBuffer textBuffer=new StringBuffer(“java”);stringReplace(textString);bufferReplace(textBuffer);
System.out.println(textString+textBuffer);} }
代碼分析:
這段代碼先產(chǎn)生一個字符串對象textString,然后調(diào)用stringReplace方法,在方法里產(chǎn)生一個text局部變量指向new String(“java”)對象。這時,執(zhí)行到第一行時,由于字符串對象不可改變,所以會新產(chǎn)生一個字符串對象”javac”,然后將局部變量text指向他。在做返回的時候,textString所指向的對象并沒有改變。所以textString 的值還是”java”。
當(dāng)使用StringBuffer的時候,調(diào)用bufferReplace方法,在方法里產(chǎn)生了一個text這樣一個局部變量,在對text對象進(jìn)行操作時,指的是對text所指向?qū)ο筮M(jìn)行操作。由于StringBuffer不會產(chǎn)生新的對象。所以在進(jìn)行到第2行時,它的改變會影響對象值的改變,這時返回之后,結(jié)果是javac
所以最后的結(jié)果是javajavac I/O注意要點(diǎn)
(2008-07-23 12:52:40)轉(zhuǎn)載 標(biāo)簽:
雜談
1.掌握常用的輸入流讀取操作方法
– int read()//讀取一個字節(jié),返回字節(jié)的ASC碼
–
int read(char cbuf[])//讀取字節(jié)進(jìn)入chuf字符數(shù)組,返回讀取的字節(jié)數(shù) –
int read(char cbuf[], int offset, int length)//讀取字節(jié)進(jìn)入chuf字符數(shù)組,返回讀取的字節(jié)數(shù),offset為數(shù)據(jù)起始位置,length讀入字節(jié)長度 –
– int available();//獲得讀入流的字節(jié)長度 –
– close();//關(guān)閉流
對于讀入流,在建立流的時候,要求數(shù)據(jù)源必須存在。如建一個文件流: FileInputStream fin = new FileInputStream(“c:/2.mp3”);這時候,如果c盤下的2.mp3不存在的話會拋出一個FileNotFoundException異常。表示文件未找到。
2.掌握常用的輸出流寫入操作方法
– void write(int b)//寫入一個字節(jié)進(jìn)輸出流。注意int b是這個字節(jié)的ASC碼。
– void close()//關(guān)閉流 – void flush()//刷新流。
對于寫入流,在建立流的時候,如果數(shù)據(jù)源不存在,系統(tǒng)會自動創(chuàng)建。如建立一個寫入流。
FileOutputStream fout = new FileOutputStream(“c:/2.mp3”);
這時候,如果文件c:/2.mp3不存在的話,系統(tǒng)會自動創(chuàng)建一個2.mp3的文件,而不會拋出異常。
在操作寫入流時要注意:寫入流里的數(shù)據(jù)必須經(jīng)過flush刷新或關(guān)閉流之后,數(shù)據(jù)才能寫入目標(biāo)數(shù)據(jù)源。如果不關(guān)閉或刷新流,則可能寫不進(jìn)數(shù)據(jù)源。如建立了一個文件寫入流,如果在寫入時不關(guān)閉或刷新流,則文件里的內(nèi)容可能是空的。
類的封裝性
方法的訪問說明符
public protected default private 同類 ok ok ok ok 同包 ok ok ok error 子類 ok ok error error 通用 ok error error error 方法的其他修飾符
final 用于該方法不想被子類覆蓋的情況
static:static修飾的也不能被覆蓋 只是產(chǎn)生了一個新的static方法
類的繼承性
類的繼承性的特點(diǎn) 子類可以繼承的部分:(1)父類中公開級的成員;(2)父類中保護(hù)級的成員;
(3)如果子類和父類在同一個包里,則子類繼承父類中缺省的包訪問級的成員; 子類不能繼承的部分:(1)父類中私有級的成員;
(2)如果不在同一個包里,則不能繼承缺省級的成員;(3)同名的成員函數(shù)或成員變量; 繼承中的構(gòu)造函數(shù)
構(gòu)造函數(shù)是比較特殊的一類函數(shù)
在繼承時,構(gòu)造函數(shù)不會被繼承,也不會被覆蓋
父類和子類的構(gòu)造函數(shù)依然是獨(dú)立存在,并且分別發(fā)揮著作用
成員的覆蓋
如果子類的成員函數(shù)或成員變量與父類同名,則子類的成員函數(shù)或成員變量將隱蕆父類的同名成員。
如果一定要訪問父類的變量,可以有兩種方法:
(1)在子類內(nèi)部用“super”關(guān)鍵字來訪問父類的同名變量;(2)用多態(tài)性的功能來訪問父類對象
類方法的覆蓋
方法覆蓋時應(yīng)遵循的原則:
覆蓋的方法的存取權(quán)限必須高于被覆蓋的方法,如:父類的方法為protected的,子類的方法為public可以;反之,編譯報錯。覆蓋后的方法不能比被覆蓋的方法產(chǎn)生更多的異常
如果用一個父類的變量指向子類,再調(diào)用同名的函數(shù),會出現(xiàn)什么情況呢? 調(diào)用的還是子類的函數(shù)。從這點(diǎn)看,成員函數(shù)和成員變量是完全不同的。
類的多態(tài)性 利用動態(tài)聯(lián)編,一個函數(shù)調(diào)用語句可能實(shí)現(xiàn)不同類型的函數(shù),這種現(xiàn)象就是多態(tài)性
當(dāng)你通過父類調(diào)用函數(shù)時,如果變量所指向的是一個子類對象,那么所調(diào)的仍然是子類函數(shù),這就是多態(tài)性 抽象函數(shù)和抽象類的概念
抽象函數(shù):僅有定義,沒有具體實(shí)現(xiàn)的函數(shù) 抽象類:含有抽象函數(shù)的類
定義一個抽象類,需要在類的定義前面加上“abstract”關(guān)鍵字 定義一個抽象函數(shù),需要在函數(shù)定義的前面加上“abstract”關(guān)鍵字
一個類如果被定義為抽象類,它就不能實(shí)例化,也就是說,不能有自己的對象
抽象類的使用
抽象函數(shù)的意義是沒有具體實(shí)現(xiàn)的函數(shù)
它的作用就是被子類的相同函數(shù)覆蓋,或通過多態(tài)性指向子類的相同函數(shù) 通過抽象函數(shù),可以定義一整套完整的函數(shù)功能,再派生出若干子類來實(shí)現(xiàn) 不同的子類可以以不同的形式實(shí)現(xiàn)這些功能,但函數(shù)形式是完全一致的 抽象類必須有子類,不然就沒有意義
抽象類中不僅僅有抽象函數(shù),也可以有普通的成員函數(shù)和成員變量 但如果一個類中有抽象函數(shù),那么這個類必須定義為抽象類 如果一個類繼承了父類的幾個抽象函數(shù),但沒有全部實(shí)現(xiàn),那么這個類也必須定義為抽象類
OutOfMemoryError
(一)(2008-07-31 00:46:25)轉(zhuǎn)載 標(biāo)簽:
雜談
OutOfMemoryError
一、內(nèi)存溢出類型
1、java.lang.OutOfMemoryError: PermGen space JVM管理兩種類型的內(nèi)存,堆和非堆。堆是給開發(fā)人員用的上面說的就是,是在JVM啟動時創(chuàng)建;非堆是留給JVM自己用的,用來存放類的信息的。它和堆不同,運(yùn)行期內(nèi)GC不會釋放空間。如果web app用了大量的第三方j(luò)ar或者應(yīng)用有太多的class文件而恰好MaxPermSize設(shè)置較小,超出了也會導(dǎo)致這塊內(nèi)存的占用過多造成溢出,或者tomcat熱部署時侯不會清理前面加載的環(huán)境,只會將context更改為新部署的,非堆存的內(nèi)容就會越來越多。
2、java.lang.OutOfMemoryError: Java heap space 第一種情況是個補(bǔ)充,主要存在問題就是出現(xiàn)在這個情況中。其默認(rèn)空間(即-Xms)是物理內(nèi)存的1/64,最大空間(-Xmx)是物理內(nèi)存的1/4。如果內(nèi)存剩余不到40%,JVM就會增大堆到Xmx設(shè)置的值,內(nèi)存剩余超過70%,JVM就會減小堆到Xms設(shè)置的值。所以服務(wù)器的Xmx和Xms設(shè)置一般應(yīng)該設(shè)置相同避免每次GC后都要調(diào)整虛擬機(jī)堆的大小。假設(shè)物理內(nèi)存無限大,那么JVM內(nèi)存的最大值跟操作系統(tǒng)有關(guān),一般32位機(jī)是1.5g到3g之間,而64位的就不會有限制了。
注意:如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了物理內(nèi)存或者操作系統(tǒng)的最大限制都會引起服務(wù)器啟動不起來。垃圾回收GC的角色
JVM調(diào)用GC的頻度還是很高的,主要兩種情況下進(jìn)行垃圾回收:
當(dāng)應(yīng)用程序線程空閑;另一個是java內(nèi)存堆不足時,會不斷調(diào)用GC,若連續(xù)回收都解決不了內(nèi)存堆不足的問題時,就會報out of memory錯誤。因?yàn)檫@個異常根據(jù)系統(tǒng)運(yùn)行環(huán)境決定,所以無法預(yù)期它何時出現(xiàn)。
根據(jù)GC的機(jī)制,程序的運(yùn)行會引起系統(tǒng)運(yùn)行環(huán)境的變化,增加GC的觸發(fā)機(jī)會。為了避免這些問題,程序的設(shè)計(jì)和編寫就應(yīng)避免垃圾對象的內(nèi)存占用和GC的開銷。顯示調(diào)用System.GC()只能建議JVM需要在內(nèi)存中對垃圾對象進(jìn)行回收,但不是必須馬上回收,一個是并不能解決內(nèi)存資源耗空的局面,另外也會增加GC的消耗。
二、JVM內(nèi)存區(qū)域組成
簡單的說java中的堆和棧
java把內(nèi)存分兩種:一種是棧內(nèi)存,另一種是堆內(nèi)存
1。在函數(shù)中定義的基本類型變量和對象的引用變量都在函數(shù)的棧內(nèi)存中分配; 2。堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組
在函數(shù)(代碼塊)中定義一個變量時,java就在棧中為這個變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,java會自動釋放掉為該變量所分配的內(nèi)存空間;在堆中分配的內(nèi)存由java虛擬機(jī)的自動垃圾回收器來管理
堆的優(yōu)勢是可以動態(tài)分配內(nèi)存大小,生存期也不必事先告訴編譯器,因?yàn)樗窃谶\(yùn)行時動態(tài)分配內(nèi)存的。缺點(diǎn)就是要在運(yùn)行時動態(tài)分配內(nèi)存,存取速度較慢; 棧的優(yōu)勢是存取速度比堆要快,缺點(diǎn)是存在棧中的數(shù)據(jù)大小與生存期必須是確定的無靈活性。
java堆分為三個區(qū):New、Old和Permanent GC有兩個線程:
新創(chuàng)建的對象被分配到New區(qū),當(dāng)該區(qū)被填滿時會被GC輔助線程移到Old區(qū),當(dāng)Old區(qū)也填滿了會觸發(fā)GC主線程遍歷堆內(nèi)存里的所有對象。Old區(qū)的大小等于Xmx減去-Xmn java棧存放
棧調(diào)整:參數(shù)有+UseDefaultStackSize-Xss256K,表示每個線程可申請256k的棧空間
每個線程都有他自己的Stack
三、JVM如何設(shè)置虛擬內(nèi)存
提示:在JVM中如果98%的時間是用于GC且可用的Heap size不足2%的時候?qū)伋龃水惓P畔ⅰ?/p>
提示:Heap Size最大不要超過可用物理內(nèi)存的80%,一般的要將-Xms和-Xmx選項(xiàng)設(shè)置為相同,而-Xmn為1/4的-Xmx值。
提示:JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64;JVM最大分配的內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。
默認(rèn)空余堆內(nèi)存小于40%時,JVM就會增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時,JVM會減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、-Xmx相等以避免在每次GC后調(diào)整堆的大小。
提示:假設(shè)物理內(nèi)存無限大的話,JVM內(nèi)存的最大值跟操作系統(tǒng)有很大的關(guān)系。簡單的說就32位處理器雖然可控內(nèi)存空間有4GB,但是具體的操作系統(tǒng)會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統(tǒng)下為1.5G-2G,Linux系統(tǒng)下為2G-3G),而64bit以上的處理器就不會有限制了
提示:注意:如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了物理內(nèi)存或者操作系統(tǒng)的最大限制都會引起服務(wù)器啟動不起來。提示:設(shè)置NewSize、MaxNewSize相等,“new”的大小最好不要大于“old”的一半,原因是old區(qū)如果不夠大會頻繁的觸發(fā)“主” GC,大大降低了性能 JVM使用-XX:PermSize設(shè)置非堆內(nèi)存初始值,默認(rèn)是物理內(nèi)存的1/64; 由XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小,默認(rèn)是物理內(nèi)存的1/4。解決方法:手動設(shè)置Heap size 修改TOMCAT_HOME/bin/catalina.bat 在“echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入以下行: JAVA_OPTS=“-server-Xms800m-Xmx800m-XX:MaxNewSize=256m”
OutOfMemoryError
四、性能檢查工具使用
定位內(nèi)存泄漏:
JProfiler工具主要用于檢查和跟蹤系統(tǒng)(限于Java開發(fā)的)的性能。JProfiler可以通過時時的監(jiān)控系統(tǒng)的內(nèi)存使用情況,隨時監(jiān)視垃圾回收,線程運(yùn)行狀況等手段,從而很好的監(jiān)視JVM運(yùn)行情況及其性能。
1.應(yīng)用服務(wù)器內(nèi)存長期不合理占用,內(nèi)存經(jīng)常處于高位占用,很難回收到低位; 2.應(yīng)用服務(wù)器極為不穩(wěn)定,幾乎每兩天重新啟動一次,有時甚至每天重新啟動一次;
3.應(yīng)用服務(wù)器經(jīng)常做Full GC(Garbage Collection),而且時間很長,大約需要30-40秒,應(yīng)用服務(wù)器在做Full GC的時候是不響應(yīng)客戶的交易請求的,非常影響系統(tǒng)性能。
因?yàn)殚_發(fā)環(huán)境和產(chǎn)品環(huán)境會有不同,導(dǎo)致該問題發(fā)生有時會在產(chǎn)品環(huán)境中發(fā)生,通常可以使用工具跟蹤系統(tǒng)的內(nèi)存使用情況,在有些個別情況下或許某個時刻確實(shí)是使用了大量內(nèi)存導(dǎo)致out of memory,這時應(yīng)繼續(xù)跟蹤看接下來是否會有下降,如果一直居高不下這肯定就因?yàn)槌绦虻脑驅(qū)е聝?nèi)存泄漏。
五、不健壯代碼的特征及解決辦法
1、盡早釋放無用對象的引用。好的辦法是使用臨時變量的時候,讓引用變量在退出活動域后,自動設(shè)置為null,暗示垃圾收集器來收集該對象,防止發(fā)生內(nèi)存泄露。
對于仍然有指針指向的實(shí)例,jvm就不會回收該資源,因?yàn)槔厥諘⒅禐閚ull的對象作為垃圾,提高GC回收機(jī)制效率;
2、我們的程序里不可避免大量使用字符串處理,避免使用String,應(yīng)大量使用StringBuffer,每一個String對象都得獨(dú)立占用內(nèi)存一塊區(qū)域; String str = “aaa”;String str2 = “bbb”;String str3 = str + str2;//假如執(zhí)行此次之后str ,str2以后再不被調(diào)用,那它就會被放在內(nèi)存中等待Java的gc去回收,程序內(nèi)過多的出現(xiàn)這樣的情況就會報上面的那個錯誤,建議在使用字符串時能使用StringBuffer就不要用String,這樣可以省不少開銷;
3、盡量少用靜態(tài)變量,因?yàn)殪o態(tài)變量是全局的,GC不會回收的;
4、避免集中創(chuàng)建對象尤其是大對象,JVM會突然需要大量內(nèi)存,這時必然會觸發(fā)GC優(yōu)化系統(tǒng)內(nèi)存環(huán)境;顯示的聲明數(shù)組空間,而且申請數(shù)量還極大。這是一個案例想定供大家警戒
使用jspsmartUpload作文件上傳,運(yùn)行過程中經(jīng)常出現(xiàn)java.outofMemoryError的錯誤,檢查之后發(fā)現(xiàn)問題:組件里的代碼
m_totalBytes = m_request.getContentLength();m_binArray = new byte[m_totalBytes];問題原因是totalBytes這個變量得到的數(shù)極大,導(dǎo)致該數(shù)組分配了很多內(nèi)存空間,而且該數(shù)組不能及時釋放。解決辦法只能換一種更合適的辦法,至少是不會引發(fā)outofMemoryError的方式解決。參考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747
5、盡量運(yùn)用對象池技術(shù)以提高系統(tǒng)性能;生命周期長的對象擁有生命周期短的對象時容易引發(fā)內(nèi)存泄漏,例如大集合對象擁有大數(shù)據(jù)量的業(yè)務(wù)對象的時候,可以考慮分塊進(jìn)行處理,然后解決一塊釋放一塊的策略。
6、不要在經(jīng)常調(diào)用的方法中創(chuàng)建對象,尤其是忌諱在循環(huán)中創(chuàng)建對象。可以適當(dāng)?shù)氖褂胔ashtable,vector創(chuàng)建一組對象容器,然后從容器中去取那些對象,而不用每次new之后又丟棄
7、一般都是發(fā)生在開啟大型文件或跟數(shù)據(jù)庫一次拿了太多的數(shù)據(jù),造成Out Of Memory Error的狀況,這時就大概要計(jì)算一下數(shù)據(jù)量的最大值是多少,并且設(shè)定所需最小及最大的內(nèi)存空間值。
設(shè)計(jì)模式之Singleton(單態(tài))
定義: Singleton模式主要作用是保證在Java應(yīng)用程序中,一個類Class只有一個實(shí)例存在。
在很多操作中,比如建立目錄數(shù)據(jù)庫連接都需要這樣的單線程操作。
還有, singleton能夠被狀態(tài)化;這樣,多個單態(tài)類在一起就可以作為一個狀態(tài)倉庫一樣向外提供服務(wù),比如,你要論壇中的帖子計(jì)數(shù)器,每次瀏覽一次需要計(jì)數(shù),單態(tài)類能否保持住這個計(jì)數(shù),并且能synchronize的安全自動加1,如果你要把這個數(shù)字永久保存到數(shù)據(jù)庫,你可以在不修改單態(tài)接口的情況下方便的做到。
另外方面,Singleton也能夠被無狀態(tài)化。提供工具性質(zhì)的功能,Singleton模式就為我們提供了這樣實(shí)現(xiàn)的可能。使用Singleton的好處還在于可以節(jié)省內(nèi)存,因?yàn)樗拗屏藢?shí)例的個數(shù),有利于Java垃圾回收(garbage collection)。
我們常常看到工廠模式中類裝入器(class loader)中也用Singleton模式實(shí)現(xiàn)的,因?yàn)楸谎b入的類實(shí)際也屬于資源。如何使用?
一般Singleton模式通常有幾種形式: public class Singleton {
private Singleton(){}
//在自己內(nèi)部定義自己一個實(shí)例,是不是很奇怪?
//注意這是private 只供內(nèi)部調(diào)用
private static Singleton instance = new Singleton();
}
第二種形式: public class Singleton {
}
使用Singleton.getInstance()可以訪問單態(tài)類。
上面第二中形式是lazy initialization,也就是說第一次調(diào)用時初始Singleton,以后就不用再生成了。
注意到lazy initialization形式中的synchronized,這個synchronized很重要,如果沒有synchronized,那么使用getInstance()是有可能得到多個Singleton實(shí)例。關(guān)于lazy initialization的Singleton有很多涉及double-checked locking(DCL)的討論,有興趣者進(jìn)一步研究。一般認(rèn)為第一種形式要更加安全些。
使用Singleton注意事項(xiàng):
有時在某些情況下,使用Singleton并不能達(dá)到Singleton的目的,如有多個Singleton對象同時被不同的類裝入器裝載;在EJB這樣的分布式系統(tǒng)中使用也要注意這種情況,因?yàn)镋JB是跨服務(wù)器,跨JVM的。
private static Singleton instance = null;public static synchronized Singleton getInstance(){
//這個方法比上面有所改進(jìn),不用每次都進(jìn)行生成對象,只是第一次
//使用時生成實(shí)例,提高了效率!if(instance==null)
instance=new Singleton();return instance;} //這里提供了一個供外部訪問本class的靜態(tài)方法,可以直接訪問
public static Singleton getInstance(){
return instance;
} 我們以SUN公司的寵物店源碼(Pet Store 1.3.1)的ServiceLocator為例稍微分析一下:
在Pet Store中ServiceLocator有兩種,一個是EJB目錄下;一個是WEB目錄下,我們檢查這兩個ServiceLocator會發(fā)現(xiàn)內(nèi)容差不多,都是提供EJB的查詢定位服務(wù),可是為什么要分開呢?仔細(xì)研究對這兩種ServiceLocator才發(fā)現(xiàn)區(qū)別:在WEB中的ServiceLocator的采取Singleton模式,ServiceLocator屬于資源定位,理所當(dāng)然應(yīng)該使用Singleton模式。但是在EJB中,Singleton模式已經(jīng)失去作用,所以ServiceLocator才分成兩種,一種面向WEB服務(wù)的,一種是面向EJB服務(wù)的。
Singleton模式看起來簡單,使用方法也很方便,但是真正用好,是非常不容易,需要對Java的類線程 內(nèi)存等概念有相當(dāng)?shù)牧私狻?/p>
設(shè)計(jì)模式之Factory(2008-08-07 12:59:17)轉(zhuǎn)載 標(biāo)簽:
雜談
設(shè)計(jì)模式之Factory
定義:提供創(chuàng)建對象的接口.為何使用?
工廠模式是我們最常用的模式了,著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程序系統(tǒng)可以說是隨處可見。
為什么工廠模式是如此常用?因?yàn)楣S模式就相當(dāng)于創(chuàng)建實(shí)例對象的new,我們經(jīng)常要根據(jù)類Class生成實(shí)例對象,如A a=new A()工廠模式也是用來創(chuàng)建實(shí)例對象的,所以以后new時就要多個心眼,是否可以考慮實(shí)用工廠模式,雖然這樣做,可能多做一些工作,但會給你系統(tǒng)帶來更大的可擴(kuò)展性和盡量少的修改量。我們以類Sample為例,如果我們要創(chuàng)建Sample的實(shí)例對象: Sample sample=new Sample();可是,實(shí)際情況是,通常我們都要在創(chuàng)建sample實(shí)例時做點(diǎn)初始化的工作,比如賦值查詢數(shù)據(jù)庫等。
首先,我們想到的是,可以使用Sample的構(gòu)造函數(shù),這樣生成實(shí)例就寫成: Sample sample=new Sample(參數(shù));但是,如果創(chuàng)建sample實(shí)例時所做的初始化工作不是象賦值這樣簡單的事,可能是很長一段代碼,如果也寫入構(gòu)造函數(shù)中,那你的代碼很難看了(就需要Refactor重整)。
為什么說代碼很難看,初學(xué)者可能沒有這種感覺,我們分析如下,初始化工作如果是很長一段代碼,說明要做的工作很多,將很多工作裝入一個方法中,相當(dāng)于將很多雞蛋放在一個籃子里,是很危險的,這也是有背于Java面向?qū)ο蟮脑瓌t,面向?qū)ο蟮姆庋b(Encapsulation)和分派(Delegation)告訴我們,盡量將長的代碼分派“切割”成每段,將每段再“封裝”起來(減少段和段之間偶合聯(lián)系性),這樣,就會將風(fēng)險分散,以后如果需要修改,只要更改每段,不會再發(fā)生牽一動百的事情。
在本例中,首先,我們需要將創(chuàng)建實(shí)例的工作與使用實(shí)例的工作分開, 也就是說,讓創(chuàng)建實(shí)例所需要的大量初始化工作從Sample的構(gòu)造函數(shù)中分離出去。這時我們就需要Factory工廠模式來生成對象了,不能再用上面簡單new Sample(參數(shù))。還有,如果Sample有個繼承如MySample, 按照面向接口編程,我們需要將Sample抽象成一個接口.現(xiàn)在Sample是接口,有兩個子類MySample 和HisSample.我們要實(shí)例化他們時,如下: Sample mysample=new MySample();Sample hissample=new HisSample();隨著項(xiàng)目的深入,Sample可能還會“生出很多兒子出來”, 那么我們要對這些兒子一個個實(shí)例化,更糟糕的是,可能還要對以前的代碼進(jìn)行修改:加入后來生出兒子的實(shí)例.這在傳統(tǒng)程序中是無法避免的.但如果你一開始就有意識使用了工廠模式,這些麻煩就沒有了.回調(diào)機(jī)制
(2008-08-13 23:55:39)轉(zhuǎn)載 標(biāo)簽:
雜談 什么是回調(diào)
軟件模塊之間總是存在著一定的接口,從調(diào)用方式上,可以把他們分為三類:同步調(diào)用、回調(diào)和異步調(diào)用。同步調(diào)用是一種阻塞式調(diào)用,調(diào)用方要等待對方執(zhí)行完畢才返回,它是一種單向調(diào)用;回調(diào)是一種雙向調(diào)用模式,也就是說,被調(diào)用方在接口被調(diào)用時也會調(diào)用對方的接口;異步調(diào)用是一種類似消息或事件的機(jī)制,不過它的調(diào)用方向剛好相反,接口的服務(wù)在收到某種訊息或發(fā)生某種事件時,會主動通知客戶方(即調(diào)用客戶方的接口)。回調(diào)和異步調(diào)用的關(guān)系非常緊密,通常我們使用回調(diào)來實(shí)現(xiàn)異步消息的注冊,通過異步調(diào)用來實(shí)現(xiàn)消息的通知。同步調(diào)用是三者當(dāng)中最簡單的,而回調(diào)又常常是異步調(diào)用的基礎(chǔ),因此,下面我們著重討論回調(diào)機(jī)制在不同軟件架構(gòu)中的實(shí)現(xiàn)。
對于不同類型的語言(如結(jié)構(gòu)化語言和對象語言)、平臺(Win32、JDK)或構(gòu)架(CORBA、DCOM、WebService),客戶和服務(wù)的交互除了同步方式以外,都需要具備一定的異步通知機(jī)制,讓服務(wù)方(或接口提供方)在某些情況下能夠主動通知客戶,而回調(diào)是實(shí)現(xiàn)異步的一個最簡捷的途徑。
對于一般的結(jié)構(gòu)化語言,可以通過回調(diào)函數(shù)來實(shí)現(xiàn)回調(diào)。回調(diào)函數(shù)也是一個函數(shù)或過程,不過它是一個由調(diào)用方自己實(shí)現(xiàn),供被調(diào)用方使用的特殊函數(shù)。在面向?qū)ο蟮恼Z言中,回調(diào)則是通過接口或抽象類來實(shí)現(xiàn)的,我們把實(shí)現(xiàn)這種接口的類成為回調(diào)類,回調(diào)類的對象成為回調(diào)對象。對于象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調(diào)對象、回調(diào)方法等特性,也能兼容過程語言的回調(diào)函數(shù)機(jī)制。Windows平臺的消息機(jī)制也可以看作是回調(diào)的一種應(yīng)用,我們通過系統(tǒng)提供的接口注冊消息處理函數(shù)(即回調(diào)函數(shù)),從而實(shí)現(xiàn)接收、處理消息的目的。由于Windows平臺的API是用C語言來構(gòu)建的,我們可以認(rèn)為它也是回調(diào)函數(shù)的一個特例。
對于分布式組件代理體系CORBA,異步處理有多種方式,如回調(diào)、事件服務(wù)、通知服務(wù)等。事件服務(wù)和通知服務(wù)是CORBA用來處理異步消息的標(biāo)準(zhǔn)服務(wù),他們主要負(fù)責(zé)消息的處理、派發(fā)、維護(hù)等工作。對一些簡單的異步處理過程,我們可以通過回調(diào)機(jī)制來實(shí)現(xiàn)。
反射機(jī)制
(2008-08-14 00:04:19)轉(zhuǎn)載 標(biāo)簽:
雜談
JAVA反射機(jī)制
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機(jī)制。
Java反射機(jī)制主要提供了以下功能: 在運(yùn)行時判斷任意一個對象所屬的類;在運(yùn)行時構(gòu)造任意一個類的對象;在運(yùn)行時判斷任意一個類所具有的成員變量和方法;在運(yùn)行時調(diào)用任意一個對象的方法;生成動態(tài)代理。1.得到某個對象的屬性 public Object getProperty(Object owner, String fieldName)throws Exception { 2 Class ownerClass = owner.getClass();3 4 Field field = ownerClass.getField(fieldName);5 6 Object property = field.get(owner);7 8 return property;9 } Class ownerClass = owner.getClass():得到該對象的Class。
Field field = ownerClass.getField(fieldName):通過Class得到類聲明的屬性。
Object property = field.get(owner):通過對象得到該屬性的實(shí)例,如果這個屬性是非公有的,這里會報IllegalAccessException。
2.得到某個類的靜態(tài)屬性
public Object getStaticProperty(String className, String fieldName)2 throws Exception { 3 Class ownerClass = Class.forName(className);4 5 Field field = ownerClass.getField(fieldName);6 7 Object property = field.get(ownerClass);8 9 return property;10 }
Class ownerClass = Class.forName(className):首先得到這個類的Class。
Field field = ownerClass.getField(fieldName):和上面一樣,通過Class得到類聲明的屬性。
Object property = field.get(ownerClass):這里和上面有些不同,因?yàn)樵搶傩允庆o態(tài)的,所以直接從類的Class里取。
3.執(zhí)行某對象的方法
public Object invokeMethod(Object owner, String methodName, Object[] args)throws Exception { 2 3 Class ownerClass = owner.getClass();4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Method method = ownerClass.getMethod(methodName, argsClass);12 13 return method.invoke(owner, args);14 } Class owner_class = owner.getClass():首先還是必須得到這個對象的Class。
5~9行:配置參數(shù)的Class數(shù)組,作為尋找Method的條件。
Method method = ownerClass.getMethod(methodName, argsClass):通過Method名和參數(shù)的Class數(shù)組得到要執(zhí)行的Method。
method.invoke(owner, args):執(zhí)行該Method,invoke方法的參數(shù)是執(zhí)行這個方法的對象,和參數(shù)數(shù)組。返回值是Object,也既是該方法的返回值。
4.執(zhí)行某個類的靜態(tài)方法
public Object invokeStaticMethod(String className, String methodName, 2 Object[] args)throws Exception { 3 Class ownerClass = Class.forName(className);4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Method method = ownerClass.getMethod(methodName, argsClass);12 13 return method.invoke(null, args);14 }
基本的原理和實(shí)例3相同,不同點(diǎn)是最后一行,invoke的一個參數(shù)是null,因?yàn)檫@是靜態(tài)方法,不需要借助實(shí)例運(yùn)行。
5.新建實(shí)例 1 2 public Object newInstance(String className, Object[] args)throws Exception { 3 Class newoneClass = Class.forName(className);4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Constructor cons = newoneClass.getConstructor(argsClass);12 13 return cons.newInstance(args);14 15 }
這里說的方法是執(zhí)行帶參數(shù)的構(gòu)造函數(shù)來新建實(shí)例的方法。如果不需要參數(shù),可以直接使用newoneClass.newInstance()來實(shí)現(xiàn)。
Class newoneClass = Class.forName(className):第一步,得到要構(gòu)造的實(shí)例的Class。
第5~第9行:得到參數(shù)的Class數(shù)組。
Constructor cons = newoneClass.getConstructor(argsClass):得到構(gòu)造子。
cons.newInstance(args):新建實(shí)例。
6.判斷是否為某個類的實(shí)例 public boolean isInstance(Object obj, Class cls){ 2 return cls.isInstance(obj);3 }
7.得到數(shù)組中的某個元素 public Object getByArray(Object array, int index){ 2 return Array 在了解Oracle體系結(jié)構(gòu)之前必須掌握以下兩個基本的概念: 數(shù)據(jù)庫和實(shí)例。
?/P>
一、數(shù)據(jù)庫 ?/P> 數(shù)據(jù)庫(database)是一個數(shù)據(jù)集合.?/P> 無論數(shù)據(jù)庫是采用關(guān)系結(jié)構(gòu)還是面向?qū)ο蠼Y(jié)構(gòu), oracle數(shù)據(jù)庫都將 ?/P> 其數(shù)據(jù)存放在數(shù)據(jù)文件中.在其內(nèi)部, 數(shù)據(jù)庫結(jié)構(gòu)數(shù)據(jù)對文件的邏輯 ?/P> 映射, 使不同的數(shù)據(jù)分開存儲, 這些邏輯劃分稱為表空間.?/P> 表空間和文件介紹: ?/P> 1: 表空間 ?/P> 表空間(tablespace)是數(shù)據(jù)庫的邏輯劃分, 每個數(shù)據(jù)庫至少有一個表空間,叫做系統(tǒng)表空間(system 表空間).一個表空間只能屬于一個數(shù)據(jù)庫。
?/P> 每個表空間由同一個磁盤上的一個或多個文件組成, 這些文件稱為數(shù)據(jù)文件.?/P> 表空間的特性: ?/P>(1)控制數(shù)據(jù)庫數(shù)據(jù)磁盤分配 ?/P>(2)限制用戶在表空間中可以使用的磁盤空間大小 ?/P>(3)表空間具有 online, offline, readonly, readwrite屬性 ?/P> 修改表空間的屬性: ?/P> SQL> alter tablespace 表空間名稱屬性;查詢表空間狀態(tài): SQL> select tablespace_name, status from dba_tablespaces;?/P> 注意: system, undo, temp表空間不能設(shè)為offline屬性.?/P>(4)完成部分?jǐn)?shù)據(jù)庫的備份與恢復(fù) ?/P>(5)表空間通過數(shù)據(jù)文件來擴(kuò)大, 表空間的大小等于構(gòu)成該表空間的所以數(shù)據(jù)文件的大小只和.?/P> 查詢表空間與數(shù)據(jù)文件對應(yīng)關(guān)系: ?/P> SQL> select tablespace_name, bytes, file_name from dba_data_files;?/P> 基于表空間的幾個操作: ?/P>(1)查詢用戶缺省表空間: SQL> select username, default_tablespace from dba_users;(2)查詢表與存儲該表的表空間: SQL> select table_name, tablespace_name from user_tables;(3)修改用戶缺省表空間: SQL> alter user username default tablespace tablespace_name;(4)將數(shù)據(jù)從一個表空間移動到另一個表空間: SQL> alter table table_name move tablespace tablespace_name;分享 2.數(shù)據(jù)文件 每個表空間由同一個磁盤上的一個或多個文件組成, 這些文件叫做數(shù)據(jù)文件(datafile),數(shù)據(jù)文件只能屬于一個表空間.數(shù)據(jù)文件創(chuàng)建后可以改變大小.創(chuàng)建新的表空間需要創(chuàng)建新的數(shù)據(jù)文件.數(shù)據(jù)文件一旦加入到表空間中, 就不能從表空間中移走, 也不能與其他表空間發(fā)生聯(lián)系。
數(shù)據(jù)庫必須的三類文件是 data file, control file, redolog file.其他文件 prameter file,password file, archived log files并不是數(shù)據(jù)庫必須的, 他們只是輔助數(shù)據(jù)庫的。
查看數(shù)據(jù)庫的物理文件組成:(1)查看數(shù)據(jù)文件: SQL> select * from v$datafile;(2)查看控制文件: SQL> select * from v$controlfile;(3)查看日志文件: SQL> select * from v$logfile;二: 實(shí)例
通俗的講實(shí)例就是操作oracle數(shù)據(jù)庫的一種手段.數(shù)據(jù)庫實(shí)例也稱作服務(wù)器, 是用來訪問數(shù)據(jù)庫文件集的存儲結(jié)構(gòu)及后臺進(jìn)程的集合.一個數(shù)據(jù)庫可以被多個實(shí)例訪問(稱為真正的應(yīng)用群集選項(xiàng)).決定實(shí)例的大小及組成的各種參數(shù)或者存儲在名稱init.ora的初始化文件中, 或者隱藏
在數(shù)據(jù)庫內(nèi)部的服務(wù)器參數(shù)文件中.通過spfile引用該文件, spfile存儲在spfile.ora文件中.實(shí)例啟動時讀取初始化文件, 數(shù)據(jù)庫系統(tǒng)管理員可以修改該文件, 對初始化文件的修改只有在下次啟動時才有效。
Instance分為兩部分:
1: memory structure(內(nèi)存結(jié)構(gòu))
memory structure分為兩部分:SGA(System Global Area)區(qū)是用于存儲數(shù)據(jù)庫信息的內(nèi)存區(qū),該信息為數(shù)據(jù)庫進(jìn)程所共享。它包含Oracle 服務(wù)器的數(shù)據(jù)和控制信息, 它是在Oracle 服務(wù)器所駐留的計(jì)算機(jī)的實(shí)際內(nèi)存中得以分配,如果實(shí)際內(nèi)存不夠再往虛擬內(nèi)存中寫。
包括:.share pool.datafase buffer cache.redo log buffer.other structures PGA(Program Global Area)區(qū)包含單個服務(wù)器進(jìn)程或單個后臺進(jìn)程的數(shù)據(jù)和控制信息,與幾個進(jìn)程共享的SGA 正相反PGA 是只被一個進(jìn)程使用的區(qū)域,PGA 在創(chuàng)建進(jìn)程時分配在終止進(jìn)程時回收。
2: background process(后臺進(jìn)程)包括:
.PMON 負(fù)責(zé)在一個Oracle 進(jìn)程失敗時清理資源
.SMON 檢查數(shù)據(jù)庫的一致性如有必要還會在數(shù)據(jù)庫打開時啟動數(shù)據(jù)庫的恢復(fù)
.DBWR 負(fù)責(zé)將更改的數(shù)據(jù)從數(shù)據(jù)庫緩沖區(qū)高速緩存寫入數(shù)據(jù)文件.LGWR 將重做日志緩沖區(qū)中的更改寫入在線重做日志文件
.CKPT 負(fù)責(zé)在每當(dāng)緩沖區(qū)高速緩存中的更改永久地記錄在數(shù)據(jù)庫中時,更新控制文件和數(shù)據(jù)文件中的數(shù)據(jù)庫狀態(tài)信息。
全面解析JDBC
綜述:Java數(shù)據(jù)庫連接體系結(jié)構(gòu)是用于Java應(yīng)用程序連接數(shù)據(jù)庫的標(biāo)準(zhǔn)方法。JDBC對Java程序員而言是API,對實(shí)現(xiàn)與數(shù)據(jù)庫連接的服務(wù)提供商而言是接口模型。作為API,JDBC為程序開發(fā)提供標(biāo)準(zhǔn)的接口,并為數(shù)據(jù)庫廠商及第三方中間件廠商實(shí)現(xiàn)與數(shù)據(jù)庫的連接提供了標(biāo)準(zhǔn)方法。JDBC使用已有的SQL標(biāo)準(zhǔn)并支持與其它數(shù)據(jù)庫連接標(biāo)準(zhǔn),如ODBC之間的橋接。JDBC實(shí)現(xiàn)了所有這些面向標(biāo)準(zhǔn)的目標(biāo)并且具有簡單、嚴(yán)格類型定義且高性能實(shí)現(xiàn)的接口。
如何選擇合適的JDBC產(chǎn)品?
有關(guān)JDBC最新的信息,有興趣的讀者可以查閱JDBC的官方網(wǎng)站--即JavaSoft的主頁,其URL為:http://Java.sun.com/products/jdbc
1.JavaSoft框架
JavaSoft提供三種JDBC產(chǎn)品組件,它們是Java開發(fā)工具包(JDK)的組成部份:JDBC驅(qū)動程序管理器、JDBC驅(qū)動程序測試工具包和JDBC-ODBC橋。
JDBC驅(qū)動程序管理器是JDBC體系結(jié)構(gòu)的支柱。它實(shí)際上很小,也很簡單;其主要作用是把Java應(yīng)用程序連接到正確的JDBC驅(qū)動程序上,然后即退出。
JDBC驅(qū)動程序測試工具包為使JDBC驅(qū)動程序運(yùn)行您的程序提供一定的可信度。只有通過JDBC驅(qū)動程序測試的驅(qū)動程序才被認(rèn)為是符合JDBC標(biāo)準(zhǔn)TM的。
JDBC-ODBC橋使ODBC驅(qū)動程序可被用作JDBC驅(qū)動程序。它的實(shí)現(xiàn)為JDBC的快速發(fā)展提供了一條途徑,其長遠(yuǎn)目標(biāo)提供一種訪問某些不常見的DBMS(如果對這些不常見的DBMS未實(shí)現(xiàn)JDBC)的方法。
2.JDBC驅(qū)動程序的類型
目前比較常見的JDBC驅(qū)動程序可分為以下四個種類:
(1)JDBC-ODBC橋加ODBC驅(qū)動程序
JavaSoft橋產(chǎn)品利用ODBC驅(qū)動程序提供JDBC訪問。注意,必須將ODBC二進(jìn)制代碼(許多情況下還包括數(shù)據(jù)庫客戶機(jī)代碼)加載到使用該驅(qū)動程序的每個客戶機(jī)上。因此,這種類型的驅(qū)動程序最適合于企業(yè)網(wǎng)(這種網(wǎng)絡(luò)上客戶機(jī)的安裝不是主要問題),或者是用Java編寫的三層結(jié)構(gòu)的應(yīng)用程序服務(wù)器代碼。
(2)本地API
這種類型的驅(qū)動程序把客戶機(jī)API上的JDBC調(diào)用轉(zhuǎn)換為Oracle、Sybase、Informix、DB2或其它DBMS的調(diào)用。注意,象橋驅(qū)動程序一樣,這種類型的驅(qū)動程序要求將某些二進(jìn)制代碼加載到每臺客戶機(jī)上。
(3)JDBC網(wǎng)絡(luò)純Java驅(qū)動程序
這種驅(qū)動程序?qū)DBC轉(zhuǎn)換為與DBMS無關(guān)的網(wǎng)絡(luò)協(xié)議,之后這種協(xié)議又被某個服務(wù)器轉(zhuǎn)換為一種DBMS協(xié)議。這種網(wǎng)絡(luò)服務(wù)器中間件能夠?qū)⑺募僇ava客戶機(jī)連接到多種不同的數(shù)據(jù)庫上。所用的具體協(xié)議取決于提供者。通常,這是最為靈活的JDBC驅(qū)動程序。有可能所有這種解決方案的提供者都提供適合于Intranet用的產(chǎn)品。為了使這些產(chǎn)品也支持Internet訪問,它們必須處理Web所提出的安全性、通過防火墻的訪問等方面的額外要求。幾家提供者正將JDBC驅(qū)動程序加到他們現(xiàn)有的數(shù)據(jù)庫中間件產(chǎn)品中。
(4)本地協(xié)議純Java驅(qū)動程序
這種類型的驅(qū)動程序?qū)DBC調(diào)用直接轉(zhuǎn)換為DBMS所使用的網(wǎng)絡(luò)協(xié)議。這將允許從客戶機(jī)機(jī)器上直接調(diào)用DBMS服務(wù)器,是Intranet訪問的一個很實(shí)用的解決方法。由于許多這樣的協(xié)議都是專用的,因此數(shù)據(jù)庫提供者自己將是主要來源,有幾家提供者已在著手做這件事了。
據(jù)專家預(yù)計(jì)第(3)、(4)類驅(qū)動程序?qū)⒊蔀閺腏DBC訪問數(shù)據(jù)庫的首方法。第(1)、(2)類驅(qū)動程序在直接的純Java驅(qū)動程序還沒有上市前會作為過渡方案來使用。對第(1)、(2)類驅(qū)動程序可能會有一些變種,這些變種要求有連接器,但通常這些是更加不可取的解決方案。第(3)、(4)類驅(qū)動程序提供了Java的所有優(yōu)點(diǎn),包括自動安裝(例如,通過使用JDBC驅(qū)動程序的appletapplet來下載該驅(qū)動程序)。
3.JDBC驅(qū)動程序的獲取
目前已有幾十個(1)類的驅(qū)動程序,即可與Javasoft橋聯(lián)合使用的ODBC驅(qū)動程序的驅(qū)動程序。有大約十多個屬于種類(2)的驅(qū)動程序是以DBMS的本地API為基礎(chǔ)編寫的。只有幾個屬于種類(3)的驅(qū)動程序,其首批提供者是SCO、OpenHorizon、Visigenic和WebLogic。此外,JavaSoft和數(shù)據(jù)庫連接的領(lǐng)先提供者Intersolv還合作研制了JDBC-ODBC橋和JDBC驅(qū)動程序測試工具包。
如何建立JDBC連接?
Connection 對象代表與數(shù)據(jù)庫的連接。連接過程包括所執(zhí)行的 SQL 語句和在該連接上所返回的結(jié)果。一個應(yīng)用程序可與單個數(shù)據(jù)庫有一個或多個連接,或者可與許多數(shù)據(jù)庫有連接。
1.打開連接
與數(shù)據(jù)庫建立連接的標(biāo)準(zhǔn)方法是調(diào)用DriverManager.getConnection方法。該方法接受含有某個URL的字符串。DriverManager類(即所謂的JDBC管理層)將嘗試找到可與那個URL所代表的數(shù)據(jù)庫進(jìn)行連接的驅(qū)動程序。DriverManager類存有已注冊的Driver類的清單。當(dāng)調(diào)用方法getConnection時,它將檢查清單中的每個驅(qū)動程序,直到找到可與URL中指定的數(shù)據(jù)庫進(jìn)行連接的驅(qū)動程序?yàn)橹埂river的方法connect使用這個URL來建立實(shí)際的連接。
用戶可繞過JDBC管理層直接調(diào)用Driver方法。這在以下特殊情況下將很有用:當(dāng)兩個驅(qū)動器可同時連接到數(shù)據(jù)庫中,而用戶需要明確地選用其中特定的驅(qū)動器。但一般情況下,讓DriverManager類處理打開連接這種事將更為簡單。
下述代碼顯示如何打開一個與位于URL“jdbc:odbc:wombat”的數(shù)據(jù)庫的連接。所用的用戶標(biāo)識符為“freely”,口令為“ec”:
String url = “jdbc:odbc:wombat”;
Connection con = DriverManager.getConnection(url,“freely”,“ec”);
2.一般用法的URL
由于URL常引起混淆,我們將先對一般URL作簡單說明,然后再討論JDBCURL。URL(統(tǒng)一資源定位符)提供在Internet上定位資源所需的信息。可將它想象為一個地址。URL的第一部份指定了訪問信息所用的協(xié)議,后面總是跟著冒號。常用的協(xié)議有“ftp”(代表“文件傳輸協(xié)議”)和“http”(代表“超文本傳輸協(xié)議”)。如果協(xié)議是“file”,表示資源是在某個本地文件系統(tǒng)上而非在Internet上(下例用于表示我們所描述的部分;它并非URL的組成部分)。
ftp://Javasoft.com/docs/JDK-1_apidocs.zip
http://Java.sun.com/products/jdk/CurrentRelease
file:/home/haroldw/docs/books/tutorial/summary.html
URL的其余部份(冒號后面的)給出了數(shù)據(jù)資源所處位置的有關(guān)信息。如果協(xié)議是file,則URL的其余部份是文件的路徑。對于ftp和http協(xié)議,URL的其余部份標(biāo)識了主機(jī)并可選地給出某個更詳盡的地址路徑。例如,以下是JavaSoft主頁的URL。該URL只標(biāo)識了主機(jī):http://Java.sun.com。從該主頁開始瀏覽,就可以進(jìn)到許多其它的網(wǎng)頁中,其中之一就是JDBC主頁。JDBC主頁的URL更為具體,它具體表示為: http://Java.sun.com/products/jdbc
3.JDBC URL
JDBC URL提供了一種標(biāo)識數(shù)據(jù)庫的方法,可以使相應(yīng)的驅(qū)動程序能識別該數(shù)據(jù)庫并與之建立連接。實(shí)際上,驅(qū)動程序編程員將決定用什么JDBC URL來標(biāo)識特定的驅(qū)動程序。用戶不必關(guān)心如何來形成JDBC URL;他們只須使用與所用的驅(qū)動程序一起提供的URL即可。JDBC的作用是提供某些約定,驅(qū)動程序編程員在構(gòu)造他們的JDBC URL時應(yīng)該遵循這些約定。
由于JDBC URL要與各種不同的驅(qū)動程序一起使用,因此這些約定應(yīng)非常靈活。首先,它們應(yīng)允許不同的驅(qū)動程序使用不同的方案來命名數(shù)據(jù)庫。例如,odbc子協(xié)議允許(但并不是要求)URL含有屬性值。
其次,JDBC URL應(yīng)允許驅(qū)動程序編程員將一切所需的信息編入其中。這樣就可以讓要與給定數(shù)據(jù)庫對話的applet打開數(shù)據(jù)庫連接,而無須要求用戶去做任何系統(tǒng)管理工作。
最后,JDBC URL應(yīng)允許某種程度的間接性。也就是說,JDBC URL可指向邏輯主機(jī)或數(shù)據(jù)庫名,而這種邏輯主機(jī)或數(shù)據(jù)庫名將由網(wǎng)絡(luò)命名系統(tǒng)動態(tài)地轉(zhuǎn)換為實(shí)際的名稱。這可以使系統(tǒng)管理員不必將特定主機(jī)聲明為JDBC名稱的一部份。網(wǎng)絡(luò)命名服務(wù)(例如DNS、NIS和DCE)有多種,而對于使用哪種命名服務(wù)并無限制。
JDBC URL的標(biāo)準(zhǔn)語法如下所示。它由三部分組成,各部分間用冒號分隔: jdbc:<子協(xié)遙荊海甲用?疲?br>
JDBC URL的三個部分可分解如下:
(1)jdbc協(xié)議:JDBC URL中的協(xié)議總是jdbc。
(2)<子協(xié)議>:驅(qū)動程序名或數(shù)據(jù)庫連接機(jī)制(這種機(jī)制可由一個或多個驅(qū)動程序支持)的名稱。子協(xié)議名的典型示例是“odbc”,該名稱是為用于指定ODBC風(fēng)格的數(shù)據(jù)資源名稱的URL專門保留的。例如,為了通過JDBC-ODBC橋來訪問某個數(shù)據(jù)庫,可以用如下所示的URL:jdbc:odbc:book。本例中,子協(xié)議為“odbc”,子名稱“book”是本地ODBC數(shù)據(jù)資源。如果要用網(wǎng)絡(luò)命名服務(wù)(這樣JDBC URL中的數(shù)據(jù)庫名稱不必是實(shí)際名稱),則命名服務(wù)可以作為子協(xié)議。例如,可用如下所示的URL:jdbc:dcenaming:accounts。本例中,該URL指定了本地DCE命名服務(wù)應(yīng)該將數(shù)據(jù)庫名稱“accounts”解析為更為具體的可用于連接真實(shí)數(shù)據(jù)庫的名稱。
(3)<子名稱>:種標(biāo)識數(shù)據(jù)庫的方法。子名稱可以依不同的子協(xié)議而變化。它還可以有子名稱的子名稱(含有驅(qū)動程序編程員所選的任何內(nèi)部語法)。使用子名稱的目的是為定位數(shù)據(jù)庫提供足夠的信息。前例中,因?yàn)镺DBC將提供其余部份的信息,因此用“book”就已足夠。然而,位于遠(yuǎn)程服務(wù)器上的數(shù)據(jù)庫需要更多的信息。例如,如果數(shù)據(jù)庫是通過Internet來訪問的,則在JDBC URL中應(yīng)將網(wǎng)絡(luò)地址作為子名稱的一部份包括進(jìn)去,且必須遵循如下所示的標(biāo)準(zhǔn)URL命名約定://主機(jī)名:端口/子協(xié)議。
假設(shè)“dbnet”是個用于將某個主機(jī)連接到Internet上的協(xié)議,則JDBC URL應(yīng)為:jdbc:dbnet://wombat:356/fred。
4.“odbc”子協(xié)議
子協(xié)議odbc是一種特殊情況。它是為用于指定ODBC風(fēng)格的數(shù)據(jù)資源名稱的URL而保留的,并具有下列特性:允許在子名稱(數(shù)據(jù)資源名稱)后面指定任意多個屬性值。odbc子協(xié)議的完整語法為:
jdbc:odbc:<數(shù)據(jù)資源名稱>[;<屬性名>=<屬性值>],因此,以下都是合法的jdbc:odbc名稱:
jdbc:odbc:qeor7
jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:qeora;UID=kgh;PWD=fooey
5.注冊子協(xié)議
驅(qū)動程序編程員可保留某個名稱以將之用作JDBC URL的子協(xié)議名。當(dāng)DriverManager類將此名稱加到已注冊的驅(qū)動程序清單中時,為之保留該名稱的驅(qū)動程序應(yīng)能識別該名稱并與它所標(biāo)識的數(shù)據(jù)庫建立連接。例如,odbc是為JDBC-ODBC橋而保留的。假設(shè)有個Miracle公司,它可能會將“miracle”注冊為連接到其Miracle DBMS上的JDBC驅(qū)動程序的子協(xié)議,從而使其他人都無法使用這個名稱。
JavaSoft目前作為非正式代理負(fù)責(zé)注冊JDBC子協(xié)議名稱。要注冊某個子協(xié)議名稱,請發(fā)送電子郵件到下述地址:jdbc@wombat.eng.sun.com。
6.發(fā)送SQL語句
連接一旦建立,就可用來向它所涉及的數(shù)據(jù)庫傳送SQL語句。JDBC對可被發(fā)送的SQL語句類型不加任何限制。這就提供了很大的靈活性,即允許使用特定的數(shù)據(jù)庫語句或甚至于非SQL語句。然而,它要求用戶自己負(fù)責(zé)確保所涉及的數(shù)據(jù)庫可以處理所發(fā)送的SQL語句,否則將自食其果。例如,如果某個應(yīng)用程序試圖向不支持儲存程序的DBMS發(fā)送儲存程序調(diào)用,就會失敗并將拋出異常。JDBC要求驅(qū)動程序應(yīng)至少能提供ANSI SQL-2 Entry Level功能才可算是符合JDBC標(biāo)準(zhǔn)TM的。這意味著用戶至少可信賴這一標(biāo)準(zhǔn)級別的功能。
JDBC提供了三個類,用于向數(shù)據(jù)庫發(fā)送SQL語句。Connection接口中的三個方法可用于創(chuàng)建這些類的實(shí)例。下面列出這些類及其創(chuàng)建方法:
(1)Statement:由方法createStatement所創(chuàng)建。Statement對象用于發(fā)送簡單的SQL語句。
(2)PreparedStatement:由方法prepareStatement所創(chuàng)建。PreparedStatement對象用于發(fā)送帶有一個或多個輸入?yún)?shù)(IN參數(shù))的SQL語句。PreparedStatement擁有一組方法,用于設(shè)置IN參數(shù)的值。執(zhí)行語句時,這些IN參數(shù)將被送到數(shù)據(jù)庫中。PreparedStatement的實(shí)例擴(kuò)展了Statement,因此它們都包括了Statement的方法。PreparedStatement對象有可能比Statement對象的效率更高,因?yàn)樗驯活A(yù)編譯過并存放在那以供將來使用。
(3)CallableStatement:由方法prepareCall所創(chuàng)建。CallableStatement對象用于執(zhí)行SQL儲存程序─一組可通過名稱來調(diào)用(就象函數(shù)的調(diào)用那樣)的SQL語句。CallableStatement對象從PreparedStatement中繼承了用于處理IN參數(shù)的方法,而且還增加了用于處理OUT參數(shù)和INOUT參數(shù)的方法。
不過通常來說createStatement方法用于簡單的SQL語句(不帶參數(shù))、prepareStatement方法用于帶一個或多個IN參數(shù)的SQL語句或經(jīng)常被執(zhí)行的簡單SQL語句,而prepareCall方法用于調(diào)用已儲存過程。
7.事務(wù)
事務(wù)由一個或多個這樣的語句組成:這些語句已被執(zhí)行、完成并被提交或還原。當(dāng)調(diào)用方法commit或rollback時,當(dāng)前事務(wù)即告就結(jié)束,另一個事務(wù)隨即開始。缺省情況下,新連接將處于自動提交模式。也就是說,當(dāng)執(zhí)行完語句后,將自動對那個語句調(diào)用commit方法。這種情況下,由于每個語句都是被單獨(dú)提交的,因此一個事務(wù)只由一個語句組成。如果禁用自動提交模式,事務(wù)將要等到commit或rollback方法被顯式調(diào)用時才結(jié)束,因此它將包括上一次調(diào)用commit或rollback方法以來所有執(zhí)行過的語句。對于第二種情況,事務(wù)中的所有語句將作為組來提交或還原。
方法commit使SQL語句對數(shù)據(jù)庫所做的任何更改成為永久性的,它還將釋放事務(wù)持有的全部鎖。而方法rollback將棄去那些更改。有時用戶在另一個更改生效前不想讓此更改生效。這可通過禁用自動提交并將兩個更新組合在一個事務(wù)中來達(dá)到。如果兩個更新都是成功,則調(diào)用commit方法,從而使兩個更新結(jié)果成為永久性的;如果其中之一或兩個更新都失敗了,則調(diào)用rollback方法,以將值恢復(fù)為進(jìn)行更新之前的值。
大多數(shù)JDBC驅(qū)動程序都支持事務(wù)。事實(shí)上,符合JDBC的驅(qū)動程序必須支持事務(wù)。DatabaseMetaData給出的信息描述DBMS所提供的事務(wù)支持水平。
8.事務(wù)隔離級別
如果DBMS支持事務(wù)處理,它必須有某種途徑來管理兩個事務(wù)同時對一個數(shù)據(jù)庫進(jìn)行操作時可能發(fā)生的沖突。用戶可指定事務(wù)隔離級別,以指明DBMS應(yīng)該花多大精力來解決潛在沖突。例如,當(dāng)事務(wù)更改了某個值而第二個事務(wù)卻在該更改被提交或還原前讀取該值時該怎么辦。
假設(shè)第一個事務(wù)被還原后,第二個事務(wù)所讀取的更改值將是無效的,那么是否可允許這種沖突?JDBC用戶可用以下代碼來指示DBMS允許在值被提交前讀取該值(“dirty讀取”),其中con是當(dāng)前連接:
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
事務(wù)隔離級別越高,為避免沖突所花的精力也就越多。Connection接口定義了五級,其中最低級別指定了根本就不支持事務(wù),而最高級別則指定當(dāng)事務(wù)在對某個數(shù)據(jù)庫進(jìn)行操作時,任何其它事務(wù)不得對那個事務(wù)正在讀取的數(shù)據(jù)進(jìn)行任何更改。通常,隔離級別越高,應(yīng)用程序執(zhí)行的速度也就越慢(由于用于鎖定的資源耗費(fèi)增加了,而用戶間的并發(fā)操作減少了)。在決定采用什么隔離級別時,開發(fā)人員必須在性能需求和數(shù)據(jù)一致性需求之間進(jìn)行權(quán)衡。當(dāng)然,實(shí)際所能支持的級別取決于所涉及的DBMS的功能。
當(dāng)創(chuàng)建Connection對象時,其事務(wù)隔離級別取決于驅(qū)動程序,但通常是所涉及的數(shù)據(jù)庫的缺省值。用戶可通過調(diào)用setIsolationLevel方法來更改事務(wù)隔離級別。新的級別將在該連接過程的剩余時間內(nèi)生效。要想只改變一個事務(wù)的事務(wù)隔離級別,必須在該事務(wù)開始前進(jìn)行設(shè)置,并在該事務(wù)結(jié)束后進(jìn)行復(fù)位。我們不提倡在事務(wù)的中途對事務(wù)隔離級別進(jìn)行更改,因?yàn)檫@將立即觸發(fā)commit方法的調(diào)用,使在此之前所作的任何更改變成永久性的。JDBC驅(qū)動管理內(nèi)幕是怎么樣的?
DriverManager 類是 JDBC 的管理層,作用于用戶和驅(qū)動程序之間。它跟蹤可用的驅(qū)動程序,并在數(shù)據(jù)庫和相應(yīng)驅(qū)動程序之間建立連接。另外,DriverManager類也處理諸如驅(qū)動程序登錄時間限制及登錄和跟蹤消息的顯示等事務(wù)。
對于簡單的應(yīng)用程序,一般程序員需要在此類中直接使用的唯一方法是DriverManager.getConnection。正如名稱所示,該方法將建立與數(shù)據(jù)庫的連接。JDBC允許用戶調(diào)用DriverManager的方法getDriver、getDrivers和registerDriver及Driver的方法connect。但多數(shù)情況下,讓DriverManager類管理建立連接的細(xì)節(jié)為上策。
1.跟蹤可用驅(qū)動程序
DriverManager類包含一列Driver類,它們已通過調(diào)用方法DriverManager.registerDriver對自己進(jìn)行了注冊。所有Driver類都必須包含有一個靜態(tài)部分。它創(chuàng)建該類的實(shí)例,然后在加載該實(shí)例時DriverManager類進(jìn)行注冊。這樣,用戶正常情況下將不會直接調(diào)用DriverManager.registerDriver;而是在加載驅(qū)動程序時由驅(qū)動程序自動調(diào)用。加載Driver類,然后自動在DriverManager中注冊的方式有兩種:
(1)調(diào)用方法Class.forName
這將顯式地加載驅(qū)動程序類。由于這與外部設(shè)置無關(guān),因此推薦使用這種加載驅(qū)動程序的方法。以下代碼加載類acme.db.Driver:Class.forName(“acme.db.Driver”)。
如果將acme.db.Driver編寫為加載時創(chuàng)建實(shí)例,并調(diào)用以該實(shí)例為參數(shù)的DriverManager.registerDriver(本該如此),則它在DriverManager的驅(qū)動程序列表中,并可用于創(chuàng)建連接。
(2)將驅(qū)動程序添加到Java.lang.System的屬性jdbc.drivers中
這是一個由DriverManager類加載的驅(qū)動程序類名的列表,由冒號分隔:初始化DriverManager類時,它搜索系統(tǒng)屬性jdbc.drivers,如果用戶已輸入了一個或多個驅(qū)動程序,則DriverManager類將試圖加載它們。以下代碼說明程序員如何在~/.hotJava/properties中輸入三個驅(qū)動程序類(啟動時,HotJava將把它加載到系統(tǒng)屬性列表中):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
對DriverManager方法的第一次調(diào)用將自動加載這些驅(qū)動程序類。注意:加載驅(qū)動程序的第二種方法需要持久的預(yù)設(shè)環(huán)境。如果對這一點(diǎn)不能保證,則調(diào)用方法Class.forName顯式地加載每個驅(qū)動程序就顯得更為安全。這也是引入特定驅(qū)動程序的方法,因?yàn)橐坏〥riverManager類被初始化,它將不再檢查jdbc.drivers屬性列表。
在以上兩種情況中,新加載的Driver類都要通過調(diào)用DriverManager.registerDriver類進(jìn)行自我注冊。如上所述,加載類時將自動執(zhí)行這一過程。
由于安全方面的原因,JDBC管理層將跟蹤哪個類加載器提供哪個驅(qū)動程序。這樣,當(dāng)DriverManager類打開連接時,它僅使用本地文件系統(tǒng)或與發(fā)出連接請求的代碼相同的類加載器提供的驅(qū)動程序。
2.建立連接
加載Driver類并在DriverManager類中注冊后,它們即可用來與數(shù)據(jù)庫建立連接。當(dāng)調(diào)用DriverManager.getConnection方法發(fā)出連接請求時,DriverManager將檢查每個驅(qū)動程序,查看它是否可以建立連接。
有時可能有多個JDBC驅(qū)動程序可以與給定的URL連接。例如,與給定遠(yuǎn)程數(shù)據(jù)庫連接時,可以使用JDBC-ODBC橋驅(qū)動程序、JDBC到通用網(wǎng)絡(luò)協(xié)議驅(qū)動程序或數(shù)據(jù)庫廠商提供的驅(qū)動程序。在這種情況下測試驅(qū)動程序的順序至關(guān)重要,因?yàn)镈riverManager將使用它所找到的第一個可以成功連接到給定URL的驅(qū)動程序。
首先DriverManager試圖按注冊的順序使用每個驅(qū)動程序(jdbc.drivers中列出的驅(qū)動程序總是先注冊)。它將跳過代碼不可信任的驅(qū)動程序,除非加載它們的源與試圖打開連接的代碼的源相同。它通過輪流在每個驅(qū)動程序上調(diào)用方法Driver.connect,并向它們傳遞用戶開始傳遞給方法DriverManager.getConnection的URL來對驅(qū)動程序進(jìn)行測試,然后連接第一個認(rèn)出該URL的驅(qū)動程序。這種方法初看起來效率不高,但由于不可能同時加載數(shù)十個驅(qū)動程序,因此每次連接實(shí)際只需幾個過程調(diào)用和字符串比較。
以下代碼是通常情況下用驅(qū)動程序(例如JDBC-ODBC橋驅(qū)動程序)建立連接所需所有步驟的示例:
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);//加載驅(qū)動程序 String url = “jdbc:odbc:fred”;
DriverManager.getConnection(url,“userID”,“passwd”); 如何利用JDBC發(fā)送SQL語句?
Statement對象用于將SQL語句發(fā)送到數(shù)據(jù)庫中。實(shí)際上有三種Statement對象,它們都作為在給定連接上執(zhí)行SQL語句的包容器:Statement、PreparedStatement(它從Statement繼承而來)和CallableStatement(它從PreparedStatement繼承而來)。它們都專用于發(fā)送特定類型的SQL語句:Statement對象用于執(zhí)行不帶參數(shù)的簡單SQL語句;PreparedStatement對象用于執(zhí)行帶或不帶IN參數(shù)的預(yù)編譯SQL語句;CallableStatement對象用于執(zhí)行對數(shù)據(jù)庫已存儲過程的調(diào)用。
Statement接口提供了執(zhí)行語句和獲取結(jié)果的基本方法;PreparedStatement接口添加了處理IN參數(shù)的方法;而CallableStatement添加了處理OUT參數(shù)的方法。
1.創(chuàng)建Statement對象
建立了到特定數(shù)據(jù)庫的連接之后,就可用該連接發(fā)送SQL語句。Statement對象用Connection的方法createStatement創(chuàng)建,如下列代碼段中所示:
Connection con = DriverManager.getConnection(url,“sunny”,“"); Statement stmt = con.createStatement();
為了執(zhí)行Statement對象,被發(fā)送到數(shù)據(jù)庫的SQL語句將被作為參數(shù)提供給Statement的方法:
ResultSet rs = stmt.executeQuery(”SELECT a,b,c FROM Table2“);
2.使用Statement對象執(zhí)行語句
Statement接口提供了三種執(zhí)行SQL語句的方法:executeQuery、executeUpdate和execute。使用哪一個方法由SQL語句所產(chǎn)生的內(nèi)容決定。
方法executeQuery用于產(chǎn)生單個結(jié)果集的語句,例如SELECT語句。方法executeUpdate用于執(zhí)行INSERT、UPDATE或DELETE語句以及SQL DDL(數(shù)據(jù)定義語言)語句,例如CREATE TABLE和DROP TABLE。INSERT、UPDATE或DELETE語句的效果是修改表中零行或多行中的一列或多列。executeUpdate的返回值是一個整數(shù),指示受影響的行數(shù)(即更新計(jì)數(shù))。對于CREATE TABLE或DROP TABLE等不操作行的語句,executeUpdate的返回值總為零。
執(zhí)行語句的所有方法都將關(guān)閉所調(diào)用的Statement對象的當(dāng)前打開結(jié)果集(如果存在)。這意味著在重新執(zhí)行Statement對象之前,需要完成對當(dāng)前ResultSet對象的處理。應(yīng)注意,繼承了Statement接口中所有方法的PreparedStatement接口都有自己的executeQuery、executeUpdate和execute方法。Statement對象本身不包含SQL語句,因而必須給Statement.execute方法提供SQL語句作為參數(shù)。PreparedStatement對象并不需要SQL語句作為參數(shù)提供給這些方法,因?yàn)樗鼈円呀?jīng)包含預(yù)編譯SQL語句。
CallableStatement對象繼承這些方法的PreparedStatement形式。對于這些方法的PreparedStatement或CallableStatement版本,使用查詢參數(shù)將拋出SQLException。
3.語句完成
當(dāng)連接處于自動提交模式時,其中所執(zhí)行的語句在完成時將自動提交或還原。語句在已執(zhí)行且所有結(jié)果返回時,即認(rèn)為已完成。對于返回一個結(jié)果集的executeQuery方法,在檢索完ResultSet對象的所有行時該語句完成。對于方法executeUpdate,當(dāng)它執(zhí)行時語句即完成。但在少數(shù)調(diào)用方法execute的情況中,在檢索所有結(jié)果集或它生成的更新計(jì)數(shù)之后語句才完成。
有些DBMS將已存儲過程中的每條語句視為獨(dú)立的語句;而另外一些則將整個過程視為一個復(fù)合語句。在啟用自動提交時,這種差別就變得非常重要,因?yàn)樗绊懯裁磿r候調(diào)用commit方法。在前一種情況中,每條語句單獨(dú)提交;在后一種情況中,所有語句同時提交。
4.關(guān)閉Statement對象
Statement對象將由Java垃圾收集程序自動關(guān)閉。而作為一種好的編程風(fēng)格,應(yīng)在不需要Statement對象時顯式地關(guān)閉它們。這將立即釋放DBMS資源,有助于避免潛在的內(nèi)存問題。
5.使用方法execute
execute方法應(yīng)該僅在語句能返回多個ResultSet對象、多個更新計(jì)數(shù)或ResultSet對象與更新計(jì)數(shù)的組合時使用。當(dāng)執(zhí)行某個已存儲過程或動態(tài)執(zhí)行未知SQL字符串(即應(yīng)用程序程序員在編譯時未知)時,有可能出現(xiàn)多個結(jié)果的情況,盡管這種情況很少見。例如,用戶可能執(zhí)行一個已存儲過程,并且該已存儲過程可執(zhí)行更新,然后執(zhí)行選擇,再進(jìn)行更新,再進(jìn)行選擇,等等。通常使用已存儲過程的人應(yīng)知道它所返回的內(nèi)容。
因?yàn)榉椒╡xecute處理非常規(guī)情況,所以獲取其結(jié)果需要一些特殊處理并不足為怪。例如,假定已知某個過程返回兩個結(jié)果集,則在使用方法execute執(zhí)行該過程后,必須調(diào)用方法getResultSet獲得第一個結(jié)果集,然后調(diào)用適當(dāng)?shù)膅etXXX方法獲取其中的值。要獲得第二個結(jié)果集,需要先調(diào)用getMoreResults方法,然后再調(diào)用getResultSet方法。如果已知某個過程返回兩個更新計(jì)數(shù),則首先調(diào)用方法getUpdateCount,然后調(diào)用getMoreResults,并再次調(diào)用getUpdateCount。
對于不知道返回內(nèi)容,則情況更為復(fù)雜。如果結(jié)果是ResultSet對象,則方法execute返回true;如果結(jié)果是Javaint,則返回false。如果返回int,則意味著結(jié)果是更新計(jì)數(shù)或執(zhí)行的語句是DL命令。在調(diào)用方法execute之后要做的第一件事情是調(diào)用getResultSet或getUpdateCount。調(diào)用方法getResultSet可以獲得兩個或多個ResultSet對象中第一個對象;或調(diào)用方法getUpdateCount可以獲得兩個或多個更新計(jì)數(shù)中第一個更新計(jì)數(shù)的內(nèi)容。
當(dāng)SQL語句的結(jié)果不是結(jié)果集時,則方法getResultSet將返回null。這可能意味著結(jié)果是一個更新計(jì)數(shù)或沒有其它結(jié)果。在這種情況下,判斷null真正含義的唯一方法是調(diào)用方法getUpdateCount,它將返回一個整數(shù)。這個整數(shù)為調(diào)用語句所影響的行數(shù);如果為-1則表示結(jié)果是結(jié)果集或沒有結(jié)果。如果方法getResultSet已返回null(表示結(jié)果不是ResultSet對象),則返回值-1表示沒有其它結(jié)果。也就是說,當(dāng)下列條件為真時表示沒有結(jié)果(或沒有其它結(jié)果):
((stmt.getResultSet()==null)&&(stmt.getUpdateCount()==-1))
如果已經(jīng)調(diào)用方法getResultSet并處理了它返回的ResultSet對象,則有必要調(diào)用方法getMoreResults以確定是否有其它結(jié)果集或更新計(jì)數(shù)。如果getMoreResults返回true,則需要再次調(diào)用getResultSet來檢索下一個結(jié)果集。如上所述,如果getResultSet返回null,則需要調(diào)用getUpdateCount來檢查null是表示結(jié)果為更新計(jì)數(shù)還是表示沒有其它結(jié)果。
當(dāng)getMoreResults返回false時,它表示該SQL語句返回一個更新計(jì)數(shù)或沒有其它結(jié)果。因此需要調(diào)用方法getUpdateCount來檢查它是哪一種情況。在這種情況下,當(dāng)下列條件為真時表示沒有其它結(jié)果:
通過JDBC連接oracle數(shù)據(jù)庫的十大技巧
(2008-08-27 22:42:57)轉(zhuǎn)載 標(biāo)簽:
雜談
Java數(shù)據(jù)庫連接(JDBC)API是一系列能夠讓Java編程人員訪問數(shù)據(jù)庫的接口,各個開發(fā)商的接口并不完全相同。在使用多年的Oracle公司的JDBC后,我積累了許多技巧,這些技巧能夠使我們更好地發(fā)揮系統(tǒng)的性能和實(shí)現(xiàn)更多的功能。
1、在客戶端軟件開發(fā)中使用Thin驅(qū)動程序
在開發(fā)Java軟件方面,Oracle的數(shù)據(jù)庫提供了四種類型的驅(qū)動程序,二種用于應(yīng)用軟件、applets、servlets等客戶端軟件,另外二種用于數(shù)據(jù)庫中的Java存儲過程等服務(wù)器端軟件。在客戶機(jī)端軟件的開發(fā)中,我們可以選擇OCI驅(qū)動程序或Thin驅(qū)動程序。OCI驅(qū)動程序利用Java本地化接口(JNI),通過Oracle客戶端軟件與數(shù)據(jù)庫進(jìn)行通訊。Thin驅(qū)動程序是純Java驅(qū)動程序,它直接與數(shù)據(jù)庫進(jìn)行通訊。為了獲得最高的性能,Oracle建議在客戶端軟件的開發(fā)中使用OCI驅(qū)動程序,這似乎是正確的。但我建議使用Thin驅(qū)動程序,因?yàn)橥ㄟ^多次測試發(fā)現(xiàn),在通常情況下,Thin驅(qū)動程序的性能都超過了OCI驅(qū)動程序。
2、關(guān)閉自動提交功能,提高系統(tǒng)性能
在第一次建立與數(shù)據(jù)庫的連接時,在缺省情況下,連接是在自動提交模式下的。為了獲得更好的性能,可以通過調(diào)用帶布爾值false參數(shù)的Connection類的setAutoCommit()方法關(guān)閉自動提交功能,如下所示:
conn.setAutoCommit(false);
值得注意的是,一旦關(guān)閉了自動提交功能,我們就需要通過調(diào)用Connection類的commit()和rollback()方法來人工的方式對事務(wù)進(jìn)行管理。
3、在動態(tài)SQL或有時間限制的命令中使用Statement對象
在執(zhí)行SQL命令時,我們有二種選擇:可以使用PreparedStatement對象,也可以使用Statement對象。無論多少次地使用同一個SQL命令,PreparedStatement都只對它解析和編譯一次。當(dāng)使用Statement對象時,每次執(zhí)行一個SQL命令時,都會對它進(jìn)行解析和編譯。這可能會使你認(rèn)為,使用PreparedStatement對象比使用Statement對象的速度更快。然而,我進(jìn)行的測試表明,在客戶端軟件中,情況并非如此。因此,在有時間限制的SQL操作中,除非成批地處理SQL命令,我們應(yīng)當(dāng)考慮使用Statement對象。
此外,使用Statement對象也使得編寫動態(tài)SQL命令更加簡單,因?yàn)槲覀兛梢詫⒆址B接在一起,建立一個有效的SQL命令。因此,我認(rèn)為,Statement對象可以使動態(tài)SQL命令的創(chuàng)建和執(zhí)行變得更加簡單。
4、利用helper函數(shù)對動態(tài)SQL命令進(jìn)行格式化
在創(chuàng)建使用Statement對象執(zhí)行的動態(tài)SQL命令時,我們需要處理一些格式化方面的問題。例如,如果我們想創(chuàng)建一個將名字O'Reilly插入表中的SQL命令,則必須使用二個相連的“''”號替換O'Reilly中的“'”號。完成這些工作的最好的方法是創(chuàng)建一個完成替換操作的helper方法,然后在連接字符串心服用公式表達(dá)一個SQL命令時,使用創(chuàng)建的helper方法。與此類似的是,我們可以讓helper方法接受一個Date型的值,然后讓它輸出基于Oracle的to_date()函數(shù)的字符串表達(dá)式。
5、利用PreparedStatement對象提高數(shù)據(jù)庫的總體效率
在使用PreparedStatement對象執(zhí)行SQL命令時,命令被數(shù)據(jù)庫進(jìn)行解析和編譯,然后被放到命令緩沖區(qū)。然后,每當(dāng)執(zhí)行同一個PreparedStatement對象時,它就會被再解析一次,但不會被再次編譯。在緩沖區(qū)中可以發(fā)現(xiàn)預(yù)編譯的命令,并且可以重新使用。在有大量用戶的企業(yè)級應(yīng)用軟件中,經(jīng)常會重復(fù)執(zhí)行相同的SQL命令,使用PreparedStatement對象帶來的編譯次數(shù)的減少能夠提高數(shù)據(jù)庫的總體性能。如果不是在客戶端創(chuàng)建、預(yù)備、執(zhí)行PreparedStatement任務(wù)需要的時間長于Statement任務(wù),我會建議在除動態(tài)SQL命令之外的所有情況下使用PreparedStatement對象。
6、在成批處理重復(fù)的插入或更新操作中使用PreparedStatement對象
如果成批地處理插入和更新操作,就能夠顯著地減少它們所需要的時間。Oracle提供的Statement和 CallableStatement并不真正地支持批處理,只有PreparedStatement對象才真正地支持批處理。我們可以使用addBatch()和executeBatch()方法選擇標(biāo)準(zhǔn)的JDBC批處理,或者通過利用PreparedStatement對象的setExecuteBatch()方法和標(biāo)準(zhǔn)的executeUpdate()方法選擇速度更快的Oracle專有的方法。要使用Oracle專有的批處理機(jī)制,可以以如下所示的方式調(diào)用setExecuteBatch():
PreparedStatement pstmt3D null;try {((OraclePreparedStatement)pstmt).setExecuteBatch(30);...pstmt.executeUpdate();}
調(diào)用setExecuteBatch()時指定的值是一個上限,當(dāng)達(dá)到該值時,就會自動地引發(fā)SQL命令執(zhí)行,標(biāo)準(zhǔn)的executeUpdate()方法就會被作為批處理送到數(shù)據(jù)庫中。我們可以通過調(diào)用PreparedStatement類的sendBatch()方法隨時傳輸批處理任務(wù)。
7、使用Oracle locator方法插入、更新大對象(LOB)
Oracle的PreparedStatement類不完全支持BLOB和CLOB等大對象的處理,尤其是Thin驅(qū)動程序不支持利用PreparedStatement對象的setObject()和setBinaryStream()方法設(shè)置BLOB的值,也不支持利用setCharacterStream()方法設(shè)置CLOB的值。只有l(wèi)ocator本身中的方法才能夠從數(shù)據(jù)庫中獲取LOB類型的值。可以使用PreparedStatement對象插入或更新LOB,但需要使用locator才能獲取LOB的值。由于存在這二個問題,因此,我建議使用locator的方法來插入、更新或獲取LOB的值。
8、使用SQL92語法調(diào)用存儲過程
在調(diào)用存儲過程時,我們可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并沒有什么實(shí)際的好處,而且會給以后維護(hù)你的應(yīng)用程序的開發(fā)人員帶來麻煩,因此,我建議在調(diào)用存儲過程時使用SQL92。
9、使用Object SQL將對象模式轉(zhuǎn)移到數(shù)據(jù)庫中
既然可以將Oracle的數(shù)據(jù)庫作為一種面向?qū)ο蟮臄?shù)據(jù)庫來使用,就可以考慮將應(yīng)用程序中的面向?qū)ο竽J睫D(zhuǎn)到數(shù)據(jù)庫中。目前的方法是創(chuàng)建Java bean作為偽裝的數(shù)據(jù)庫對象,將它們的屬性映射到關(guān)系表中,然后在這些bean中添加方法。盡管這樣作在Java中沒有什么問題,但由于操作都是在數(shù)據(jù)庫之外進(jìn)行的,因此其他訪問數(shù)據(jù)庫的應(yīng)用軟件無法利用對象模式。如果利用Oracle的面向?qū)ο蟮募夹g(shù),可以通過創(chuàng)建一個新的數(shù)據(jù)庫對象類型在數(shù)據(jù)庫中模仿其數(shù)據(jù)和操作,然后使用JPublisher等工具生成自己的Java bean類。如果使用這種方式,不但Java應(yīng)用程序可以使用應(yīng)用軟件的對象模式,其他需要共享你的應(yīng)用中的數(shù)據(jù)和操作的應(yīng)用軟件也可以使用應(yīng)用軟件中的對象模式。
10、利用SQL完成數(shù)據(jù)庫內(nèi)的操作
我要向大家介紹的最重要的經(jīng)驗(yàn)是充分利用SQL的面向集合的方法來解決數(shù)據(jù)庫處理需求,而不是使用Java等過程化的編程語言。
如果編程人員要在一個表中查找許多行,結(jié)果中的每個行都會查找其他表中的數(shù)據(jù),最后,編程人員創(chuàng)建了獨(dú)立的UPDATE命令來成批地更新第一個表中的數(shù)據(jù)。與此類似的任務(wù)可以通過在set子句中使用多列子查詢而在一個UPDATE命令中完成。當(dāng)能夠在單一的SQL命令中完成任務(wù),何必要讓數(shù)據(jù)在網(wǎng)上流來流去的?我建議用戶認(rèn)真學(xué)習(xí)如何最大限度地發(fā)揮SQL的功能
1、這是oracle的規(guī)定,不能對執(zhí)行觸發(fā)器的表進(jìn)行操作。可以對new.xxx進(jìn)行操作啊,對于oracle行級觸發(fā)器(for each row),不能對本表做任何操作,包括讀取
原則:
在before insert觸發(fā)器中,可以實(shí)現(xiàn)對本表的訪問; 在after insert觸發(fā)器中,不能實(shí)現(xiàn)對本表的訪問;
在before/after update/delete觸發(fā)器中,都不能實(shí)現(xiàn)對本表的訪問
其實(shí)原因很簡單,就是為了防止臟讀
2、寫oracle行級觸發(fā)器時,不能操作本表,報”表 *** 發(fā)生了變化,觸發(fā)器/函數(shù)不能讀"的錯誤的解決辦法
原因已經(jīng)很明顯了就是行級的觸發(fā)器代碼中不能操作該表,包括select,是挺郁悶的
當(dāng)然解決方法就是要根據(jù)原因了,正因?yàn)橄薅诵屑売|發(fā)器的操作,只能選擇表級的觸發(fā)器了,但是在表級的觸發(fā)器又不能獲得:new和:old的值,那就只能采取兩種觸發(fā)器并用的方法了,并且還要包或者臨時表加以輔助.首先在行級觸發(fā)器中將所需的,:new或者:old的值,寫到包或者臨時表中
然后在表級觸發(fā)器中處理包或者臨時表中已經(jīng)寫入的數(shù)據(jù),操作成功后可以按照需求再刪除臨時表的數(shù)據(jù).3、ORACLE 觸發(fā)器
ORACLE產(chǎn)生數(shù)據(jù)庫觸發(fā)器的語法為:
create [or replace] trigger 觸發(fā)器名 觸發(fā)時間 觸發(fā)事件
on 表名
[for each row]
pl/sql 語句
其中:
觸發(fā)器名:觸發(fā)器對象的名稱。由于觸發(fā)器是數(shù)據(jù)庫自動執(zhí)行 的,因此該名稱只是一個名稱,沒有實(shí)質(zhì)的用途。
觸發(fā)時間:指明觸發(fā)器何時執(zhí)行,該值可取:
before---表示在數(shù)據(jù)庫動作之前觸發(fā)器執(zhí)行;
after---表示在數(shù)據(jù)庫動作之后出發(fā)器執(zhí)行。
觸發(fā)事件:指明哪些數(shù)據(jù)庫動作會觸發(fā)此觸發(fā)器:
insert:數(shù)據(jù)庫插入會觸發(fā)此觸發(fā)器;
update:數(shù)據(jù)庫修改會觸發(fā)此觸發(fā)器;
delete:數(shù)據(jù)庫刪除會觸發(fā)此觸發(fā)器。
表 名:數(shù)據(jù)庫觸發(fā)器所在的表。
for each row:對表的每一行觸發(fā)器執(zhí)行一次。如果沒有這一
選項(xiàng),則只對整個表執(zhí)行一次。
舉例:下面的觸發(fā)器在更新表auths之前觸發(fā),目的是不允許在
周末修改表:
create trigger auth_secure
before insert or update or delete //對整表更新前觸發(fā)
on auths
begin
if(to_char(sysdate,'DY')='SUN'
RAISE_APPLICATION_ERROR(-20600,'不能在周末修改表auths');
end if;end 分析函數(shù)
(2008-09-04 00:11:55)轉(zhuǎn)載 標(biāo)簽:
雜談 分析函數(shù)
1、ROW_NUMBER(為有序組中的每一行返回一個唯一的排序值,序號由order by子句指定,從1開始。)例80:對所有員工的工資進(jìn)行排名,即使工資相同,其排名也不能相同。select ename,job,deptno,sal, row_number()over(order by sal desc)as sal_rank from emp;例81:對員工的工資按部門進(jìn)行排名,即使工資相同,排名也不同。select ename,job,deptno,sal, row_number()over(partition by deptno order by sal desc)as sal_rank from emp;
2、RANK(計(jì)算一個值在一組值中的排位,排位是以1開頭的連續(xù)整數(shù)。具有相等值的行排位相同,序數(shù)隨后跳躍相應(yīng)的數(shù)值,即如果兩行的序數(shù)為1,則沒有序數(shù)2,下一行的序號為3)例82:如何根據(jù)員工的工資和俑金對員工在每個部門中進(jìn)行排位。相同的工資排位相同,并且排位不連續(xù)。
select ename,sal,comm,deptno, rank()over(partition by deptno order by sal desc)as RANK from emp;
3、DENSE_RANK(計(jì)算一個行在一組有序行中的排位,排位是以1開頭的連續(xù)整數(shù)。具有相等值的行排位相同,并且排位是連續(xù)的。)例83:分別計(jì)算每個員工的工資在部門中的排位。相等的工資排位相同。select d.dname,e.ename,e.sal, dense_rank()over(partition by e.deptno order by e.sal desc)as denserank from emp e,dept d where e.deptno=d.deptno;