1、核心的包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons.pool.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>${commons.dbcp2.version}</version>
</dependency>
2、基本內容介紹
(1)dbcp藉助於commons-pool這個jar包,commons-pool是一種對象池的實現,下面給出一些基本的pool中的概念。
對象生命週期狀態枚舉
public enum PooledObjectState {
// 空閒的對象
IDLE,
// 使用中的對象
ALLOCATED,
// 正在被驅逐線程測試的對象
EVICTION,
// 正在被驅逐線程測試,一單測試完成後,會將
EVICTION_RETURN_TO_HEAD,
// 正在被驗證的對象
VALIDATION,
// 正在被驗證的對象,一單驗證通過,會直接通過borrow借出。
VALIDATION_PREALLOCATED,
// 驅逐線程驗證的對象,沒問題回將對象放到隊列的頭部
VALIDATION_RETURN_TO_HEAD,
// 不合法的對象,之後會被移出
INVALID,
//廢棄的對象,之後會被銷燬
ABANDONED,
//正在返回到池中的對象
RETURNING
}
3、核心參數和代碼中的體現
- initialSize
當連接池創建時初始化創建連接的數目
當首次獲取數據庫連接時,這個參數起到作用,會初始化initialSize這個數目的空閒連接數。在BasicDataSource的
createDataSource這個方法中,會有如下代碼,進行初始化連接的建立。
// If initialSize > 0, preload the pool
try {
for (int i = 0; i < initialSize; i++) {
connectionPool.addObject();
}
} catch (final Exception e) {
closeConnectionPool();
throw new SQLException("Error preloading the connection pool", e);
}
- minIdle
最小化的空閒連接數目,當空閒驅逐線程進行超時空閒線程驅逐時,驅逐完畢後能夠保證空閒連接數目至少達到最小值,這個參數必須在設置timeBetweenEvictionRunsMillis這個參數爲正值時纔會起作用(就是讓驅逐線程運行起來)
在BaseGenericObjectPool.Evictor中,會用到minIdle,Evictor實現了Runnable接口,驅逐線程運行的就是這個實例任務。當完成了
超時連接和基於其他參數的連接後,會調用內部方法ensureMinIdle()保證空閒連接滿足minIdle這個值。
private void ensureIdle(final int idleCount, final boolean always) throws Exception {
if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
return;
}
// 空閒連接小於設置的minIdle
while (idleObjects.size() < idleCount) {
final PooledObject<T> p = create();
if (p == null) {
// Can't create objects, no reason to think another call to
// create will work. Give up.
break;
}
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
}
if (isClosed()) {
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
- maxIdle
最大的空閒連接的數目,當用戶用完一個連接後,然後重新把這個連接放到空閒連接池中,如果此時的空閒連接數超過maxIdle,就會destroy部分
多餘的空閒線程。
- maxWaitMillis
獲取一個連接的最長超時時間,如果等於小於等於0表示無限等待。
- validationQuery
當獲取一個連接後,用這個sql驗證連接的有效性。
- testOnBorrow
當從池子中獲取一個連接時,是否需要驗證連接的有效性,如果需要驗證,就使用validationQuery設置的語句驗證連接的有效性。
- removeAbandonedTimeout
一個活動連接的超時時間(意思就是一個連接在removeAbandonedTimeout這個時間裏一直處於使用狀態),一個連接在過長的時間裏處於活動狀態,要麼是連接設置狀態出現問題,要麼是執行一個很慢的sql語句,此時主動回收,可以中斷慢sql的執行。這個參數只有removeAbandonedOnBorrow
和removeAbandonedOnMaintenance這個參數其中之一設置爲true時才能起作用。
- removeAbandonedOnBorrow
表示當在pool中拿連接時,嘗試將廢棄的連接移除掉。
- minEvictableIdleTimeMillis
表示一個連接持續minEvictableIdleTimeMillis這麼長的時間一直處於空閒時,空閒連接會嘗試驅逐。注:主要可以防止一個連接空閒時間太久了,服務端主要將連接斷開了,導致連接時效,從而查庫失敗。
- timeBetweenEvictionRunsMillis
表示驅逐線程每隔多久運行一次。
只有這個參數設置的值大於0,纔會啓動驅逐線程
protected void startPoolMaintenance() {
// 只有timeBetweenEvictionRunsMillis這個參數大於0,纔會啓動驅逐線程
if (connectionPool != null && timeBetweenEvictionRunsMillis > 0) {
connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
}
}
public final void setTimeBetweenEvictionRunsMillis(
final long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
startEvictor(timeBetweenEvictionRunsMillis);
}
//啓動驅逐線程
final void startEvictor(final long delay) {
synchronized (evictionLock) {
EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS);
evictor = null;
evictionIterator = null;
if (delay > 0) {
evictor = new Evictor();
EvictionTimer.schedule(evictor, delay, delay);
}
}
}
- testWhileIdle
表示驅逐線程是否要驗證連接的有效性,就算一個空閒連接沒有超時,但是如果有效性無法驗證通過,也會進行驅逐。(驗證有效性,主要是防止一個空閒連接服務端在很短的時間裏就斷開了。)
testWhileIdle參數也是針對於驅逐線程,當使用驅逐線程判斷線程沒有超時不需要驅逐時,會判斷testWhileIdle是否爲true,如果是,
對連接進行校驗,校驗失敗也會從連接池中移除此線程。
核心代碼如下:
public void evict() throws Exception {
assertOpen();
// 有空閒連接就進行校驗
if (idleObjects.size() > 0) {
PooledObject<T> underTest = null;
final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();
synchronized (evictionLock) {
final EvictionConfig evictionConfig = new EvictionConfig(
getMinEvictableIdleTimeMillis(),
getSoftMinEvictableIdleTimeMillis(),
getMinIdle());
final boolean testWhileIdle = getTestWhileIdle();
for (int i = 0, m = getNumTests(); i < m; i++) {
if (evictionIterator == null || !evictionIterator.hasNext()) {
evictionIterator = new EvictionIterator(idleObjects);
}
if (!evictionIterator.hasNext()) {
// Pool exhausted, nothing to do here
return;
}
try {
underTest = evictionIterator.next();
} catch (final NoSuchElementException nsee) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
evictionIterator = null;
continue;
}
if (!underTest.startEvictionTest()) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--;
continue;
}
// User provided eviction policy could throw all sorts of
// crazy exceptions. Protect against such an exception
// killing the eviction thread.
boolean evict;
try {
// 根據配置的驅逐策略進行驅逐校驗
evict = evictionPolicy.evict(evictionConfig, underTest,
idleObjects.size());
} catch (final Throwable t) {
// Slightly convoluted as SwallowedExceptionListener
// uses Exception rather than Throwable
PoolUtils.checkRethrow(t);
swallowException(new Exception(t));
// Don't evict on error conditions
evict = false;
}
// 如果應該驅逐,直接銷燬連接
if (evict) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
} else {
// 判斷testWhileIdle是否爲true,進行校驗。
if (testWhileIdle) {
boolean active = false;
try {
factory.activateObject(underTest);
active = true;
} catch (final Exception e) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
}
if (active) {
if (!factory.validateObject(underTest)) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
} else {
try {
factory.passivateObject(underTest);
} catch (final Exception e) {
destroy(underTest);
destroyedByEvictorCount.incrementAndGet();
}
}
}
}
if (!underTest.endEvictionTest(idleObjects)) {
// TODO - May need to add code here once additional
// states are used
}
}
}
}
}
final AbandonedConfig ac = this.abandonedConfig;
// 如果removeAbandonedOnMaintenance設置爲true時,在驅逐線程中還會移除廢棄的連接。
if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
removeAbandoned(ac);
}
}
- numTestsPerEvictionRun
表示驅逐線程每次校驗的連接的數目
- maxTotal
表示連接的最大數目,包括空閒連接和活動的連接。
GenericObjectPool類中有兩個對象allObjects和idleObjects,其中allObjects就是連接對象的總集合,
idleObjects就是空閒連接。
- accessToUnderlyingConnectionAllowed
主要是控制PoolGuard是否允許訪問正在處理中的線程
- connectionProperties
設置連接的一些屬性參數,主要是驅動器進行數據庫連接時需要用到的一些參數。
- connectionInitSqls
當一個連接物理連接創建後,執行的一些sql語句的集合。
調用PoolableConnectionFactory的makObject方法構建連接時,會執行連接創建的初始化語句。
protected void initializeConnection(final Connection conn) throws SQLException {
final Collection<String> sqls = connectionInitSqls;
if (conn.isClosed()) {
throw new SQLException("initializeConnection: connection closed");
}
if (null != sqls) {
try (Statement stmt = conn.createStatement()) {
for (final String sql : sqls) {
Objects.requireNonNull(sql, "null connectionInitSqls element");
stmt.execute(sql);
}
}
}
}
- testOnReturn
當一個連接重新歸還到池子中時,校驗連接的有效性。
當一個連接使用完後重新往連接池中放時,如果testOnReturn設置爲true,則校驗連接,如何正常,會重新放入連接池,否則
銷燬連接。部分歸還連接的代碼如下:
/**
* Return me to my pool.
*/
@Override
public void close() throws SQLException {
// calling close twice should have no effect
if (!isClosed()) {
try {
// 整理就是歸還連接池
pool.returnObject(key, this);
} catch (final SQLException e) {
throw e;
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new SQLException("Cannot close preparedstatement (return to pool failed)", e);
}
}
}
- validationQueryTimeoutSeconds
執行一個校驗連接查詢的超時時間,當小於等於0時,沒有超時的限制。
public void validateConnection(final PoolableConnection conn) throws SQLException {
if (conn.isClosed()) {
throw new SQLException("validateConnection: connection closed");
}
conn.validate(validationQuery, validationQueryTimeoutSeconds);
}
- removeAbandonedOnMaintenance
當對pool進行檢查時,移除超時的廢棄連接。也是在驅逐線程的evict()方法中用到這個參數
- evictionPolicyClassName
定義驅逐策略的類,也就是一個線程是否應該進行驅逐可以自己定義一個EvictionPolicy這個接口的實現類。
- fastFailValidation
使用工廠創建的連接能夠進行快速失敗驗證, 驗證時直接拋出異常
- testOnCreate
表示一個對象創建完成後,直接進行校驗。
- softMinEvictableIdleTimeMillis
和minEvictableIdleTimeMillis類似,只是softMinEvictableIdleTimeMillis這個參數需要保證池中至少有minIdle個空閒連接存在才進行驅逐。
4、使用實例
public static void main(String[] args) throws Exception {
testGetConnection();
}
public static void testGetConnection() throws Exception {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setInitialSize(2);
dataSource.setMinIdle(3);
dataSource.setMaxIdle(6);
// dataSource.setMaxWaitMillis(500);
dataSource.setMaxTotal(10);
dataSource.setAccessToUnderlyingConnectionAllowed(true);
dataSource.setConnectionProperties("m=1");
List<String> sqls = new ArrayList<>();
sqls.add("select 1");
dataSource.setConnectionInitSqls(sqls);
dataSource.setTestOnReturn(true);
// dataSource.setValidationQueryTimeout(1);
dataSource.setRemoveAbandonedOnMaintenance(true);
// dataSource.setEvictionPolicyClassName();
dataSource.setFastFailValidation(true);
dataSource.setTestOnCreate(true);
// dataSource.setSoftMinEvictableIdleTimeMillis(100);
dataSource.setValidationQuery("select 1");
dataSource.setTestOnBorrow(true);
dataSource.setRemoveAbandonedTimeout(100);
dataSource.setRemoveAbandonedOnBorrow(true);
dataSource.setMinEvictableIdleTimeMillis(3000);
dataSource.setTimeBetweenEvictionRunsMillis(3000);
dataSource.setTestWhileIdle(true);
dataSource.setNumTestsPerEvictionRun(10);
dataSource.setUrl("jdbc:mysql://localhost:3306/testdb0");
dataSource.setPassword("jcw123");
dataSource.setUsername("root");
for(int i = 0; i < 5; i++) {
new Thread(()->{
while(true) {
try {
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from testtable0 limit 1");
ResultSet resultSet = preparedStatement.executeQuery();
resultSet.next();
System.out.println(resultSet.getInt(2));
Thread.sleep(1000);
}catch (Exception e) {}
}
}, "tread" + i).start();
}
}