前些天在調試公司系統的時候發現這樣的一個問題:mysql數據庫服務停止一段時間后再次重啟后嗎,tomcat服務無法請求數據庫服務,調試了半天對這個問題進行定位解決,期間也搞了很多有關mysql數據庫的知識,包括數據庫連接池的問題,以前沒有遇到問題的時候只知道數據庫連接池這個概念和如何配置,但是當遇到問題的時候就要去看怎么實現了,比如很簡單的默認的數據庫連接池的個數是多少呢,我相信沒有看過源代碼的是不知道的,答案是8.下面就針對最近學習的org.apache.commons.dbcp.BasicDataSource這個數據源的連接池做一個分享吧。
分享之前有關數據庫連接池的一些概念性的問題就不解釋,可以參考http://www.cnblogs.com/duanxiaojun/p/5413502.html。
下面我們以一個完成的數據庫操作來分析詳細的dbcp數據源連接池實現的原理:
1 package cn.com.chnsys.dbcpDataSource; 2 3 import javax.sql.DataSource; 4 import java.sql.Connection; 5 import java.sql.Statement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 import org.apache.commons.dbcp.BasicDataSource; 9 /** 10 * 11 * <p>dbcp數據源連接池分析</p> 12 * 13 * 類說明 14 * 15 * @author duanxj 16 * @version 1.0 17 */ 18 public class BasicDataSourceExample { 19 20 public static void main(String[] args) { 21 //設置數據源基本配置項 22 DataSource dataSource = setupDataSource(args[0]); 23 //創建連接 24 Connection conn = null; 25 Statement stmt = null; 26 ResultSet rset = null; 27 try { 28 //創建連接對象 29 conn = dataSource.getConnection(); 30 //創建Statement 對象,這里我們使用Statement prepareStatement也是一樣的 31 stmt = conn.createStatement(); 32 //創建結果返回集 33 rset = stmt.executeQuery(args[1]); 34 //得到查詢影響記錄數 35 int numcols = rset.getMetaData().getColumnCount(); 36 while(rset.next()) { 37 for(int i=1;i<=numcols;i++) { 38 System.out.print("\t" + rset.getString(i)); 39 } 40 System.out.println(""); 41 } 42 } catch(SQLException e) { 43 e.printStackTrace(); 44 } finally { 45 //關閉資源 46 try { if (rset != null) rset.close(); } catch(Exception e) { } 47 try { if (stmt != null) stmt.close(); } catch(Exception e) { } 48 try { if (conn != null) conn.close(); } catch(Exception e) { } 49 } 50 } 51 52 /** 53 * 創建數據源,並設置數據源基本配置項 54 * @param connectURI 55 * @return 56 */ 57 public static DataSource setupDataSource(String connectURI) { 58 BasicDataSource ds = new BasicDataSource(); 59 ds.setDriverClassName("oracle.jdbc.driver.OracleDriver"); //設置驅動 60 ds.setUsername("root"); //設置用戶名 61 ds.setPassword("root"); //設置密碼 62 ds.setUrl(connectURI);//設置連接url 63 return ds; 64 } 65 /** 66 * 打印創建的數據源的配置項 67 * @param ds 68 */ 69 public static void printDataSourceStats(DataSource ds) { 70 BasicDataSource bds = (BasicDataSource) ds; 71 System.out.println("NumActive: " + bds.getNumActive()); 72 System.out.println("NumIdle: " + bds.getNumIdle()); 73 } 74 /** 75 * 關閉銷毀 76 * @param ds 77 * @throws SQLException 78 */ 79 public static void shutdownDataSource(DataSource ds) throws SQLException { 80 BasicDataSource bds = (BasicDataSource) ds; 81 bds.close(); 82 } 83 }
上面的代碼大家應該能看懂,我們只說其中比較關鍵的。首先喲啊創建一個dataSource,並對這個dataSource設置一些必須的配置項(數據庫驅動,URL,用戶名,密碼)等,然后關鍵的操作是在dataSource.getConnection();處,我們查看dbcp源碼的實現就可以知道,datasource的配置只是配置一些有關的配置信息,真正的創建連接池pool的操作是在用戶第一次去獲取connection的時候創建的,下面是源碼getConnection()的實現:
public Connection getConnection() throws SQLException { return createDataSource().getConnection(); } protected synchronized DataSource createDataSource() throws SQLException { if (this.closed) { throw new SQLException("Data source is closed"); } if (this.dataSource != null) { return this.dataSource; } ConnectionFactory driverConnectionFactory = createConnectionFactory(); createConnectionPool(); GenericKeyedObjectPoolFactory statementPoolFactory = null; if (isPoolPreparedStatements()) { statementPoolFactory = new GenericKeyedObjectPoolFactory(null, -1, (byte)0, 0L, 1, this.maxOpenPreparedStatements); } createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, this.abandonedConfig); createDataSourceInstance(); try { for (int i = 0; i < this.initialSize; i++) this.connectionPool.addObject(); } catch (Exception e) { throw new SQLNestedException("Error preloading the connection pool", e); } return this.dataSource; }
上面的代碼我做如下的分析:
首先我們看到這個createDataSource()方法是用synchronized修飾的我們知道這是線程安全的。
1 首先是在getConnection()方法中先是去創建一個createDataSource()。在createDataSource(),的方法中先判斷該數據源是否關閉,如果關閉直接拋出異常,如果這個數據源已經創建成功則直接返回,否則去創建這個數據源,
2 創建數據源之前先創建數據庫的連接工廠createConnectionFactory();在這個方法中主要是通過反射CLASS.FORNAME()創建一個數據庫連接對象,其中涉及到的參數主要是driverClassName,url,validationQuery,username,password(對這些鏈接數據庫的配置應該不陌生,這些參數的作用就是創建一個連接工廠)。
ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, this.url, this.connectionProperties);並返回創建成功的連接工廠示例在下面創建連接池createConnectionPool使用。
3 下面是真正的創建連接池的核心部分createConnectionPool();我們先看一下這個方法的具體實現,這里的前提是我們已經創建成功一個與數據庫的連接對象,
1 protected void createConnectionPool() 2 { 3 GenericObjectPool gop; 4 GenericObjectPool gop; 5 if ((this.abandonedConfig != null) && (this.abandonedConfig.getRemoveAbandoned())) { 6 gop = new AbandonedObjectPool(null, this.abandonedConfig); 7 } 8 else { 9 gop = new GenericObjectPool(); 10 } 11 gop.setMaxActive(this.maxActive); 12 gop.setMaxIdle(this.maxIdle); 13 gop.setMinIdle(this.minIdle); 14 gop.setMaxWait(this.maxWait); 15 gop.setTestOnBorrow(this.testOnBorrow); 16 gop.setTestOnReturn(this.testOnReturn); 17 gop.setTimeBetweenEvictionRunsMillis(this.timeBetweenEvictionRunsMillis); 18 gop.setNumTestsPerEvictionRun(this.numTestsPerEvictionRun); 19 gop.setMinEvictableIdleTimeMillis(this.minEvictableIdleTimeMillis); 20 gop.setTestWhileIdle(this.testWhileIdle); 21 this.connectionPool = gop; 22 }
在源碼中我們可以看到使用了GenericObjectPool對象,這個是使用commons-pool的GenericObjectPool 實現的,創建一個GenericObjectPool對象並設置連接池的配置參數信息,(這里知道我們再配置文件中配置的選項是如何在源碼中起作用的了吧~~)。然后返回這個GenericObjectPool(這個commons-pool的GenericObjectPool的源碼后續我們再分析,大家只要知道這里是使用的commons-pool的GenericObjectPool創建了一個池 )。
3.1 創建成功 this.connectionPool后
就是創建GenericKeyedObjectPoolFactory(暫時不知道這個是個什么東東?????)
// 設置連接池工廠 createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);
// 建立數據庫連接池實例 createDataSourceInstance();
然后是根據我們配置文件中配置的初始的數據庫連接池的大小去設置連接池的初始個數
// 根據配置,初始化建立一些數據庫連接 =
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
這樣一個完成的數據源dataSource和連接池就創建完成了。