0、DBCP簡介java
DBCP(DataBase connection pool)數據庫鏈接池是 apache 上的一個Java鏈接池項目。DBCP經過鏈接池預先同數據庫創建一些鏈接放在內存中(即鏈接池中),應用程序須要創建數據庫鏈接時直接到從接池中申請一個鏈接使用,用完後由鏈接池回收該鏈接,從而達到鏈接複用,減小資源消耗的目的。mysql
一、DBCP所依賴的jar包(如下例子基於以下jar包版本)sql
commons-dbcp2-2.1.1.jar commons-logging-1.2.jar commons-pool2-2.4.2.jar數據庫
二、DBCP使用示例apache
下圖是在Eclipse中建立的Java工程,使用了DBCP相關的jar包,mysql的jdbc驅動jar包,junit4 。測試
並在src同級目錄下建立了config目錄,用於存放DBCP的配置文件。url
【注】類DBCPUtil.java在下面的例子中未用到。spa
1) DBCP配置文件dbcp.properties3d
########DBCP配置文件##########
#驅動名
driverClassName=com.mysql.jdbc.Driver
#url
url=jdbc:mysql://127.0.0.1:3306/mydb
#用戶名
username=sa
#密碼
password=123456
#初試鏈接數
initialSize=30
#最大活躍數
maxTotal=30
#最大idle數
maxIdle=10
#最小idle數
minIdle=5
#最長等待時間(毫秒)
maxWaitMillis=1000
#程序中的鏈接不使用後是否被鏈接池回收(該版本要使用removeAbandonedOnMaintenance和removeAbandonedOnBorrow)
#removeAbandoned=true
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
#鏈接在所指定的秒數內未使用纔會被刪除(秒)(爲配合測試程序才配置爲1秒)
removeAbandonedTimeout=1code
2) 建立初始化DBCP的類KCYDBCPUtil.java
1 package dbcp; 2 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 import java.sql.Connection; 6 import java.sql.SQLException; 7 import java.util.Properties; 8 9 import javax.sql.DataSource; 10 11 import org.apache.commons.dbcp2.BasicDataSourceFactory; 12 13 /** 14 * DBCP配置類 15 * @author SUN 16 */ 17 public class KCYDBCPUtil { 18 19 private static Properties properties = new Properties(); 20 private static DataSource dataSource; 21 //加載DBCP配置文件 22 static{ 23 try{ 24 FileInputStream is = new FileInputStream("config/dbcp.properties"); 25 properties.load(is); 26 }catch(IOException e){ 27 e.printStackTrace(); 28 } 29 30 try{ 31 dataSource = BasicDataSourceFactory.createDataSource(properties); 32 }catch(Exception e){ 33 e.printStackTrace(); 34 } 35 } 36 37 //從鏈接池中獲取一個鏈接 38 public static Connection getConnection(){ 39 Connection connection = null; 40 try{ 41 connection = dataSource.getConnection(); 42 }catch(SQLException e){ 43 e.printStackTrace(); 44 } 45 try { 46 connection.setAutoCommit(false); 47 } catch (SQLException e) { 48 e.printStackTrace(); 49 } 50 return connection; 51 } 52 53 public static void main(String[] args) { 54 getConnection(); 55 } 56 }
3) 建立使用JDBC獲取數據庫鏈接的類DBConn.java(用於和DBCP鏈接池對比)
1 package dbcp; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 6 public class DBConn { 7 private static Connection conn = null; 8 9 //獲取一個數據庫鏈接 10 public static Connection getConnection() { 11 try { 12 Class.forName("com.mysql.jdbc.Driver"); 13 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 14 String dbUrl = "jdbc:mysql://127.0.0.1:3306/mydb"; 15 conn = DriverManager.getConnection(dbUrl, "sa", "123456"); 16 // System.out.println("========數據庫鏈接成功========"); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 // System.out.println("========數據庫鏈接失敗========"); 20 return null; 21 } 22 return conn; 23 } 24 }
4) 建立測試類DBCPTest.java
測試類中採用3中方法將2000個數據插入數據庫同一張表中,每次插入數據以前,先清空表,並對結果進行了對比。
3中插入數據方法以下:
(1) 每次插入一條數據前,就建立一個鏈接,該條數據插入完成後,關閉該鏈接;
(2) 使用DBCP鏈接池,每次插入一條數據前,從DBCP鏈接池中獲取一條鏈接,該條數據插入完成後,該鏈接交由DBCP鏈接池管理;
(3) 在插入數據以前建立一條鏈接,2000個數據所有使用該鏈接,2000個數據插入完畢後,關閉該鏈接。
1 package dbcp; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import java.sql.Statement; 6 7 import org.junit.Test; 8 9 public class DBCPTest { 10 11 //測試,每寫一條數據前,就新建一個鏈接 12 @Test 13 public void testWriteDBByEveryConn() throws Exception{ 14 for(int i = 0; i < 2000; i++){ 15 writeDBByEveryConn(i); 16 } 17 System.out.println("DONE"); 18 19 } 20 21 //測試,使用鏈接池,每寫一條數據前,從鏈接池中獲取一個鏈接 22 @Test 23 public void testWriteDBByDBCP() throws Exception{ 24 for(int i = 0; i < 2000; i++){ 25 writeDBByDBCP(i); 26 } 27 System.out.println("DONE"); 28 } 29 30 //測試,只建一條鏈接,寫入全部數據 31 @Test 32 public void testWriteDBByOneConn() throws Exception{ 33 Connection conn = DBConn.getConnection(); 34 Statement stat = conn.createStatement(); 35 for(int i = 0; i < 2000; i++){ 36 writeDBByOneConn(i, stat); 37 } 38 conn.close(); 39 System.out.println("DONE"); 40 } 41 42 //不使用鏈接池寫數據庫,每寫一條數據建立一個鏈接 43 public void writeDBByEveryConn(int data){ 44 String sql = "insert into dbcp values (" + data + ")"; 45 Connection conn = DBConn.getConnection(); 46 try{ 47 Statement stat = conn.createStatement(); 48 stat.executeUpdate(sql); 49 }catch(Exception e){ 50 e.printStackTrace() ; 51 }finally{ 52 try { 53 conn.close(); 54 } catch (SQLException e) { 55 e.printStackTrace(); 56 } 57 58 } 59 } 60 61 //不使用鏈接池寫數據庫,只用一個鏈接,寫全部數據 62 public void writeDBByOneConn(int data, Statement stat){ 63 String sql = "insert into dbcp values (" + data + ")"; 64 try{ 65 stat.executeUpdate(sql); 66 }catch(Exception e){ 67 e.printStackTrace() ; 68 } 69 } 70 71 //經過DBCP鏈接池寫數據庫 72 public void writeDBByDBCP(int data){ 73 String sql = "insert into dbcp values (" + data + ")"; 74 try { 75 Connection conn = KCYDBCPUtil.getConnection(); 76 Statement stat = conn.createStatement(); 77 stat.executeUpdate(sql); 78 conn.commit(); 79 conn.close(); 80 } catch (SQLException e) { 81 e.printStackTrace(); 82 } 83 } 84 85 }
測試結果以下:
(1) 每次插入一條數據前,就建立一個鏈接,該條數據插入完成後,關閉該鏈接。耗時158.318秒
(2) 使用DBCP鏈接池,每次插入一條數據前,從DBCP鏈接池中獲取一條鏈接,該條數據插入完成後,該鏈接交由DBCP鏈接池管理。耗時122.404秒
(3) 在插入數據以前建立一條鏈接,2000個數據所有使用該鏈接,2000個數據插入完畢後,關閉該鏈接。耗時117.87秒
經過對比結果看出,向同一個表中插入2000條數據,每插入一條數據前建立一個新鏈接,會很是耗時,而使用DBCP鏈接池和使用同一個鏈接操做,耗時比較接近。
三、相關問題
1) 應用程序中,使用完一個數據庫鏈接後,DBCP鏈接池如何管理該鏈接。
分兩種狀況:
(1) 應用程序中主動關閉該鏈接,即DBCPTest.java中第79行 conn.close();
這種狀況並非手動將該鏈接關閉,而是將該鏈接交回給DBCP鏈接池,由鏈接池管理該鏈接。即用完鏈接後顯示的將數據庫鏈接提交至DBCP鏈接池。
(2) 應用程序中不關閉該鏈接,即將DBCPTest.java中第79行 conn.close()註釋掉
這種狀況DBCP配置文件dbcp.properties中的配置項(注意jar包版本,低版本中使用removeAbandoned=true配置項)
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
removeAbandonedTimeout=1
會起做用,removeAbandonedOnMaintenance=true和removeAbandonedOnBorrow=true表示DBCP鏈接池自動管理應程序中使用完畢的鏈接,removeAbandonedTimeout=1表示一個鏈接在程序中使用完畢後,若在1秒以內沒有再次使用,則DBCP鏈接池回收該鏈接(一般removeAbandonedTimeout不會配置1,此處爲了測試使用)。
(3) 驗證removeAbandonedOnMaintenance=true、removeAbandonedOnBorrow=true和removeAbandonedTimeout=1配置項的做用
將測試類DBCPTest.java的writeDBByDBCP(int data)方法修改成以下:
1 //經過DBCP鏈接池寫數據庫 2 public void writeDBByDBCP(int data){ 3 String sql = "insert into dbcp values (" + data + ")"; 4 try { 5 Connection conn = KCYDBCPUtil.getConnection(); 6 Statement stat = conn.createStatement(); 7 stat.executeUpdate(sql); 8 conn.commit(); 9 // conn.close(); 10 } catch (SQLException e) { 11 e.printStackTrace(); 12 } 13 }
從新執行testWriteDBByDBCP()方法,結果以下:
可見writeDBByDBCP(int data)方法修改後和修改前做用相同,說明鏈接使用完後,由DBCP鏈接池管理。
而若是將修改配置項removeAbandonedTimeout=180,即一個鏈接用完後會等待180秒,超過180秒後才由DBCP鏈接池回收,從新執行testWriteDBByDBCP()方法,執行一段時間後報錯(Cannot get a connection, pool error Timeout waiting for idle object),以下:
此時,查詢數據表,發現正好插入了30條數據,以下:
這說明在插入第31條數據的時候報錯,錯誤緣由是鏈接池中沒有可用的鏈接了。這是由於DBCP鏈接池初始化鏈接數爲30,removeAbandonedTimeout設爲180秒,因此30個鏈接用完後,程序運行還未 到180秒,程序中用完的鏈接都尚未被DBCP鏈接池回收,因此DBCP鏈接池中沒有可用的鏈接了,纔會在插入第31條數據時報錯。