手写 Java 连接池:实践心得
SanguiCP
,并将代码开源在 GitHub 上。本文将梳理我的学习过程、核心实现思路以及关键代码片段,帮助你快速理解连接池原理与装饰器模式的实践。
一、为何需要连接池?
在 Java 应用中,通过 DriverManager.getConnection(url, user, password)
建立新连接时,JVM 与数据库进程需完成 TCP 连接、身份认证等一系列耗时操作。使用调试模式,你会发现在执行该行代码时,程序会出现明显卡顿。若请求并发量较高,无节制地新建连接,不仅浪费资源,还可能导致数据库服务压力过大甚至宕机。
面临的两大问题:
性能低下:每次新建连接都要完成网络握手与认证。
资源受限:连接数不受控,超过数据库承载上限时,会触发连接拒绝或服务崩溃。
为此,连接池(Connection Pool)成为现代后端开发的标配:
预创建 & 缓存:应用启动时,提前构建一定数量的连接实例,存放于内存集合中。用户请求时,直接获取复用,无需重复创建。
限量管理:通过最大连接数限制并发连接,超出时可阻塞等待或抛出超时异常,避免数据库超载。
二、缓存策略与设计模式
缓存策略(Cache)
核心思想:提前创建、存放内存、按需取用。常见应用包括:
Java 字符串常量池、整型常量池
线程池
Redis、Ehcache 等第三方缓存组件
通过缓存机制,可以大幅减少对象构建开销,提高程序整体性能。
装饰器模式(Decorator)
在 SanguiCP
实现中,我对 java.sql.Connection
做了包装:
被装饰者:
Connection
原始实现来自DriverManager
。装饰对象:
SanguiConnection
实现了Connection
接口,并持有底层连接实例及连接池引用。关键改造:重写
close()
方法,当用户关闭连接时,实际将装饰对象归还到池中,而非真正关闭底层连接;其他方法均委托给原始连接执行。
此方式无需修改原有 Connection
类,即可在运行时动态增强功能,符合开闭原则。
三、项目结构与配置
├── pom.xml
├── src
│ ├── main
│ │ ├── java/com/sangui/sanguicp
│ │ │ ├── SanguiDataSource.java // 数据源核心实现
│ │ │ └── SanguiConnection.java // 装饰器连接实现
│ │ └── resources
│ │ └── sanguicp.properties // 配置示例
│ └── test
│ └── java/com/sangui/sanguicp/test
│ └── MyDataSourceTest.java // 单元测试示例
└── README.md
配置示例(sanguicp.properties
)
# 驱动类名
driver=com.mysql.cj.jdbc.Driver
# 数据库连接URL
url=jdbc:mysql://localhost:3306/mybatis
user=root
password=333
# 连接池最大连接数
maxTotal=3
# 单次等待间隔(毫秒)
waitTime=1000
# 最大累计等待时长(毫秒)
maxWaitTime=10000
四、关键代码解读
1. SanguiDataSource.java
public class SanguiDataSource implements DataSource {
private final LinkedList<Connection> pool = new LinkedList<>();
public SanguiDataSource() {
// 读取配置,预创建 maxTotal 个连接
for (int i = 0; i < maxTotal; i++) {
Connection raw = DriverManager.getConnection(url, user, password);
pool.add(new SanguiConnection(raw, pool));
}
}
public Connection getConnection() throws SQLException {
if (!pool.isEmpty()) {
return pool.removeFirst();
}
// 池空则等待或超时抛出
Thread.sleep(waitTime);
return getConnection();
}
// 其他 DataSource 接口方法留空或返回默认,实现可拓展
}
要点:
使用
LinkedList
支撑队列操作,支持高效的头部增删。递归调用
getConnection()
实现重复等待逻辑。超过
maxWaitTime
则抛出SQLException
,提醒使用者释放或检查连接。
2. SanguiConnection.java
public class SanguiConnection implements Connection {
private final Connection delegate;
private final LinkedList<Connection> pool;
public SanguiConnection(Connection delegate, LinkedList<Connection> pool) {
this.delegate = delegate;
this.pool = pool;
}
public void close() throws SQLException {
// 回收到池中,而非关闭底层连接
pool.add(this);
}
// 其他方法均委托给 delegate
public Statement createStatement() throws SQLException { return delegate.createStatement(); }
// ...
}
要点:
装饰器模式的经典实现:在
close()
中插入自定义逻辑,其他方法不做修改。连接被归还后,可再次通过
getConnection()
获取,达到复用效果。
五、使用示例
public void testSanguiCP() throws Exception {
SanguiDataSource ds = new SanguiDataSource();
// 连续获取三个连接
Connection c1 = ds.getConnection();
Connection c2 = ds.getConnection();
Connection c3 = ds.getConnection();
System.out.println(c1 + ", " + c2 + ", " + c3);
// 超出 maxTotal 将进入等待
// Connection c4 = ds.getConnection();
// 归还连接
c3.close();
Connection c4 = ds.getConnection();
System.out.println(c4); // 应与 c3 实例相同
}
六、总结与后续
通过此项目,我深入理解了:
数据库连接的性能成本:连接创建与认证的代价为何如此高。
缓存技术思维:提前构建、内存复用、动静分离的基本策略。
装饰器设计模式:在不侵入原有类的前提下,通过包装增强功能。
在后续迭代中,可考虑:
支持连接失效检测与自动重连
增加监控统计(活跃连接数、等待时长分布等)
提供多种回收策略(FIFO、LIFO、超时释放)
欢迎访问并关注我的 GitHub 仓库:
https://github.com/WuSangui571/sanguicp
git@github.com:WuSangui571/sanguicp.git
- 微信
- 赶快加我聊天吧
- 赶快加我聊天吧