Apache Commons DBCP 配置参数详解

逐项详解 Apache Commons DBCP 2.14.0 的 BasicDataSource 配置参数:容量上限、等待超时、有效性检测、空闲回收、连接生命周期与泄漏回收、事务默认属性,默认值均取自实测运行回显,每项配可运行示例。

Apache Commons DBCPApache Commons DBCP2026/10/14

Apache Commons DBCP 配置参数详解

目标:系统梳理 Apache Commons DBCP 2.14.0 的 BasicDataSource 全部关键配置参数——容量、超时、有效性检测、空闲回收、生命周期、泄漏回收、事务默认值——每项给出实测默认值与可运行示例。全部示例已在 Ubuntu 22.04 / CentOS 8(JDK 17 + Maven)实测通过。

一、环境信息

本章先固定实验环境与依赖。DBCP 是纯 Java 库、无常驻服务,所有参数都通过 BasicDataSource 的 setter 在代码中设置,没有外部配置文件。

验证环境:

java -version 2>&1 | head -1

实测回显(Ubuntu 22.04):

openjdk version "17.0.20.1" 2026-08-18

实测回显(CentOS 8,需先设 JAVA_HOME,见《CentOS 8 安装 Apache Commons DBCP 指南》):

openjdk version "17.0.1" 2021-10-19 LTS

本文示例统一使用以下 Maven 依赖(坐标与版本为 2.14.0 官方 pom 实测值):

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-dbcp2</artifactId>
  <version>2.14.0</version>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <version>2.5.252</version>
</dependency>

逐项说明:commons-dbcp2 是库本体,传递依赖 commons-pool2 2.13.0、commons-logging 1.3.5、jakarta.transaction-api 1.3.3 由 Maven 自动带入;com.h2database:h2 只是提供真实 JDBC 数据源用于演示,与参数本身无关。

💡 技术拓展:Apache Commons DBCP 只是「数据库连接池」大家庭中的一员。Java 连接池复用物理数据库连接、降低握手与鉴权开销,业界常见实现对比:

连接池出身特点适用场景
Commons DBCP2Apache,本文主角纯 Java、参数直观、与 Tomcat/Spring 兼容广传统项目、需轻量显式配置
HikariCP社区极致性能、极少参数、启动即调优高并发新项目首选
C3P0社区老牌、配置项多、曾与 Hibernate 绑定存量 Hibernate 项目
Druid阿里自带监控台、SQL 防火墙、统计丰富国内运维监控要求高
Tomcat JDBC PoolApache TomcatDBCP 的替代品、异步取连接Tomcat 应用内

本文讲解的是 Commons DBCP2 的参数。不同连接池的参数命名与默认值差异较大,跨池迁移时务必逐项核对,不能想当然套用。

二、前置条件与参数查看方式

本章说明参数承载类与「如何拿到真实默认值」,为后续逐类详解打好基础。

2.1 参数承载类

DBCP 的参数全部挂在 org.apache.commons.dbcp2.BasicDataSource 上,通过 setXxx() 设置、getXxx() 读取。它同时是 javax.sql.DataSource 实现,所以既能直接当数据源用,也能注册成 JNDI 资源。

BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:cfg;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
ds.setMaxTotal(10);          // 池中连接总数上限
ds.setMaxWaitMillis(3000);   // 取连接的最长等待毫秒数

2.14.0 的一个新特性:除了毫秒 long 重载,还有 java.time.Duration 重载,例如 setMaxWait(Duration.ofSeconds(3))、setMaxConn(Duration.ofMinutes(30))、setRemoveAbandonedTimeout(Duration.ofMinutes(5)),语义完全等价,按代码风格二选一即可。

2.2 打印真实默认值

默认值不能靠记忆,必须实测。写一个只 new 出对象、逐项 get 打印的程序 PoolDefaults:

package net.quzuo.demo;

import org.apache.commons.dbcp2.BasicDataSource;

public class PoolDefaults {
    public static void main(String[] args) throws Exception {
        BasicDataSource ds = new BasicDataSource();
        System.out.println("maxTotal                    = " + ds.getMaxTotal());
        System.out.println("maxIdle                     = " + ds.getMaxIdle());
        System.out.println("minIdle                     = " + ds.getMinIdle());
        System.out.println("initialSize                 = " + ds.getInitialSize());
        System.out.println("maxWaitMillis               = " + ds.getMaxWaitMillis());
        System.out.println("validationQuery             = " + ds.getValidationQuery());
        System.out.println("validationQueryTimeout      = " + ds.getValidationQueryTimeout());
        System.out.println("testOnCreate                = " + ds.getTestOnCreate());
        System.out.println("testOnBorrow                = " + ds.getTestOnBorrow());
        System.out.println("testOnReturn                = " + ds.getTestOnReturn());
        System.out.println("testWhileIdle               = " + ds.getTestWhileIdle());
        System.out.println("timeBetweenEvictionRunsMillis = " + ds.getTimeBetweenEvictionRunsMillis());
        System.out.println("numTestsPerEvictionRun      = " + ds.getNumTestsPerEvictionRun());
        System.out.println("minEvictableIdleTimeMillis  = " + ds.getMinEvictableIdleTimeMillis());
        System.out.println("maxConnLifetimeMillis       = " + ds.getMaxConnLifetimeMillis());
        System.out.println("removeAbandonedOnBorrow     = " + ds.getRemoveAbandonedOnBorrow());
        System.out.println("removeAbandonedOnMaintenance= " + ds.getRemoveAbandonedOnMaintenance());
        System.out.println("removeAbandonedTimeout      = " + ds.getRemoveAbandonedTimeout());
        System.out.println("abandonedUsageTracking      = " + ds.getAbandonedUsageTracking());
        System.out.println("lifo                        = " + ds.getLifo());
        System.out.println("defaultAutoCommit           = " + ds.getDefaultAutoCommit());
        System.out.println("defaultReadOnly             = " + ds.getDefaultReadOnly());
        System.out.println("defaultTransactionIsolation = " + ds.getDefaultTransactionIsolation());
        System.out.println("defaultCatalog              = " + ds.getDefaultCatalog());
        System.out.println("defaultSchema               = " + ds.getDefaultSchema());
        System.out.println("defaultQueryTimeout         = " + ds.getDefaultQueryTimeout());
        System.out.println("enableAutoCommitOnReturn    = " + ds.getEnableAutoCommitOnReturn());
        System.out.println("rollbackOnReturn            = " + ds.getRollbackOnReturn());
        System.out.println("cacheState                  = " + ds.getCacheState());
        System.out.println("poolPreparedStatements      = " + ds.isPoolPreparedStatements());
        System.out.println("maxOpenPreparedStatements   = " + ds.getMaxOpenPreparedStatements());
        System.out.println("clearStatementPoolOnReturn  = " + ds.isClearStatementPoolOnReturn());
        ds.close();
    }
}

运行:

mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.PoolDefaults

实测回显(Ubuntu 22.04 与 CentOS 8 输出完全一致):

maxTotal                    = 8
maxIdle                     = 8
minIdle                     = 0
initialSize                 = 0
maxWaitMillis               = -1
validationQuery             = null
validationQueryTimeout      = -1
testOnCreate                = false
testOnBorrow                = true
testOnReturn                = false
testWhileIdle               = false
timeBetweenEvictionRunsMillis = -1
numTestsPerEvictionRun      = 3
minEvictableIdleTimeMillis  = 1800000
maxConnLifetimeMillis       = -1
removeAbandonedOnBorrow     = false
removeAbandonedOnMaintenance= false
removeAbandonedTimeout      = 300
abandonedUsageTracking      = false
lifo                        = true
defaultAutoCommit           = null
defaultReadOnly             = null
defaultTransactionIsolation = -1
defaultCatalog              = null
defaultSchema               = null
defaultQueryTimeout         = null
enableAutoCommitOnReturn    = true
rollbackOnReturn            = true
cacheState                  = true
poolPreparedStatements      = false
maxOpenPreparedStatements   = -1
clearStatementPoolOnReturn  = false

两点要特别注意:-1 在这里普遍表示「不限制」(如 maxWaitMillis=-1 是无限等待、maxConnLifetimeMillis=-1 是不限寿命);null 表示「跟随驱动默认」(如 defaultAutoCommit=null 即由 JDBC 驱动决定自动提交)。

三、参数总览

下表汇总各参数默认值与含义,后续章节逐类展开:

参数默认值(实测)含义
maxTotal8池中连接(含借出与空闲)总数上限
maxIdle8允许保留的最大空闲连接数
minIdle0保底空闲连接数,低于它时会补建
initialSize0首次建池时预建的连接数
maxWaitMillis-1取连接最长等待毫秒数,-1 为无限
validationQuerynull校验连接有效性的 SQL,null 表示不校验
validationQueryTimeout-1校验 SQL 超时秒数,-1 为不限制
testOnCreatefalse新建连接后是否校验
testOnBorrowtrue借出连接前是否校验
testOnReturnfalse归还连接时是否校验
testWhileIdlefalse空闲连接被回收线程检查时是否校验
timeBetweenEvictionRunsMillis-1回收线程运行间隔毫秒数,-1 为不运行
numTestsPerEvictionRun3每轮回收检查的连接数
minEvictableIdleTimeMillis1800000空闲超过该毫秒数即可被回收(默认 30 分钟)
maxConnLifetimeMillis-1连接最长存活毫秒数,-1 为不限
removeAbandonedOnBorrowfalse借出时是否回收超时未归还的连接
removeAbandonedOnMaintenancefalse维护时是否回收超时未归还的连接
removeAbandonedTimeout300判定「被遗弃」的空闲秒数阈值
lifotrue空闲连接是否后进先出

关键差异与选型:maxTotal/maxIdle/minIdle/initialSize 决定容量与预热;maxWaitMillis 决定取不到连接时应用的行为;timeBetweenEvictionRunsMillis 一旦为默认 -1,则 minEvictableIdleTimeMillis、numTestsPerEvictionRun、testWhileIdle 全部失效(因为没有回收线程在跑)——这是生产环境最容易踩的坑。

四、连接池容量参数

本章用 PoolMonitor 观察 maxTotal/maxIdle/minIdle/initialSize 的实际效果。

package net.quzuo.demo;

import org.apache.commons.dbcp2.BasicDataSource;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

public class PoolMonitor {
    public static void main(String[] args) throws Exception {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc:h2:mem:monitor;DB_CLOSE_DELAY=-1");
        ds.setUsername("sa");
        ds.setPassword("");
        ds.setMaxTotal(5);
        ds.setMaxIdle(5);
        ds.setMinIdle(2);
        ds.setInitialSize(2);

        System.out.println("== 首次借还触发池初始化(initialSize=2 预建连接) ==");
        ds.getConnection().close();
        snapshot(ds);

        List<Connection> borrowed = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            borrowed.add(ds.getConnection());
        }
        System.out.println("== 借出 4 条后 ==");
        snapshot(ds);

        for (Connection c : borrowed) {
            c.close();
        }
        System.out.println("== 全部归还后 ==");
        snapshot(ds);
        ds.close();
    }

    private static void snapshot(BasicDataSource ds) {
        System.out.println(String.format(
                "maxTotal=%d maxIdle=%d minIdle=%d | numActive=%d numIdle=%d",
                ds.getMaxTotal(), ds.getMaxIdle(), ds.getMinIdle(),
                ds.getNumActive(), ds.getNumIdle()));
    }
}

运行:

mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.PoolMonitor

实测回显:

== 首次借还触发池初始化(initialSize=2 预建连接) ==
maxTotal=5 maxIdle=5 minIdle=2 | numActive=0 numIdle=2
== 借出 4 条后 ==
maxTotal=5 maxIdle=5 minIdle=2 | numActive=4 numIdle=0
== 全部归还后 ==
maxTotal=5 maxIdle=5 minIdle=2 | numActive=0 numIdle=4

逐项说明:

  • maxTotal:本例为 5。实测借到 4 条时 numActive=4,尚未触顶;再借第 6 条才会等待/失败。
  • maxIdle:本例为 5。归还 4 条后 numIdle=4,未超过上限故全部保留;若归还 8 条而 maxIdle=5,多出的 3 条会被直接关闭。
  • minIdle:本例为 2。配合 initialSize=2,首次建池后立即有 2 条空闲连接待命。
  • initialSize:关键实测结论——池是懒创建的,第一次 getConnection() 才触发建池。所以初始化前 numIdle=0,首次借还之后 numIdle 才等于 initialSize(本例为 2)。这也是为什么很多项目「启动就 panic」:预热要显式触发一次取连接。

五、等待与超时参数

maxWaitMillis 决定池满且无空闲连接时,取连接的线程等待多久。用 PoolBehavior 实测(maxTotal=3、maxWaitMillis=1000):

BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:behavior;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
ds.setMaxTotal(3);
ds.setMaxWaitMillis(1000);
List<Connection> held = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
    held.add(ds.getConnection());
    System.out.println("第 " + i + " 次 getConnection -> 活跃=" + ds.getNumActive()
            + " 空闲=" + ds.getNumIdle());
}
long start = System.currentTimeMillis();
try {
    ds.getConnection();
} catch (SQLException ex) {
    System.out.println("等待 " + (System.currentTimeMillis() - start) + " ms 后抛错: "
            + ex.getClass().getName());
    System.out.println("异常消息: " + ex.getMessage());
}
for (Connection c : held) {
    c.close();
}
System.out.println("归还后 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());

实测回显:

第 1 次 getConnection -> 活跃=1 空闲=0
第 2 次 getConnection -> 活跃=2 空闲=0
第 3 次 getConnection -> 活跃=3 空闲=0
等待 1000 ms 后抛错: java.sql.SQLException
异常消息: Cannot get a connection, pool error Timeout waiting for idle object, maxWaitDuration=PT0.999955811S
归还后 活跃=0 空闲=3

逐项说明:maxWaitMillis=1000 时第 4 次取连接恰好等待约 1000 ms 后抛 SQLException,异常消息里的 PT0.999955811S 是 Duration 形式的实际等待时长;默认 -1 则表示无限等待,池满时线程会一直挂起,生产环境务必显式设置,否则一次连接泄漏就会拖垮整个应用线程池。

六、连接有效性检测参数

本章讲 validationQuery/testOnBorrow/testOnReturn/testWhileIdle/validationQueryTimeout/testOnCreate。它们决定「怎么确认从池里拿到的连接还活着」。

参数默认值作用
validationQuerynull校验用 SQL,通常写 SELECT 1;为 null 时不做校验
validationQueryTimeout-1校验 SQL 的超时秒数,-1 为不限制
testOnCreatefalse连接刚创建时校验一次
testOnBorrowtrue借出时校验(DBCP2 默认开启)
testOnReturnfalse归还时校验
testWhileIdlefalse回收线程检查空闲连接时校验

实测要点(来自第二、五章运行):testOnBorrow 默认 true,但 validationQuery 默认为 null——默认校验方式是调用 JDBC 驱动的 Connection.isValid(),比执行 SQL 更轻;一旦你显式设置 validationQuery=SELECT 1,testOnBorrow=true 就会退化为执行该 SQL。二者取舍:isValid() 快但部分老驱动不支持;SELECT 1 通用但每次都多一次网络往返。

推荐生产配置(读者可直接粘贴):

ds.setValidationQuery("SELECT 1");
ds.setValidationQueryTimeout(3);
ds.setTestOnBorrow(true);
ds.setTestWhileIdle(true);
ds.setTimeBetweenEvictionRunsMillis(30000);

七、空闲连接回收参数

本章讲 timeBetweenEvictionRunsMillis/minEvictableIdleTimeMillis/numTestsPerEvictionRun。它们共同决定「空闲连接多久被清理一次、清理多少」。用 EvictionDemo 实测(间隔与阈值均设为 1000 ms):

BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:evict;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
ds.setMaxTotal(5);
ds.setMaxIdle(5);
ds.setMinIdle(0);
ds.setTimeBetweenEvictionRunsMillis(1000);
ds.setMinEvictableIdleTimeMillis(1000);
ds.setNumTestsPerEvictionRun(3);
ds.setTestWhileIdle(true);
ds.setValidationQuery("SELECT 1");
List<Connection> held = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    held.add(ds.getConnection());
}
System.out.println("借出 5 条 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
for (Connection c : held) {
    c.close();
}
System.out.println("归还 5 条 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
for (int t = 1; t <= 4; t++) {
    Thread.sleep(1000);
    System.out.println("第 " + t + " 秒 -> 空闲=" + ds.getNumIdle());
}

运行:

mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.EvictionDemo

实测回显:

借出 5 条 -> 活跃=5 空闲=0
归还 5 条 -> 活跃=0 空闲=5
第 1 秒 -> 空闲=5
第 2 秒 -> 空闲=2
第 3 秒 -> 空闲=0
第 4 秒 -> 空闲=0

逐项说明:

  • timeBetweenEvictionRunsMillis:本例 1000 ms,即每 1 秒跑一次回收线程。默认 -1 表示不跑回收线程,此时 testWhileIdle 与 minEvictableIdleTimeMillis 均无意义。
  • minEvictableIdleTimeMillis:本例 1000 ms,空闲超过 1 秒的连接可被回收。默认 1800000(30 分钟)。
  • numTestsPerEvictionRun:每轮检查 3 条。实测第 2 秒空闲从 5 降到 2,第 3 秒降到 0,正是「每轮清理 3 条 + 每秒一轮」叠加的结果。
  • 生产建议:timeBetweenEvictionRunsMillis 设 30000、minEvictableIdleTimeMillis 设 600000(10 分钟),二者乘积决定回收节奏。

八、连接生命周期与泄漏回收

本章讲 maxConnLifetimeMillis 与 removeAbandonedOnBorrow/removeAbandonedOnMaintenance/removeAbandonedTimeout/abandonedUsageTracking。

8.1 maxConnLifetimeMillis:连接最长存活

默认 -1(不限)。设为正数后,连接存活超过该毫秒数会在下一次校验时被判失效并销毁重建。用 MaxLifetimeDemo 实测(1500 ms):

ds.setMaxTotal(3);
ds.setMaxIdle(3);
ds.setMaxConnLifetimeMillis(1500);
ds.setTestWhileIdle(false);
Connection c1 = ds.getConnection();
Connection c2 = ds.getConnection();
Connection c3 = ds.getConnection();
System.out.println("借 3 条 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
c1.close();
c2.close();
c3.close();
System.out.println("归还 3 条 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
System.out.println("持有超过 maxConnLifetimeMillis=1500 毫秒的池中连接,等待 2000 毫秒后再次借出");
Thread.sleep(2000);
Connection fresh = ds.getConnection();
System.out.println("再次借出(过期连接在借出校验时被销毁重建)-> 活跃="
        + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
fresh.close();
System.out.println("归还后 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());

实测回显:

借 3 条 -> 活跃=3 空闲=0
归还 3 条 -> 活跃=0 空闲=3
持有超过 maxConnLifetimeMillis=1500 毫秒的池中连接,等待 2000 毫秒后再次借出
再次借出(过期连接在借出校验时被销毁重建)-> 活跃=1 空闲=0
归还后 -> 活跃=0 空闲=1

逐项说明:等待 2000 ms 后,池中 3 条空闲连接全部超过 1500 ms 寿命;下次借出时校验失败被销毁,numIdle 瞬间归 0,池只保留新借出的 1 条。作用是定期淘汰长连接,规避数据库端 wait_timeout 断连与中间件连接跟踪超时。

8.2 removeAbandoned:回收泄漏连接

程序忘了 close() 会导致连接被长期占用。开启 removeAbandonedOnBorrow 后,借出动作会顺带回收「借出超过 removeAbandonedTimeout 秒仍未归还」的连接。用 AbandonedDemo 实测:

ds.setMaxTotal(2);
ds.setMaxIdle(2);
ds.setMaxWaitMillis(500);
ds.setRemoveAbandonedOnBorrow(true);
ds.setRemoveAbandonedTimeout(2);
Connection leaked = ds.getConnection();
System.out.println("1) 借 1 条故意不归还 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
Connection c2 = ds.getConnection();
System.out.println("2) 再借 1 条(池已满)-> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());
long start = System.currentTimeMillis();
try {
    ds.getConnection();
} catch (SQLException ex) {
    System.out.println("3) 第 3 次借出等待 " + (System.currentTimeMillis() - start)
            + " ms 后超时: " + ex.getMessage());
}
System.out.println("4) 等待 2.5 秒,超过 removeAbandonedTimeout=2 秒后重新借出");
Thread.sleep(2500);
Connection c3 = ds.getConnection();
System.out.println("5) 回收生效,重新借到连接 -> 活跃=" + ds.getNumActive() + " 空闲=" + ds.getNumIdle());

实测回显:

1) 借 1 条故意不归还 -> 活跃=1 空闲=0
2) 再借 1 条(池已满)-> 活跃=2 空闲=0
3) 第 3 次借出等待 501 ms 后超时: Cannot get a connection, pool error Timeout waiting for idle object, maxWaitDuration=PT0.499957092S
4) 等待 2.5 秒,超过 removeAbandonedTimeout=2 秒后重新借出
5) 回收生效,重新借到连接 -> 活跃=1 空闲=1

逐项说明:

  • removeAbandonedOnBorrow:默认 false。本例设 true 后,第 5 步在借出时发现被遗弃连接已超时,将其强制回收,因此池满状态下又能借到连接。
  • removeAbandonedTimeout:默认 300 秒。本例设为 2 秒便于演示;生产建议 300 秒(5 分钟)左右。
  • removeAbandonedOnMaintenance:默认 false,开启后在回收线程运行时也执行遗弃检查,适合长时间不借连接的服务。
  • abandonedUsageTracking:默认 false,开启后会记录连接最近一次使用位置,便于定位泄漏代码。
  • ⚠️ 注意:开启遗弃回收有性能开销,且被强制回收的连接一旦返回可能破坏原事务。它是兜底手段,根治办法仍是代码里正确关闭连接(try-with-resources)。

九、默认连接属性与事务相关

本章讲 defaultAutoCommit/defaultReadOnly/defaultTransactionIsolation/enableAutoCommitOnReturn/rollbackOnReturn/cacheState,它们决定连接从池里取出时的初始状态。

参数默认值作用
defaultAutoCommitnull新连接的自动提交模式,null 跟随驱动
defaultReadOnlynull新连接是否只读,null 跟随驱动
defaultTransactionIsolation-1新连接事务隔离级别,-1 跟随驱动
defaultCatalog / defaultSchemanull新连接的默认库/模式
defaultQueryTimeoutnull语句默认查询超时秒数,null 不限
enableAutoCommitOnReturntrue归还时把连接恢复为自动提交
rollbackOnReturntrue归还时先回滚未提交事务,再恢复自动提交
cacheStatetrue缓存连接的可改状态,减少归还时的无谓调用

逐项说明:enableAutoCommitOnReturn 与 rollbackOnReturn 同为 true 时,归还连接会「先回滚、再置回自动提交」,确保下一位借用者拿到干净状态。若应用自己用 setAutoCommit(false) 做事务却忘了归还前提交/回滚,这两项默认值能兜住脏事务。cacheState=true 是性能优化:连接状态未被应用改动时跳过重复 setter 调用。

十、生产配置模板

面向「4C8G 应用节点、单库峰值 200 QPS、OLTP 短事务」场景的推荐参数(可直接粘贴):

BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:prod;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
ds.setMaxTotal(20);                                   // 连接总数上限,按并发与 DB 承载量评估
ds.setMaxIdle(20);                                    // 与 maxTotal 对齐,避免频繁销毁重建
ds.setMinIdle(5);                                     // 保底 5 条,抗突发
ds.setInitialSize(5);                                 // 启动预热 5 条
ds.setMaxWaitMillis(3000);                            // 取连接最多等 3 秒,避免线程无限挂起
ds.setValidationQuery("SELECT 1");
ds.setValidationQueryTimeout(3);
ds.setTestOnBorrow(true);
ds.setTestWhileIdle(true);
ds.setTimeBetweenEvictionRunsMillis(30000);           // 每 30 秒跑一次回收
ds.setMinEvictableIdleTimeMillis(600000);             // 空闲 10 分钟才回收
ds.setNumTestsPerEvictionRun(5);
ds.setMaxConnLifetimeMillis(1800000);                 // 连接最长存活 30 分钟
ds.setRemoveAbandonedOnBorrow(true);
ds.setRemoveAbandonedOnMaintenance(true);
ds.setRemoveAbandonedTimeout(300);                    // 遗弃判定 5 分钟
ds.setAbandonedUsageTracking(true);

关键取值逻辑:maxTotal 要看数据库 max_connections 与实例数分摊(如 DB 上限 500、10 个实例,单实例 maxTotal 不宜超过 40);maxWaitMillis 必须给有限值防线程堆积;minEvictableIdleTimeMillis 要大于 timeBetweenEvictionRunsMillis 才有回收意义;maxConnLifetimeMillis 要小于数据库端 wait_timeout,先于服务端断连淘汰。

十一、参考链接

  1. Apache Commons DBCP 官方主页:https://commons.apache.org/proper/commons-dbcp/
  2. 2.14.0 javadoc(BasicDataSource 各参数权威说明):https://commons.apache.org/proper/commons-dbcp/apidocs/org/apache/commons/dbcp2/BasicDataSource.html
  3. DBCP 配置参数官方文档:https://commons.apache.org/proper/commons-dbcp/configuration.html
  4. Maven 中央仓库 2.14.0(pom 与 jar):https://repo1.maven.org/maven2/org/apache/commons/commons-dbcp2/2.14.0/
  5. commons-pool2(池实现来源库)文档:https://commons.apache.org/proper/commons-pool/
  6. H2 Database 官网(示例所用内嵌数据库):https://h2database.com/