高性能 · 可监控 · 安全可靠 —— 基于 Filter 链与 Proxy 代理的智能连接池
一、为什么需要连接池?—— 问题驱动理解
数据库连接(Connection)创建过程涉及:
- TCP 握手
- 用户认证
- 会话初始化
每次请求都新建连接 = 每次开门都要造一把新钥匙,成本极高!
✅ 连接池的作用:
提前创建一批“可复用”的连接,像“共享单车”一样按需借用、归还,大幅提升系统吞吐量和响应速度。
二、Druid 是什么?
Druid 不仅是一个连接池,更是一个 可插拔的 JDBC 增强框架。其核心设计思想是:
通过代理(Proxy)包装原始 JDBC 对象,再通过过滤器链(Filter Chain)拦截所有操作,实现监控、审计、日志等非业务功能。
这使得 Druid 在不侵入业务代码的前提下,提供强大的 AOP 能力。
三、核心原理详解(含 Filter 与 Proxy)
3.1 整体架构:Proxy + Filter Chain 模式
Druid 对所有 JDBC 核心对象(Connection, Statement, ResultSet 等)都进行了 动态代理封装,并在方法调用前后插入 Filter 链。
+---------------------+
| Application Code |
+----------+----------+
|
v
+----------+----------+
| Druid DataSource | ← 返回的是 ProxyConnection
+----------+----------+
|
v
+----------+----------+
| ProxyConnection | ← 动态代理对象
+----------+----------+
|
v
+----------+----------+
| Filter Chain | ← 责任链模式:StatFilter → WallFilter → LogFilter → ...
+----------+----------+
|
v
+----------+----------+
| Physical Connection | ← 真实的数据库连接(如 MySQL Connection)
+---------------------+
3.2 Filter 链执行流程(以执行 SQL 为例)
当应用执行 statement.executeQuery(sql) 时:
sequenceDiagram
participant App as 应用代码
participant ProxyStmt as ProxyPreparedStatement
participant StatFilter
participant WallFilter
participant LogFilter
participant RealStmt as 物理 PreparedStatement
App->>ProxyStmt: executeQuery(sql)
ProxyStmt->>StatFilter: preExecute(stmt, sql)
StatFilter->>WallFilter: preExecute(stmt, sql)
WallFilter->>LogFilter: preExecute(stmt, sql)
LogFilter->>RealStmt: executeQuery(sql) // 调用真实对象
RealStmt-->>LogFilter: ResultSet
LogFilter->>WallFilter: postExecute(result)
WallFilter->>StatFilter: postExecute(result)
StatFilter->>ProxyStmt: postExecute(result)
ProxyStmt-->>App: 返回 ResultSet
🔍 每个 Filter 的作用:
- StatFilter:记录 SQL 执行次数、耗时、并发数,用于监控面板
- WallFilter:解析 SQL 语法树,判断是否为危险操作(如
DROP TABLE),可配置白名单/黑名单 - LogFilter:打印 SQL 日志(支持参数脱敏)
3.3 如何配置 Filter?
在 Spring Boot 中,通过 filters 属性指定启用的 Filter:
spring:
datasource:
druid:
filters: stat,wall,log4j2
也可通过 Java Config 精细控制:
@Bean
public DruidDataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("");
// 添加 StatFilter
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(1000);
statFilter.setLogSlowSql(true);
ds.getProxyFilters().add(statFilter);
// 添加 WallFilter(MySQL 模式)
WallFilter wallFilter = new WallFilter();
wallFilter.setDbType("mysql");
WallConfig config = new WallConfig();
config.setSelectAllow(true);
wallFilter.setConfig(config);
ds.getProxyFilters().add(wallFilter);
return ds;
}
💡 Filter 执行顺序 = 添加顺序!建议:
Stat → Wall → Log
四、生产级推荐配置(含 Filter 安全策略)
前提:使用 druid-spring-boot-starter(推荐方式),版本 ≥ 1.2.16
spring:
datasource:
url: jdbc:mysql://prod-db-host:3306/your_db?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: your_app_user
password: ${DB_PASSWORD} # 推荐从环境变量或配置中心读取
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
# === 连接池核心参数 ===
initial-size: 10
min-idle: 10
max-active: 50
max-wait: 3000 # 单位:毫秒,建议 2000~5000
# === 连接有效性检测(关键!)===
time-between-eviction-runs-millis: 30000 # 30秒运行一次空闲连接检测线程
min-evictable-idle-time-millis: 600000 # 连接在池中最小生存时间(10分钟)
max-evictable-idle-time-millis: 900000 # 最大空闲时间(可选,Druid 1.2.6+ 支持)
validation-query: SELECT 1
validation-query-timeout: 1 # 校验 SQL 超时(秒)
test-while-idle: true # 后台线程检测空闲连接(推荐开启)
test-on-borrow: false # 借出时不检测(避免性能损耗)
test-on-return: false # 归还时不检测
# === 过滤器配置(核心功能)===
filters: stat,wall,slf4j # 注意:log4j2 已不推荐,用 slf4j
# StatFilter 配置(通过 connection-properties 设置)
connection-properties: >
druid.stat.mergeSql=true;
druid.stat.slowSqlMillis=1000;
druid.stat.logSlowSql=true
# WallFilter 配置(MySQL 模式)
wall:
db-type: mysql
config:
drop-table-allow: false
truncate-allow: false
alter-table-allow: false
lock-table-allow: false
delete-allow: true
update-allow: true
select-for-update-allow: false
# 白名单模式(更安全,按需启用)
# white-list-enabled: true
# white-list: com.yourcompany.dao.*
# === Web 监控(谨慎开放!)===
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.css,*.png,*.jpg,*.gif,*.ico,*.woff,*.woff2,/druid/*"
session-stat-max-count: 1000
profile-enable: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false # 禁止重置统计数据
login-username: ${DRUID_MONITOR_USER:admin}
login-password: ${DRUID_MONITOR_PASS}
allow: 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1 # 仅内网可访问
deny: # 可选:拒绝特定 IP
| 配置项 | 是否正确 | 说明 |
|---|---|---|
filters: stat,wall,slf4j | ✅ | log4j2 在新版 starter 中已不自动集成,推荐 slf4j,日志由应用统一管理 |
connection-properties | ✅ | 必须用此方式配置 StatFilter 参数,直接写 druid.stat.xxx 无效 |
wall.db-type: mysql | ✅ | 必须指定,否则默认为 sql92,可能误判语法 |
test-on-borrow: false | ✅ | 官方强烈建议关闭,性能损耗大;靠 test-while-idle 保证健康 |
max-wait: 3000 | ✅ | 避免线程无限阻塞,快速失败更利于系统稳定 |
allow 列表 | ✅ | 使用 CIDR 表示法(如 10.0.0.0/8),禁止写 "" 或 * |
reset-enable: false | ✅ | 生产环境必须关闭,防止监控数据被清空 |
⚠️ WallFilter 使用建议:
- 生产环境优先使用 白名单模式(只允许指定包下的 DAO 访问)
- 黑名单模式需谨慎测试,避免误杀合法 SQL
五、Druid vs HikariCP —— 补充 Filter 维度对比
| 能力 | Druid | HikariCP |
|---|---|---|
| JDBC 代理 | ✅ 完整代理 Connection/Statement/ResultSet | ❌ 仅管理连接,不代理 JDBC 对象 |
| SQL 拦截 | ✅ 通过 Filter 链实现 | ❌ 无法拦截 SQL 内容 |
| SQL 防火墙 | ✅ 基于语法树分析(WallFilter) | ❌ 不支持 |
| SQL 脱敏日志 | ✅ LogFilter 支持参数脱敏 | ❌ 需自行实现 |
| 扩展性 | ✅ 可自定义 Filter 实现 AOP | ❌ 无扩展点 |
🎯 结论:
如果你需要 SQL 级别的可观测性与安全性,Druid 的 Filter 机制是不可替代的优势。
六、生产注意事项(Filter 相关)
❗ 常见陷阱
| 问题 | 原因 | 解决方案 |
|---|---|---|
| WallFilter 误拦截合法 SQL | SQL 方言不匹配或规则过严 | 设置 dbType 正确值;先开启 logViolation=true 观察日志 |
| 自定义 Filter 未生效 | 未加入 proxyFilters 列表 | 使用 dataSource.getProxyFilters().add(myFilter) |
| 多数据源 Filter 冲突 | 全局 Filter 影响所有数据源 | 为每个 DruidDataSource 单独配置 Filter |
| 性能下降明显 | 开启了 testOnBorrow + 多个 Filter | 关闭 testOnBorrow,仅保留必要 Filter |
✅ 最佳实践
-
Filter 尽量轻量:避免在
preExecute中做耗时操作 -
WallFilter 白名单优先:比黑名单更安全可靠
-
慢 SQL 自动告警:结合
StatFilter+ 日志系统(如 ELK)实现
七、总结
Druid 的真正威力,源于其 “代理 + 过滤器链” 的架构设计:
- Proxy:透明包装 JDBC 对象,无侵入
- Filter Chain:像“流水线”一样处理每个数据库操作,实现监控、安全、日志等横切关注点
✅ 适用场景:
- 需要 SQL 审计、防注入的企业系统
- 要求慢 SQL 自动发现的高可用服务
- 合规性强、需操作留痕的金融/政务项目
📌 记住:
“Druid 不只是一个池,而是一个 JDBC 增强中间件。”