基于J2EE平臺(tái)的應(yīng)用開發(fā)中,大多數(shù)的應(yīng)用都需要跟數(shù)據(jù)庫(kù)打交道。而自從接觸JDBC起,我們便不止一次地被告之:數(shù)據(jù)庫(kù)資源是十分寶貴的系統(tǒng)資源,一定要謹(jǐn)慎使用。但令人遺憾的是,在筆者見過(guò)的大部分跟數(shù)據(jù)庫(kù)相關(guān)的應(yīng)用開發(fā)中,針對(duì)數(shù)據(jù)庫(kù)資源的使用總是充斥著這樣或者那樣的問(wèn)題。在本文中,筆者對(duì)一些常見的錯(cuò)誤或者不當(dāng)?shù)氖褂脭?shù)據(jù)庫(kù)資源的案例進(jìn)行介紹與分析,幫助讀者避免某些錯(cuò)誤的發(fā)生。
未正確關(guān)閉數(shù)據(jù)庫(kù)連接
自增長(zhǎng)整數(shù)型字段賦值表
申請(qǐng)了數(shù)據(jù)庫(kù)連接,卻沒有及時(shí)關(guān)閉,這是最常見的數(shù)據(jù)庫(kù)連接使用方面的錯(cuò)誤。犯這種錯(cuò)誤的原因很多,以下是常見的一種比較低級(jí)的錯(cuò)誤:
public void foo() {
Connection conn=
getConnection();
Statement stmt = null;
try {
conn=getConnection();
stmt=conn.createStatement();
} catch(Exception e) {
} finally {
close(stmt, conn);
}
}
在上述案例中的第2行代碼中,作者申請(qǐng)了一個(gè)Connection,但在第6行代碼中,又申請(qǐng)了一個(gè)新的,并且丟失了第一次申請(qǐng)的Connection的引用。至此,當(dāng)程序每調(diào)一次Foo方法,將導(dǎo)致申請(qǐng)一個(gè)新的Connection而沒有釋放它。因此,當(dāng)數(shù)據(jù)庫(kù)達(dá)到最大連接數(shù)時(shí),將導(dǎo)致整個(gè)應(yīng)用的運(yùn)行失敗。
避免這種錯(cuò)誤的方法有很多,譬如,可采用類似于FindBugs的代碼分析工具對(duì)應(yīng)用的源碼進(jìn)行分析,找出可能產(chǎn)生錯(cuò)誤的代碼。
此外,在應(yīng)用中,我們要頻繁地對(duì)申請(qǐng)的數(shù)據(jù)庫(kù)連接進(jìn)行關(guān)閉與釋放。此時(shí),建議封裝成某些工具類使用,并且要盡可能安全地關(guān)閉數(shù)據(jù)庫(kù)連接。
任意申請(qǐng)數(shù)據(jù)庫(kù)連接
不考慮事務(wù)上下文,任意申請(qǐng)數(shù)據(jù)庫(kù)連接資源也是常見的不當(dāng)用法。但這種問(wèn)題往往是難以克服的,根源在于Java是一種面向?qū)ο蟮恼Z(yǔ)言,而數(shù)據(jù)庫(kù)的事務(wù)卻是一種批量化的操作過(guò)程。我們以常見的序列號(hào)的實(shí)現(xiàn)方案為例:在某些應(yīng)用場(chǎng)景中,我們需要一種自增長(zhǎng)的整數(shù)型字段。但由于不同的數(shù)據(jù)庫(kù)有不同的實(shí)現(xiàn),所以,為達(dá)到各個(gè)數(shù)據(jù)庫(kù)兼容的目的,我們常用的解決方案是,新建一張T_SEQUENCE表,它可能包含的字段有:NAME varchar(100), CURRENT_VAL number(10);其中,NAME存放序列的名稱,而CURRENT_VAL存放序列的當(dāng)前值。假設(shè)某一業(yè)務(wù)對(duì)象Customer需要新增一筆記錄時(shí),為獲得不重復(fù)且自增長(zhǎng)的Customer ID,需要將T_SEQUENCE表中與該業(yè)務(wù)表對(duì)應(yīng)的序列號(hào)加1并更新,然后將更新后的值作為Customer的ID。我們以面向?qū)ο蟮?種方法來(lái)實(shí)現(xiàn):
public class Customer {
/更新序列號(hào)使其加1/
public void sequencePlus(){
Connection conn=null;
Statement stmt =null;
……//將T_SEQUENCE的序列號(hào)當(dāng)前值加1;
}
/獲取當(dāng)前序列號(hào)/
public int getSequenceCurrentVal(){
Connection conn=null;
Statement stmt=null;
ResultSet rset =null;
……// 獲取當(dāng)前的序列號(hào)值;
}
/新增一條Customer記錄,自動(dòng)根據(jù)序列號(hào)生成主鍵/
public void addCustomer(String name) {
Connection conn=null;
PreparedStatement stmt = null;
ResultSet rset=null;
sequencePlus();// 序列號(hào)加1;
int id = getSequenceCurrentVal(); // 得到當(dāng)前序列號(hào);
…….// 將最新序列號(hào)作為新的T_Customer記錄的主鍵插入;
}
}
針對(duì)這種應(yīng)用場(chǎng)景,我們首先需要認(rèn)識(shí)到:上述3個(gè)方法應(yīng)該屬于同一個(gè)數(shù)據(jù)庫(kù)事務(wù)。否則,在并發(fā)情況下,將出現(xiàn)由于主鍵重復(fù)而導(dǎo)致數(shù)據(jù)插入失敗的情況。但同時(shí),我們也需要看到:即便上述3個(gè)方法的執(zhí)行位于同一個(gè)事務(wù)中,但3個(gè)方法使用的是不同的數(shù)據(jù)庫(kù)連接,雖然在sequencePlus方法中將T_SEQUENCE表中的數(shù)據(jù)加1 ,但在事務(wù)并未提交的情況下,由于Connection隔離級(jí)別的原因,在getSequenceCurrentVal方法中,是看不到sequencePlus方法中更新以后的數(shù)據(jù)的。這樣,也將導(dǎo)致數(shù)據(jù)插入失敗,因?yàn)橹麈I勢(shì)必跟舊有ID值重復(fù)。
因此,傳統(tǒng)編程方法為克服上述問(wèn)題,只有在上述的方法中使用同一個(gè)Connection,才能夠保證業(yè)務(wù)數(shù)據(jù)的正確。
更多信息請(qǐng)查看IT技術(shù)專欄