Apache Commons DBCP 配置参数详解
逐项详解 Apache Commons DBCP 2.14.0 的 BasicDataSource 配置参数:容量上限、等待超时、有效性检测、空闲回收、连接生命周期与泄漏回收、事务默认属性,默认值均取自实测运行回显,每项配可运行示例。
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 DBCP2 | Apache,本文主角 | 纯 Java、参数直观、与 Tomcat/Spring 兼容广 | 传统项目、需轻量显式配置 |
| HikariCP | 社区 | 极致性能、极少参数、启动即调优 | 高并发新项目首选 |
| C3P0 | 社区 | 老牌、配置项多、曾与 Hibernate 绑定 | 存量 Hibernate 项目 |
| Druid | 阿里 | 自带监控台、SQL 防火墙、统计丰富 | 国内运维监控要求高 |
| Tomcat JDBC Pool | Apache Tomcat | DBCP 的替代品、异步取连接 | 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 驱动决定自动提交)。
三、参数总览
下表汇总各参数默认值与含义,后续章节逐类展开:
| 参数 | 默认值(实测) | 含义 |
|---|---|---|
| maxTotal | 8 | 池中连接(含借出与空闲)总数上限 |
| maxIdle | 8 | 允许保留的最大空闲连接数 |
| minIdle | 0 | 保底空闲连接数,低于它时会补建 |
| initialSize | 0 | 首次建池时预建的连接数 |
| maxWaitMillis | -1 | 取连接最长等待毫秒数,-1 为无限 |
| validationQuery | null | 校验连接有效性的 SQL,null 表示不校验 |
| validationQueryTimeout | -1 | 校验 SQL 超时秒数,-1 为不限制 |
| testOnCreate | false | 新建连接后是否校验 |
| testOnBorrow | true | 借出连接前是否校验 |
| testOnReturn | false | 归还连接时是否校验 |
| testWhileIdle | false | 空闲连接被回收线程检查时是否校验 |
| timeBetweenEvictionRunsMillis | -1 | 回收线程运行间隔毫秒数,-1 为不运行 |
| numTestsPerEvictionRun | 3 | 每轮回收检查的连接数 |
| minEvictableIdleTimeMillis | 1800000 | 空闲超过该毫秒数即可被回收(默认 30 分钟) |
| maxConnLifetimeMillis | -1 | 连接最长存活毫秒数,-1 为不限 |
| removeAbandonedOnBorrow | false | 借出时是否回收超时未归还的连接 |
| removeAbandonedOnMaintenance | false | 维护时是否回收超时未归还的连接 |
| removeAbandonedTimeout | 300 | 判定「被遗弃」的空闲秒数阈值 |
| lifo | true | 空闲连接是否后进先出 |
关键差异与选型: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。它们决定「怎么确认从池里拿到的连接还活着」。
| 参数 | 默认值 | 作用 |
|---|---|---|
| validationQuery | null | 校验用 SQL,通常写 SELECT 1;为 null 时不做校验 |
| validationQueryTimeout | -1 | 校验 SQL 的超时秒数,-1 为不限制 |
| testOnCreate | false | 连接刚创建时校验一次 |
| testOnBorrow | true | 借出时校验(DBCP2 默认开启) |
| testOnReturn | false | 归还时校验 |
| testWhileIdle | false | 回收线程检查空闲连接时校验 |
实测要点(来自第二、五章运行):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,它们决定连接从池里取出时的初始状态。
| 参数 | 默认值 | 作用 |
|---|---|---|
| defaultAutoCommit | null | 新连接的自动提交模式,null 跟随驱动 |
| defaultReadOnly | null | 新连接是否只读,null 跟随驱动 |
| defaultTransactionIsolation | -1 | 新连接事务隔离级别,-1 跟随驱动 |
| defaultCatalog / defaultSchema | null | 新连接的默认库/模式 |
| defaultQueryTimeout | null | 语句默认查询超时秒数,null 不限 |
| enableAutoCommitOnReturn | true | 归还时把连接恢复为自动提交 |
| rollbackOnReturn | true | 归还时先回滚未提交事务,再恢复自动提交 |
| cacheState | true | 缓存连接的可改状态,减少归还时的无谓调用 |
逐项说明: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,先于服务端断连淘汰。
十一、参考链接
- Apache Commons DBCP 官方主页:https://commons.apache.org/proper/commons-dbcp/
- 2.14.0 javadoc(
BasicDataSource各参数权威说明):https://commons.apache.org/proper/commons-dbcp/apidocs/org/apache/commons/dbcp2/BasicDataSource.html - DBCP 配置参数官方文档:https://commons.apache.org/proper/commons-dbcp/configuration.html
- Maven 中央仓库 2.14.0(pom 与 jar):https://repo1.maven.org/maven2/org/apache/commons/commons-dbcp2/2.14.0/
- commons-pool2(池实现来源库)文档:https://commons.apache.org/proper/commons-pool/
- H2 Database 官网(示例所用内嵌数据库):https://h2database.com/