DBCP數據庫連接池的簡單使用
0、DBCP簡介
DBCP(DataBase connection pool)數據庫連接池是 apache 上的一個Java連接池項目。DBCP通過連接池預先同數據庫建立一些連接放在內存中(即連接池中),應用程序需要建立數據庫連接時直接到從接池中申請一個連接使用,用完後由連接池回收該連接,從而達到連接復用,減少資源消耗的目的。
1、DBCP所依賴的jar包(以下例子基於如下jar包版本)
commons-dbcp2-2.1.1.jar commons-logging-1.2.jar commons-pool2-2.4.2.jar
2、DBCP使用示例
並在src同級目錄下創建了config目錄,用於存放DBCP的配置文件。
【註】類DBCPUtil.java在下面的例子中未用到。
1) DBCP配置文件dbcp.properties
########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=1
2) 創建初始化DBCP的類KCYDBCPUtil.java
package dbcp; import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; /** * DBCP配置類 * @author SUN */ public class KCYDBCPUtil { private static Properties properties = new Properties(); private static DataSource dataSource; //加載DBCP配置文件 static{ try{ FileInputStream is = new FileInputStream("config/dbcp.properties"); properties.load(is); }catch(IOException e){ e.printStackTrace(); } try{ dataSource = BasicDataSourceFactory.createDataSource(properties); }catch(Exception e){ e.printStackTrace(); } } //從連接池中獲取一個連接 public static Connection getConnection(){ Connection connection = null; try{ connection = dataSource.getConnection(); }catch(SQLException e){ e.printStackTrace(); } try { connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } return connection; } public static void main(String[] args) { getConnection(); } }
3) 創建使用JDBC獲取數據庫連接的類DBConn.java(用於和DBCP連接池對比)
package dbcp;import java.sql.Connection; import java.sql.DriverManager; public class DBConn { private static Connection conn = null; //獲取一個數據庫連接 public static Connection getConnection() { try { Class.forName("com.mysql.jdbc.Driver"); DriverManager.registerDriver(new com.mysql.jdbc.Driver()); String dbUrl = "jdbc:mysql://127.0.0.1:3306/mydb"; conn = DriverManager.getConnection(dbUrl, "sa", "123456"); // System.out.println("========數據庫連接成功========"); } catch (Exception e) { e.printStackTrace(); // System.out.println("========數據庫連接失敗========"); return null; } return conn; } }
4) 創建測試類DBCPTest.java
測試類中采用3中方法將2000個數據插入數據庫同一張表中,每次插入數據之前,先清空表,並對結果進行了對比。
3中插入數據方法如下:
(1) 每次插入一條數據前,就創建一個連接,該條數據插入完成後,關閉該連接;
(2) 使用DBCP連接池,每次插入一條數據前,從DBCP連接池中獲取一條連接,該條數據插入完成後,該連接交由DBCP連接池管理;
(3) 在插入數據之前創建一條連接,2000個數據全部使用該連接,2000個數據插入完畢後,關閉該連接。
package dbcp; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import org.junit.Test; public class DBCPTest { //測試,每寫一條數據前,就新建一個連接 @Test public void testWriteDBByEveryConn() throws Exception{ for(int i = 0; i < 2000; i++){ writeDBByEveryConn(i); } System.out.println("DONE"); } //測試,使用連接池,每寫一條數據前,從連接池中獲取一個連接 @Test public void testWriteDBByDBCP() throws Exception{ for(int i = 0; i < 2000; i++){ writeDBByDBCP(i); } System.out.println("DONE"); } //測試,只建一條連接,寫入所有數據 @Test public void testWriteDBByOneConn() throws Exception{ Connection conn = DBConn.getConnection(); Statement stat = conn.createStatement(); for(int i = 0; i < 2000; i++){ writeDBByOneConn(i, stat); } conn.close(); System.out.println("DONE"); } //不使用連接池寫數據庫,每寫一條數據創建一個連接 public void writeDBByEveryConn(int data){ String sql = "insert into dbcp values (" + data + ")"; Connection conn = DBConn.getConnection(); try{ Statement stat = conn.createStatement(); stat.executeUpdate(sql); }catch(Exception e){ e.printStackTrace() ; }finally{ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } //不使用連接池寫數據庫,只用一個連接,寫所有數據 public void writeDBByOneConn(int data, Statement stat){ String sql = "insert into dbcp values (" + data + ")"; try{ stat.executeUpdate(sql); }catch(Exception e){ e.printStackTrace() ; } } //通過DBCP連接池寫數據庫 public void writeDBByDBCP(int data){ String sql = "insert into dbcp values (" + data + ")"; try { Connection conn = KCYDBCPUtil.getConnection(); Statement stat = conn.createStatement(); stat.executeUpdate(sql); conn.commit(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
測試結果如下:
(1) 每次插入一條數據前,就創建一個連接,該條數據插入完成後,關閉該連接。耗時158.318秒
(2) 使用DBCP連接池,每次插入一條數據前,從DBCP連接池中獲取一條連接,該條數據插入完成後,該連接交由DBCP連接池管理。耗時122.404秒
(3) 在插入數據之前創建一條連接,2000個數據全部使用該連接,2000個數據插入完畢後,關閉該連接。耗時117.87秒
通過對比結果看出,向同一個表中插入2000條數據,每插入一條數據前創建一個新連接,會非常耗時,而使用DBCP連接池和使用同一個連接操作,耗時比較接近。
3、相關問題
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)方法修改為如下:
//通過DBCP連接池寫數據庫 public void writeDBByDBCP(int data){ String sql = "insert into dbcp values (" + data + ")"; try { Connection conn = KCYDBCPUtil.getConnection(); Statement stat = conn.createStatement(); stat.executeUpdate(sql); conn.commit(); // conn.close(); } catch (SQLException e) { e.printStackTrace(); } }
重新執行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條數據時報錯。
DBCP數據庫連接池的簡單使用