0、DBCP簡介
DBCP(DataBase connection pool)數(shù)據(jù)庫連接池是 apache 上的一個(gè)Java連接池項(xiàng)目。DBCP通過連接池預(yù)先同數(shù)據(jù)庫建立一些連接放在內(nèi)存中(即連接池中),應(yīng)用程序需要建立數(shù)據(jù)庫連接時(shí)直接到從接池中申請一個(gè)連接使用,用完后由連接池回收該連接,從而達(dá)到連接復(fù)用,減少資源消耗的目的。
1、DBCP所依賴的jar包(以下例子基于如下jar包版本)
commons-dbcp2-2.1.1.jar commons-logging-1.2.jar commons-pool2-2.4.2.jar
2、DBCP使用示例
下圖是在Eclipse中創(chuàng)建的Java工程,使用了DBCP相關(guān)的jar包,mysql的jdbc驅(qū)動(dòng)jar包,junit4 。
并在src同級目錄下創(chuàng)建了config目錄,用于存放DBCP的配置文件。
【注】類DBCPUtil.java在下面的例子中未用到。

1) DBCP配置文件dbcp.properties
#驅(qū)動(dòng)名
driverClassName=com.mysql.jdbc.Driver
#url
url=jdbc:mysql://127.0.0.1:3306/mydb
#用戶名
username=sa
#密碼
password=123456
#初試連接數(shù)
initialSize=30
#最大活躍數(shù)
maxTotal=30
#最大idle數(shù)
maxIdle=10
#最小idle數(shù)
minIdle=5
#最長等待時(shí)間(毫秒)
maxWaitMillis=1000
#程序中的連接不使用后是否被連接池回收(該版本要使用removeAbandonedOnMaintenance和removeAbandonedOnBorrow)
#removeAbandoned=true
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
#連接在所指定的秒數(shù)內(nèi)未使用才會被刪除(秒)(為配合測試程序才配置為1秒)
removeAbandonedTimeout=1
2) 創(chuàng)建初始化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();
}
}
//從連接池中獲取一個(gè)連接
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) 創(chuàng)建使用JDBC獲取數(shù)據(jù)庫連接的類DBConn.java(用于和DBCP連接池對比)
import java.sql.Connection;
import java.sql.DriverManager;
public class DBConn {
private static Connection conn = null;
//獲取一個(gè)數(shù)據(jù)庫連接
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("========數(shù)據(jù)庫連接成功========");
} catch (Exception e) {
e.printStackTrace();
// System.out.println("========數(shù)據(jù)庫連接失敗========");
return null;
}
return conn;
}
}
4) 創(chuàng)建測試類DBCPTest.java
測試類中采用3中方法將2000個(gè)數(shù)據(jù)插入數(shù)據(jù)庫同一張表中,每次插入數(shù)據(jù)之前,先清空表,并對結(jié)果進(jìn)行了對比。
3中插入數(shù)據(jù)方法如下:
(1) 每次插入一條數(shù)據(jù)前,就創(chuàng)建一個(gè)連接,該條數(shù)據(jù)插入完成后,關(guān)閉該連接;
(2) 使用DBCP連接池,每次插入一條數(shù)據(jù)前,從DBCP連接池中獲取一條連接,該條數(shù)據(jù)插入完成后,該連接交由DBCP連接池管理;
(3) 在插入數(shù)據(jù)之前創(chuàng)建一條連接,2000個(gè)數(shù)據(jù)全部使用該連接,2000個(gè)數(shù)據(jù)插入完畢后,關(guān)閉該連接。
package dbcp;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
public class DBCPTest {
//測試,每寫一條數(shù)據(jù)前,就新建一個(gè)連接
@Test
public void testWriteDBByEveryConn() throws Exception{
for(int i = 0; i < 2000; i++){
writeDBByEveryConn(i);
}
System.out.println("DONE");
}
//測試,使用連接池,每寫一條數(shù)據(jù)前,從連接池中獲取一個(gè)連接
@Test
public void testWriteDBByDBCP() throws Exception{
for(int i = 0; i < 2000; i++){
writeDBByDBCP(i);
}
System.out.println("DONE");
}
//測試,只建一條連接,寫入所有數(shù)據(jù)
@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");
}
//不使用連接池寫數(shù)據(jù)庫,每寫一條數(shù)據(jù)創(chuàng)建一個(gè)連接
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();
}
}
}
//不使用連接池寫數(shù)據(jù)庫,只用一個(gè)連接,寫所有數(shù)據(jù)
public void writeDBByOneConn(int data, Statement stat){
String sql = "insert into dbcp values (" + data + ")";
try{
stat.executeUpdate(sql);
}catch(Exception e){
e.printStackTrace() ;
}
}
//通過DBCP連接池寫數(shù)據(jù)庫
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();
}
}
}
測試結(jié)果如下:
(1) 每次插入一條數(shù)據(jù)前,就創(chuàng)建一個(gè)連接,該條數(shù)據(jù)插入完成后,關(guān)閉該連接。耗時(shí)158.318秒

(2) 使用DBCP連接池,每次插入一條數(shù)據(jù)前,從DBCP連接池中獲取一條連接,該條數(shù)據(jù)插入完成后,該連接交由DBCP連接池管理。耗時(shí)122.404秒

(3) 在插入數(shù)據(jù)之前創(chuàng)建一條連接,2000個(gè)數(shù)據(jù)全部使用該連接,2000個(gè)數(shù)據(jù)插入完畢后,關(guān)閉該連接。耗時(shí)117.87秒

通過對比結(jié)果看出,向同一個(gè)表中插入2000條數(shù)據(jù),每插入一條數(shù)據(jù)前創(chuàng)建一個(gè)新連接,會非常耗時(shí),而使用DBCP連接池和使用同一個(gè)連接操作,耗時(shí)比較接近。
3、相關(guān)問題
1) 應(yīng)用程序中,使用完一個(gè)數(shù)據(jù)庫連接后,DBCP連接池如何管理該連接。
分兩種情況:
(1) 應(yīng)用程序中主動(dòng)關(guān)閉該連接,即DBCPTest.java中第79行 conn.close();
這種情況并不是手動(dòng)將該連接關(guān)閉,而是將該連接交回給DBCP連接池,由連接池管理該連接。即用完連接后顯示的將數(shù)據(jù)庫連接提交至DBCP連接池。
(2) 應(yīng)用程序中不關(guān)閉該連接,即將DBCPTest.java中第79行 conn.close()注釋掉
這種情況DBCP配置文件dbcp.properties中的配置項(xiàng)(注意jar包版本,低版本中使用removeAbandoned=true配置項(xiàng))
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
removeAbandonedTimeout=1
會起作用,removeAbandonedOnMaintenance=true和removeAbandonedOnBorrow=true表示DBCP連接池自動(dòng)管理應(yīng)程序中使用完畢的連接,removeAbandonedTimeout=1表示一個(gè)連接在程序中使用完畢后,若在1秒之內(nèi)沒有再次使用,則DBCP連接池回收該連接(通常removeAbandonedTimeout不會配置1,此處為了測試使用)。
(3) 驗(yàn)證removeAbandonedOnMaintenance=true、removeAbandonedOnBorrow=true和removeAbandonedTimeout=1配置項(xiàng)的作用
將測試類DBCPTest.java的writeDBByDBCP(int data)方法修改為如下:
//通過DBCP連接池寫數(shù)據(jù)庫
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();
}
}
重新執(zhí)行testWriteDBByDBCP()方法,結(jié)果如下:

可見writeDBByDBCP(int data)方法修改后和修改前作用相同,說明連接使用完后,由DBCP連接池管理。
而如果將修改配置項(xiàng)removeAbandonedTimeout=180,即一個(gè)連接用完后會等待180秒,超過180秒后才由DBCP連接池回收,重新執(zhí)行testWriteDBByDBCP()方法,執(zhí)行一段時(shí)間后報(bào)錯(cuò)(Cannot get a connection, pool error Timeout waiting for idle object),如下:

此時(shí),查詢數(shù)據(jù)表,發(fā)現(xiàn)正好插入了30條數(shù)據(jù),如下:

這說明在插入第31條數(shù)據(jù)的時(shí)候報(bào)錯(cuò),錯(cuò)誤原因是連接池中沒有可用的連接了。這是因?yàn)镈BCP連接池初始化連接數(shù)為30,removeAbandonedTimeout設(shè)為180秒,所以30個(gè)連接用完后,程序運(yùn)行還未 到180秒,程序中用完的連接都還沒有被DBCP連接池回收,所以DBCP連接池中沒有可用的連接了,才會在插入第31條數(shù)據(jù)時(shí)報(bào)錯(cuò)。
浙公網(wǎng)安備 33010602011771號