Apache Commons DBCP 快速入门与集成示例

Apache Commons DBCP 2.14.0 快速入门与集成示例:从依赖引入、BasicDataSource 核心 API,到建表、批量插入、事务提交回滚、查询的完整可跑程序,并给出 properties 外置配置集成现有工程示例。

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)实测通过。

一、环境信息

本章固定示例环境与依赖坐标。示例用 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 DBCP2Apache,本文主角纯 Java、参数直观、与 Tomcat/Spring 兼容广传统项目、需轻量显式配置
HikariCP社区极致性能、极少参数高并发新项目首选
C3P0社区老牌、配置项多存量 Hibernate 项目
Druid阿里自带监控台、SQL 防火墙国内运维监控要求高
Tomcat JDBC PoolApache TomcatDBCP 的替代品、异步取连接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 完全一致。

类/接口包作用
BasicDataSourceorg.apache.commons.dbcp2主入口,既是 DataSource 又是连接池配置载体
PoolingDataSourceorg.apache.commons.dbcp2更底层的池化数据源,需手工装配
BasicDataSourceFactoryorg.apache.commons.dbcp2从 Properties 创建数据源,适合外置配置
javax.sql.DataSourceJDK标准接口,便于后续替换连接池实现
java.sql.ConnectionJDK从池中取得的连接,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 绑定。

八、注意事项

  1. 连接必须归还:一律使用 try-with-resources;池满时取连接会抛 SQLException(默认 maxWaitMillis=-1 则无限等待,生产必须显式设有限值)。
  2. 池是懒创建的:第一次 getConnection() 才建池,需要启动预热时显式取一次连接再关闭。
  3. 参数默认值别想当然:testOnBorrow=true、maxTotal=8、timeBetweenEvictionRunsMillis=-1(回收线程默认不跑),具体见《Apache Commons DBCP 配置参数详解》。
  4. 版本要核对:多版本共存时确认类路径里的主 jar 版本,可用 BasicDataSource.class.getPackage().getImplementationVersion() 打印。
  5. 中文回显乱码:设置 LANG=C.UTF-8 或运行时加 -Dfile.encoding=UTF-8。

九、参考链接

  1. Apache Commons DBCP 官方主页:https://commons.apache.org/proper/commons-dbcp/
  2. DBCP 快速入门与配置说明:https://commons.apache.org/proper/commons-dbcp/configuration.html
  3. 2.14.0 javadoc(BasicDataSource / BasicDataSourceFactory):https://commons.apache.org/proper/commons-dbcp/apidocs/
  4. Maven 中央仓库 2.14.0:https://repo1.maven.org/maven2/org/apache/commons/commons-dbcp2/2.14.0/
  5. H2 Database 官网(示例所用内嵌数据库):https://h2database.com/
  6. commons-pool2(池实现来源库):https://commons.apache.org/proper/commons-pool/
  7. Java SE 17 JDBC 教程(PreparedStatement 与事务):https://docs.oracle.com/javase/tutorial/jdbc/