在日常 Java 后端开发中,我们几乎每天都在使用 Hibernate / JPA / MyBatis ,也都会配置 HikariCP、Druid 这样的连接池。
但很多人其实只停留在「会配参数」层面,对于下面这些问题并不真正清楚:
-
什么是数据库连接池?
-
Hibernate 是在什么时候去拿数据库连接的?
-
拿到连接之后,什么时候归还?
-
使用 HikariDataSource 和 DruidDataSource,Hibernate 的行为有没有区别?
-
Hikari 和 Druid 的差异,是不是只是"性能不一样"?
这篇文章尝试从工程视角把这些问题一次性说清楚。
一、什么是数据库连接池?
1. 为什么需要连接池?
数据库连接是一种非常昂贵的资源:
-
建立连接需要 TCP 握手、认证、会话初始化
-
销毁连接需要数据库回收资源
-
高并发下频繁创建 / 销毁连接,会严重拖慢系统性能
如果每次 SQL 执行都这样:
Connection conn = DriverManager.getConnection(...);
// execute sql
conn.close();
系统在并发稍微高一点时就会崩溃。
2. 连接池的本质是什么?
一句话概括:
连接池 = 预先创建并维护一批数据库连接,统一管理、重复使用
核心思想只有两个字:
借 / 还
而不是:
创建 / 销毁
3. 一个抽象模型
应用线程
↓ borrow
连接池(空闲连接 / 活跃连接)
↓
数据库
连接池负责:
-
连接数量控制
-
连接复用
-
超时检测
-
连接回收
-
泄露防护
二、Hibernate 是什么时候去拿数据库连接的?
这是一个非常容易被误解的问题。
❌ 常见误解
Hibernate 启动时就会占用很多数据库连接
✅ 实际情况
Hibernate 是"按需获取连接"的
1. Hibernate 不会在启动时主动占用连接
Hibernate 启动阶段主要做的是:
-
解析实体映射
-
初始化 SessionFactory
-
准备缓存、拦截器等组件
不会长期持有数据库连接。
2. Hibernate 真正获取连接的时机
Hibernate 只有在以下场景,才会向 DataSource 请求连接:
场景一:真正执行 SQL
例如:
session.save(entity);
session.createQuery("from User").list();
session.flush();
底层调用链大致是:
Session
→ JDBC Coordinator
→ DataSource.getConnection()
场景二:事务中需要数据库交互
@Transactional
public void doBusiness() {
dao.save();
dao.update();
}
-
开启事务时 不一定立刻拿连接
-
但在第一次真正执行 SQL 时,一定会拿
3. 一个重要结论
Hibernate 本身不"持有连接",它只是一个按需使用连接的协调者
三、Hibernate 拿到连接后,什么时候归还?
这是理解连接池行为的关键。
1. 最常见的情况(无显式事务)
执行 SQL
→ 从连接池借连接
→ 执行完成
→ 归还连接池
一次业务方法中,可能会:
-
多次借连接
-
多次归还连接
2. 在 Spring 事务中的行为
public void serviceMethod() {
dao.save();
dao.update();
}
xml 配置
<property name="transactionAttributes">
<props>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="copy*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="init*">PROPAGATION_REQUIRED</prop>
<prop key="clone*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
行为是:
-
第一次 SQL:从连接池获取连接
-
事务期间:复用同一个连接
-
事务提交 / 回滚:
-
调用
connection.close() -
实际行为是:归还连接到连接池
-
3. 一个非常关键的点
connection.close();
❗ 并不等于真的关闭数据库连接
而是:
把连接"还给连接池"
真正的物理关闭,只会在:
-
连接池缩容
-
连接失效
-
连接池关闭
时发生。
4. 那什么是"连接泄露"?
常见原因包括:
-
开启事务但没有提交 / 回滚
-
手写 JDBC 忘记关闭连接
-
长事务 + 慢 SQL
-
线程池任务未正确结束
这也是为什么连接池需要:
-
超时检测
-
abandoned 回收
-
泄露监控
四、DataSource 在 Hibernate 中扮演什么角色?
Hibernate 并不关心你用的是:
-
Hikari
-
Druid
-
C3P0
-
Tomcat Pool
它只关心一件事:
javax.sql.DataSource
Hibernate 调用的永远是:
DataSource.getConnection();
至于连接怎么创建、怎么复用、怎么监控,完全由连接池实现决定。
设计理念:
做最少的事情,跑最快的路径
特点:
-
极简实现
-
极短代码路径
-
极低 GC 压力
优点:
-
连接获取速度快
-
吞吐量高
-
延迟低
缺点:
-
监控能力弱
-
SQL 可视化能力几乎没有
4. Druid:运维和诊断优先
设计理念:
提供尽可能丰富的运行期观测能力
特点:
-
内置 SQL 监控
-
Web 管理页面
-
慢 SQL 统计
-
泄露分析
优点:
-
非常适合排查问题
-
运维友好
缺点:
-
实现复杂
-
相比 Hikari 有一定性能开销(但通常可接受)
5. EKP提供启动后选择使用Hikari 或者Druid

修改后保存,重启可以根据可配置重新加载数据源。
public void afterPropertiesSet() {
String datasourceName = ResourceUtil.getKmssConfigString("kmss.jdbc.datasource");
DataSource datasource = defaultDataSource;
if (StringUtil.isNotNull(datasourceName)) {
datasource = (DataSource) context.getBean(datasourceName);
}
// 使用代理数据源(增加SQL监控)
this.setTargetDataSource(new MonitoringDataSource(datasource, sqlMonitor));
super.afterPropertiesSet();
}
6. 对比总结
| 维度 | HikariCP | Druid |
|---|---|---|
| 设计目标 | 极致性能 | 可观测性 |
| 吞吐量 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| SQL 监控 | ❌ | ✅ |
| 运维排障 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 生产稳定性 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
六、工程实践中的选择建议
-
高并发、性能敏感系统:优先 Hikari
-
复杂业务、SQL 问题多、需要排障:优先 Druid
-
统一监控体系(JMX / APM)成熟:Hikari 完全够用
-
中小团队、问题定位依赖连接池:Druid 更省心
七、结语
可以用这样一句话总结全文:
Hibernate 决定"什么时候用连接",连接池决定"连接怎么被管理"
理解这一点之后,你会发现:
-
切换 Hikari 或 Druid
-
调整连接池参数
-
排查连接泄露问题
都会变得清晰很多。