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