C3P0連接池自誕生以來在Java Web領域反響甚好,業已成為hibenate框架推薦的連接池。誰知人紅是非多,C3P0在大型應用場合中暴露了越來越多的局限性,包括但不限於下列幾點:
1、C3P0管理池內連接時沒有采取LRU排隊規則(最久未使用算法),意味着C3P0未能將數據庫性能調到最優。
2、在處理大批量數據的時候,C3P0對耗時操作過於容忍,致使容易出現線程死鎖的狀況。
3、C3P0不支持監控功能,外界難以實時跟蹤連接池的運行情況,不利於按需分配和調度系統資源。
就上面幾點問題的看法因人而異,對老外來說,他們國家人口不多,一百年都難得遇上這種嚴苛的條件,考慮超大規模的數據處理純屬杞人憂天。但對國人來說,數據庫里的業務記錄動輒以千萬計,億級以上的海量數據也不罕見,此時一點一滴的性能差距匯總起來就可能出大問題。然而C3P0源自國外,人家才懶得搭理這茬事;再說,此等關鍵要害豈能由外人扼住咽喉?當然要自己掌握核心技術才讓人放心,於是阿里巴巴公司推出了國產的開源連接池Druid,該連接池立足於本國國情,在諸多方面加以調整和優化,比C3P0更適用於國內的業務系統。
Druid的用法近似於C3P0,它擁有自己的連接池工具DruidDataSource,該工具的常見方法列舉如下:
setDriverClassName:設置連接池的數據庫驅動。
setUrl:設置數據庫的連接地址。
setUsername:設置數據庫的用戶名。
setPassword:設置數據庫的密碼。
setInitialSize:設置連接池的初始大小。
setMinIdle:設置連接池大小的下限。
setMaxActive:設置連接池大小的上限。
setRemoveAbandoned:設置是否拋棄已超時的連接。
setRemoveAbandonedTimeout:設置超時的時間間隔,單位秒。如果某連接超過該時間仍未釋放,則會被自動回收。
setMaxWait:設置獲取連接所允許的等待時間,單位毫秒。超過該時間將不再獲取連接。
setTimeBetweenEvictionRunsMillis:設置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位毫秒。
setValidationQuery:設置檢測連接是否有效的SQL語句。
setTestWhileIdle:當空閑時是否需要進行有效性測試。建議設置為true,保證安全性。
setTestOnBorrow:設置為true,表示申請連接時將調用validationQuery方法來檢測連接是否有效。
getDbType:獲取數據庫的名稱。
getActiveCount:獲取活躍連接的數量。
getConnectCount:獲取已連上連接的數量。
getPoolingCount:獲取空閑連接的數量。
getConnection:從連接池中獲取一個連接,連接類型為DruidPooledConnection。
close:關閉連接池。
至於Druid的編碼過程,則依然分成兩個步驟:初始化連接池、從連接池中取出一個連接處理,分別說明如下:
1、初始化連接池
該步驟首先創建Druid連接池的對象,再依次調用相關方法設置詳細的參數信息,包括數據庫驅動、連接地址、用戶名、密碼,以及與連接池有關的規格參數。下面是初始化Druid連接池的代碼例子:
private static DruidDataSource dataSource; // 聲明Druid連接池的對象 // 初始化連接池 private static void initDataSource() { dataSource = new DruidDataSource(); // 創建Druid連接池 dataSource.setDriverClassName(driver_class); // 設置連接池的數據庫驅動 dataSource.setUrl(dbUrl); // 設置數據庫的連接地址 dataSource.setUsername(dbUserName); // 設置數據庫的用戶名 dataSource.setPassword(dbPassword); // 設置數據庫的密碼 dataSource.setInitialSize(1); // 設置連接池的初始大小 dataSource.setMinIdle(1); // 設置連接池大小的下限 dataSource.setMaxActive(20); // 設置連接池大小的上限 }
2、從連接池中取出一個連接處理
注意該步驟的getConnection方法拿到的是DruidPooledConnection類型的連接對象,再根據該連接創建對應的報告,並開展后續的數據庫操作。為方便觀察連接池的運行情況,可在其中添加幾個連接池的檢測方法,例如getActiveCount、getConnectCount、getPoolingCount等等。修改后的數據庫操作代碼示例如下:
// 顯示性別分組 private static void showRecordGroupBySex() { String sql = "select sex,count(1) count from teacher group by sex order by sex asc"; // 從連接池中獲取連接、創建連接的報告、命令報告執行指定的SQL語句 try (DruidPooledConnection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { // 循環遍歷結果集里面的所有記錄 int sex = rs.getInt("sex"); // 獲取指定字段的整型值 int count = rs.getInt("count"); // 獲取指定字段的整型值 String desc = String.format("%s老師有%d位;", sex==0 ? "男" : "女", count); System.out.print(desc); } System.out.println("\ngetActiveCount="+dataSource.getActiveCount()); // 獲取活躍連接的數量 System.out.println("getConnectCount="+dataSource.getConnectCount()); // 獲取已連上連接的數量 System.out.println("getPoolingCount="+dataSource.getPoolingCount()); // 獲取空閑連接的數量 } catch (SQLException e) { e.printStackTrace(); } }
然后由外部反復調用以上的showRecordGroupBySex方法,假設准備測試連續的三次數據庫操作,則外部的調用代碼如下所示:
for (int i=0; i<3; i++) { // 多次操作數據庫 showRecordGroupBySex(); // 顯示性別分組 }
運行包含上面代碼的測試程序,觀察到下面的輸出日志:
男老師有2位;女老師有3位; getActiveCount=1 getConnectCount=1 getPoolingCount=0 男老師有2位;女老師有3位; getActiveCount=1 getConnectCount=2 getPoolingCount=0 男老師有2位;女老師有3位; getActiveCount=1 getConnectCount=3 getPoolingCount=0
由日志可見,getActiveCount方法返回了當前正在使用的連接數量,getConnectCount方法返回了曾經連上與已經連上的連接總數,getPoolingCount返回了連接池中剩余的連接數量。
更多Java技術文章參見《Java開發筆記(序)章節目錄》