Apache Commons DBCP 快速入门与集成示例
Apache Commons DBCP 2.14.0 快速入门与集成示例:从依赖引入、BasicDataSource 核心 API,到建表、批量插入、事务提交回滚、查询的完整可跑程序,并给出 properties 外置配置集成现有工程示例。
Apache Commons DBCP 快速入门与集成示例
目标:用最短路径把 Apache Commons DBCP 2.14.0 跑起来——引入依赖、创建
BasicDataSource、完成建表/批量插入/事务/查询,并集成进现有工程。全部代码与回显已在 Ubuntu 22.04 / CentOS 8(JDK 17 + Maven)实测通过。
一、环境信息
本章固定示例环境与依赖坐标。示例用 H2 内嵌数据库作为真实 JDBC 数据源,无需额外安装数据库服务即可完整演示连接池行为。
java -version 2>&1 | head -1
mvn -v 2>&1 | head -1
实测回显(Ubuntu 22.04):
openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
实测回显(CentOS 8,需先设 JAVA_HOME):
openjdk version "17.0.1" 2021-10-19 LTS
Apache Maven 3.5.4 (Red Hat 3.5.4-5)
本文统一使用以下坐标(版本为 2.14.0 官方 pom 实测值):
org.apache.commons:commons-dbcp2:2.14.0
com.h2database:h2:2.5.252
💡 技术拓展:Apache Commons DBCP 只是「数据库连接池」大家庭中的一员。Java 连接池复用物理数据库连接、降低握手与鉴权开销,业界常见实现对比:
| 连接池 | 出身 | 特点 | 适用场景 |
|---|---|---|---|
| Commons DBCP2 | Apache,本文主角 | 纯 Java、参数直观、与 Tomcat/Spring 兼容广 | 传统项目、需轻量显式配置 |
| HikariCP | 社区 | 极致性能、极少参数 | 高并发新项目首选 |
| C3P0 | 社区 | 老牌、配置项多 | 存量 Hibernate 项目 |
| Druid | 阿里 | 自带监控台、SQL 防火墙 | 国内运维监控要求高 |
| Tomcat JDBC Pool | Apache Tomcat | DBCP 的替代品、异步取连接 | Tomcat 应用内 |
本文用 Commons DBCP2 演示。选它的理由:BasicDataSource 一个类就完成「数据源 + 连接池」两件事,API 与 JDBC 原生写法几乎无差别,学习成本最低;迁移到 HikariCP 时,代码只需把数据源换成 HikariDataSource,SQL 与事务逻辑不变。
二、前置条件
本章准备基础运行环境。需要 JDK 17 与 Maven;若尚未安装,按对应系统执行:
sudo apt install -y openjdk-17-jdk maven # Ubuntu 22.04
实测回显(输出节选,实测首尾):
Setting up maven (3.6.3-5) ...
Setting up openjdk-17-jdk:amd64 (17.0.20.1+1-1~22.04) ...
CentOS 8 换用 dnf install -y java-17-openjdk-devel maven,并注意系统默认 JDK 可能是 8,需要 export JAVA_HOME=/usr/lib/jvm/java-17-openjdk。创建工程目录:
mkdir -p /opt/dbcp-demo/src/main/java/net/quzuo/demo
cd /opt/dbcp-demo
该命令无输出。
三、依赖引入
本章把 DBCP 与 H2 驱动加进 Maven 工程。pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.quzuo.demo</groupId>
<artifactId>dbcp-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
逐项说明:前两个字段指定编译目标为 Java 17;project.build.sourceEncoding 保证源码按 UTF-8 读;commons-dbcp2 是库本体、传递依赖自动带入;h2 仅作示例数据源;exec-maven-plugin 让示例可以用 mvn exec:java 一键运行。
验证依赖解析:
mvn -B dependency:tree
实测回显(输出节选,实测首尾):
[INFO] +- org.apache.commons:commons-dbcp2:jar:2.14.0:compile
[INFO] | +- org.apache.commons:commons-pool2:jar:2.13.0:compile
[INFO] | +- commons-logging:commons-logging:jar:1.3.5:compile
[INFO] | \- jakarta.transaction:jakarta.transaction-api:jar:1.3.3:compile
[INFO] \- com.h2database:h2:jar:2.5.252:compile
[INFO] BUILD SUCCESS
关键解读:只声明了 commons-dbcp2 与 h2,但 Maven 自动带入了 commons-pool2 2.13.0(池引擎)、commons-logging 1.3.5(日志)、jakarta.transaction-api 1.3.3(事务集成)。用别的构建工具(Gradle 等)时要保证这三个依赖也在运行期类路径上。
四、核心 API 概览
本章说明示例用到的 API。DBCP 的用法极简:把 BasicDataSource 当作 JDBC 的 DataSource 用,其余 SQL 代码与原生 JDBC 完全一致。
| 类/接口 | 包 | 作用 |
|---|---|---|
| BasicDataSource | org.apache.commons.dbcp2 | 主入口,既是 DataSource 又是连接池配置载体 |
| PoolingDataSource | org.apache.commons.dbcp2 | 更底层的池化数据源,需手工装配 |
| BasicDataSourceFactory | org.apache.commons.dbcp2 | 从 Properties 创建数据源,适合外置配置 |
| javax.sql.DataSource | JDK | 标准接口,便于后续替换连接池实现 |
| java.sql.Connection | JDK | 从池中取得的连接,close() 即归还 |
最小用法只有四行:
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:quickstart;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
之后 ds.getConnection() 即从池中取连接,conn.close() 即归还。关键是始终用 try-with-resources,保证异常路径也能归还连接。
五、完整示例:建表、批量插入、事务、查询
本章给出一个从建表到查询的完整可跑示例 QuickStart,覆盖日常最常用的四类操作。
package net.quzuo.demo;
import org.apache.commons.dbcp2.BasicDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class QuickStart {
public static void main(String[] args) throws Exception {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:quickstart;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
ds.setMaxTotal(8);
ds.setMaxIdle(8);
ds.setMinIdle(2);
ds.setMaxWaitMillis(2000);
ds.setValidationQuery("SELECT 1");
ds.setTestOnBorrow(true);
ds.setTestWhileIdle(true);
ds.setTimeBetweenEvictionRunsMillis(30000);
System.out.println("== 1. 建表 ==");
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(
"CREATE TABLE dept (id INT PRIMARY KEY, name VARCHAR(32), city VARCHAR(32))")) {
ps.execute();
System.out.println("dept 表已创建");
}
System.out.println("== 2. 批量插入(PreparedStatement + JDBC 批处理) ==");
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO dept (id, name, city) VALUES (?, ?, ?)")) {
String[][] rows = {
{"1", "研发中心", "北京"},
{"2", "数据中心", "上海"},
{"3", "运维中心", "深圳"}};
for (String[] r : rows) {
ps.setInt(1, Integer.parseInt(r[0]));
ps.setString(2, r[1]);
ps.setString(3, r[2]);
ps.addBatch();
}
int[] affected = ps.executeBatch();
System.out.println("批处理影响行数: " + affected.length);
}
System.out.println("== 3. 事务:转账 + 回滚示例 ==");
try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO dept (id, name, city) VALUES (4, '新增部门', '杭州')")) {
ps.executeUpdate();
}
conn.commit();
System.out.println("提交一行 id=4");
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO dept (id, name, city) VALUES (5, '待回滚部门', '成都')")) {
ps.executeUpdate();
}
conn.rollback();
System.out.println("回滚一行 id=5");
}
System.out.println("== 4. 查询全部记录 ==");
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT id, name, city FROM dept ORDER BY id");
ResultSet rs = ps.executeQuery()) {
List<String> lines = new ArrayList<>();
while (rs.next()) {
lines.add(rs.getInt("id") + " | " + rs.getString("name") + " | " + rs.getString("city"));
}
System.out.println("总记录数: " + lines.size());
for (String line : lines) {
System.out.println(" " + line);
}
}
System.out.println("== 5. 池状态 ==");
System.out.println("numActive=" + ds.getNumActive() + " numIdle=" + ds.getNumIdle());
ds.close();
System.out.println("完成");
}
}
运行:
mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.QuickStart
实测回显:
== 1. 建表 ==
dept 表已创建
== 2. 批量插入(PreparedStatement + JDBC 批处理) ==
批处理影响行数: 3
== 3. 事务:转账 + 回滚示例 ==
提交一行 id=4
回滚一行 id=5
== 4. 查询全部记录 ==
总记录数: 4
1 | 研发中心 | 北京
2 | 数据中心 | 上海
3 | 运维中心 | 深圳
4 | 新增部门 | 杭州
== 5. 池状态 ==
numActive=0 numIdle=1
完成
逐段解读:
- 建表:
ds.getConnection()首次调用时才真正建池(懒创建),这一步顺带完成预热。 - 批量插入:
addBatch()/executeBatch()一次网络往返插入 3 行,批处理影响行数: 3与输入一致。 - 事务:
setAutoCommit(false)后手动commit()与rollback()。实测可见 id=4 被提交保留、id=5 被回滚丢弃——查询结果里只有 4 条记录、没有 id=5,证明回滚生效。 - 查询:
PreparedStatement+ResultSet,中文内容(研发中心/北京等)正确回显,说明连接与驱动字符集一致。 - 池状态:全部
try-with-resources结束后numActive=0,连接已归还。
六、事务处理示例
本章把事务部分单独拆出,说明连接池下事务的正确写法与常见错误。上面完整示例已包含提交与回滚,这里补充一个独立的事务方法,便于复用:
private static void transfer(BasicDataSource ds, int id, int delta) throws Exception {
try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(
"UPDATE account SET balance = balance + ? WHERE id = ?")) {
ps.setInt(1, delta);
ps.setInt(2, id);
ps.executeUpdate();
}
conn.commit();
}
}
三点说明:连接从池里取出后 setAutoCommit(false) 开启事务,正常路径 commit()、异常时由 try-with-resources 触发 close();DBCP 默认 rollbackOnReturn=true,归还时会自动回滚未提交事务,因此即使漏写 rollback 也不会把脏事务带给下一位借用者;但绝不要把连接存取到线程外长期持有,用完必须归还。
实测佐证(来自第五章示例的事务段):
提交后保留 id=4;回滚后丢弃 id=5;最终查询总记录数: 4
七、集成到现有工程
本章给出把连接参数外置到 properties 文件的做法,便于不改代码切换环境。写配置文件 dbcp.properties:
driverClassName=org.h2.Driver
url=jdbc:h2:mem:factory;DB_CLOSE_DELAY=-1
username=sa
password=
maxTotal=6
maxIdle=6
minIdle=1
maxWaitMillis=2000
validationQuery=SELECT 1
读取并创建数据源的程序 FactoryDemo:
package net.quzuo.demo;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class FactoryDemo {
public static void main(String[] args) throws Exception {
Properties props = new Properties();
try (InputStream in = new FileInputStream("dbcp.properties")) {
props.load(in);
}
BasicDataSource ds = BasicDataSourceFactory.createDataSource(props);
System.out.println("从 dbcp.properties 读取的参数:");
System.out.println(" url = " + ds.getUrl());
System.out.println(" maxTotal = " + ds.getMaxTotal());
System.out.println(" maxIdle = " + ds.getMaxIdle());
System.out.println(" minIdle = " + ds.getMinIdle());
System.out.println(" maxWaitMillis = " + ds.getMaxWaitMillis());
System.out.println(" validationQuery = " + ds.getValidationQuery());
try (Connection conn = ds.getConnection();
Statement st = conn.createStatement()) {
st.execute("CREATE TABLE demo (id INT PRIMARY KEY, v VARCHAR(32))");
st.execute("INSERT INTO demo VALUES (1,'factory')");
try (ResultSet rs = st.executeQuery("SELECT v FROM demo WHERE id=1")) {
rs.next();
System.out.println("查询结果: " + rs.getString(1));
}
}
System.out.println("numActive=" + ds.getNumActive() + " numIdle=" + ds.getNumIdle());
ds.close();
}
}
运行(须在 dbcp.properties 所在目录执行):
mvn -q compile exec:java -Dexec.mainClass=net.quzuo.demo.FactoryDemo
实测回显:
从 dbcp.properties 读取的参数:
url = jdbc:h2:mem:factory;DB_CLOSE_DELAY=-1
maxTotal = 6
maxIdle = 6
minIdle = 1
maxWaitMillis = 2000
validationQuery = SELECT 1
查询结果: factory
numActive=0 numIdle=1
说明:BasicDataSourceFactory.createDataSource(Properties) 会把键名与 BasicDataSource 的 setter 逐一对齐(maxTotal → setMaxTotal,maxWaitMillis → setMaxWaitMillis),因此配置项与代码设置的参数名完全一致。集成到 Web 工程时,也可以把同样的键写进 Servlet 应用服务器的 JNDI 数据源配置,或交给 Spring 的 BasicDataSource Bean 绑定。
八、注意事项
- 连接必须归还:一律使用
try-with-resources;池满时取连接会抛SQLException(默认maxWaitMillis=-1则无限等待,生产必须显式设有限值)。 - 池是懒创建的:第一次
getConnection()才建池,需要启动预热时显式取一次连接再关闭。 - 参数默认值别想当然:
testOnBorrow=true、maxTotal=8、timeBetweenEvictionRunsMillis=-1(回收线程默认不跑),具体见《Apache Commons DBCP 配置参数详解》。 - 版本要核对:多版本共存时确认类路径里的主 jar 版本,可用
BasicDataSource.class.getPackage().getImplementationVersion()打印。 - 中文回显乱码:设置
LANG=C.UTF-8或运行时加-Dfile.encoding=UTF-8。
九、参考链接
- Apache Commons DBCP 官方主页:https://commons.apache.org/proper/commons-dbcp/
- DBCP 快速入门与配置说明:https://commons.apache.org/proper/commons-dbcp/configuration.html
- 2.14.0 javadoc(BasicDataSource / BasicDataSourceFactory):https://commons.apache.org/proper/commons-dbcp/apidocs/
- Maven 中央仓库 2.14.0:https://repo1.maven.org/maven2/org/apache/commons/commons-dbcp2/2.14.0/
- H2 Database 官网(示例所用内嵌数据库):https://h2database.com/
- commons-pool2(池实现来源库):https://commons.apache.org/proper/commons-pool/
- Java SE 17 JDBC 教程(PreparedStatement 与事务):https://docs.oracle.com/javase/tutorial/jdbc/