Article

数据库连接池 Druid

更新于:2026-07-14

第一章:Druid 概述与核心特性

1.1 什么是 Druid?

概念名称说明注意事项
Druid 定义Alibaba 开源的高性能数据库连接池组件,全称为 Alibaba Druid,专为监控、安全与扩展性设计。注意与 Apache Druid(实时 OLAP 分析引擎)区分,二者无任何关联。
项目定位不仅是连接池,还集成了 ProxyDriver、SQL Parser、Filter 扩展机制和 Web 监控台。主要面向 Java 应用,尤其在 Spring Boot 生态中广泛使用。
开源地址GitHub 仓库:https://github.com/alibaba/druid。建议通过 Maven Central 引入依赖,避免手动下载 JAR 包。

1.2 Druid 的核心功能与优势

功能类别功能名称说明注意事项
连接池基础连接复用与生命周期管理预创建连接、自动回收空闲/失效连接、控制最大/最小连接数,避免频繁创建销毁开销。需合理配置 maxActiveminIdle,防止资源浪费或连接耗尽。
性能优化PreparedStatement 缓存(PSCache)缓存预编译 SQL 语句,减少重复解析开销。需显式开启 poolPreparedStatements=true 并设置 maxOpenPreparedStatements
监控能力StatFilter 统计记录 SQL 执行时间、返回行数、错误次数、ResultSet 持有时间等。启用后会有约 5–15% 性能损耗。
监控能力Web 监控页面通过 StatViewServlet 提供 HTML 和 JSON API 查看连接池与 SQL 执行详情。生产环境务必配置登录认证(loginUsername/loginPassword)防止信息泄露。
安全防护WallFilter(SQL 防火墙)基于规则过滤危险 SQL(如 DROP TABLE),防御 SQL 注入。默认规则较宽松,建议根据业务自定义白名单或黑名单。
安全防护密码加密支持通过 ConfigTools.encrypt() 对数据库密码加密存储。解密需在运行时由 Druid 自动处理,配置文件中使用 password=xxx 即可。
扩展机制Filter-Chain 模式允许开发者通过 SPI 或编程方式插入自定义 Filter,拦截 JDBC 方法调用。Filter 执行顺序影响行为,需注意链式调用逻辑。
诊断能力连接泄漏检测自动记录未关闭连接的调用栈,辅助定位资源泄漏问题。需开启 removeAbandoned=true 并设置 removeAbandonedTimeout

1.3 与其他连接池(HikariCP、DBCP、C3P0)对比

对比维度HikariCPDruidDBCP / C3P0
性能⭐⭐⭐⭐⭐ 极致性能,纳秒级连接获取,无锁设计。⭐⭐⭐ 中高性能,因监控功能存在 5–15% 开销。⭐ 性能较差,已基本被社区淘汰。
内存占用~130KB,极简设计。~2MB,功能模块多。较高,且无有效优化。
监控能力无内置监控,需依赖 Spring Boot Actuator 或 AOP。✅ 内置完整监控体系(SQL、连接、慢查询、Web UI)。无或极其有限。
安全功能无 SQL 防火墙或审计能力。✅ 支持 WallFilter 防注入、操作审计、密码加密。无。
扩展性不支持 Filter 扩展,专注核心连接池。✅ 支持 Filter-Chain 自定义扩展。扩展机制弱或复杂。
适用场景微服务、云原生、高并发低延迟系统(如交易系统)。企业后台、金融/医疗合规系统、需深度监控与安全审计场景。已不推荐用于新项目。
Spring Boot 默认✅ 是(2.x+ 版本默认集成)。❌ 需手动引入 druid-spring-boot-starter

选型建议:

  • 若追求极致性能与轻量化 → 选择 HikariCP。
  • 若需要生产级监控、安全审计、故障诊断 → 选择 Druid。

第二章:快速入门与基础配置

2.1 Maven / Gradle 依赖引入

构建工具依赖名称语法用途注意事项
MavenDruid 核心库<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
引入 Druid 连接池核心功能(含 StatFilter、WallFilter 等)推荐使用最新稳定版(截至 2026 年为 1.2.23),避免安全漏洞。
MavenSpring Boot Starter(可选)<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.23</version>
</dependency>
自动配置 DataSource Bean,简化 Spring Boot 集成若使用此 starter,无需手动定义 DruidDataSource Bean。
GradleDruid 核心库implementation 'com.alibaba:druid:1.2.23'同 Maven 核心库功能需确保项目已配置 mavenCentral() 仓库。
GradleSpring Boot Starter(可选)implementation 'com.alibaba:druid-spring-boot-starter:1.2.23'同 Maven Starter 功能适用于基于 Spring Boot 的 Gradle 项目。

提示:可通过 Maven Repository 查询最新版本。

2.2 基础数据源配置(Java / XML / YAML)

Java 配置方式

配置项代码示例说明注意事项
创建 DataSourceDruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
通过编程方式设置连接参数必须调用 init() 初始化连接池(或交由 Spring 管理)。
设置连接池参数dataSource.setInitialSize(5);
dataSource.setMinIdle(5);
dataSource.setMaxActive(20);
控制连接池容量maxActive 在新版本中建议使用 maxTotal(兼容旧 API)。

XML 配置(Spring 传统配置)

配置项代码示例说明注意事项
定义 Bean<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
</bean>
applicationContext.xml 中声明需确保类路径包含 MySQL 驱动。

YAML 配置(Spring Boot)

配置项代码示例说明注意事项
基础连接spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
使用 Spring Boot 默认数据源配置前缀若使用 druid-spring-boot-starter,Druid 会自动接管。
Druid 特有参数spring:
datasource:
druid:
initial-size: 5
min-idle: 5
max-active: 20
validation-query: SELECT 1
test-while-idle: true
通过 spring.datasource.druid.* 配置 Druid 参数参数名使用 kebab-case(如 max-active 对应 setMaxActive)。

2.3 最小可运行示例(Spring Boot / 原生 Java)

Spring Boot 示例

文件类型内容说明注意事项
pom.xml<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
引入 Web、Druid Starter、MySQL 驱动runtime 表示驱动仅在运行时需要。
application.ymlspring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
配置数据库连接确保数据库 test 已存在且可访问。
主启动类@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
启动应用启动后可访问 /actuator/health 验证数据源状态。

原生 Java 示例(无框架)

文件类型内容说明注意事项
Main.javaimport com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;

public class Main {
public static void main(String[] args) throws Exception {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("123456");
ds.setInitialSize(1);
ds.init();
Connection conn = ds.getConnection();
System.out.println("Connection OK: " + !conn.isClosed());
conn.close();
ds.close();
}
}
手动创建、使用并关闭连接池必须显式调用 ds.init()ds.close(),否则资源泄漏。
编译运行命令javac -cp ".:druid-1.2.23.jar:mysql-connector-java-8.0.33.jar" Main.java
java -cp ".:druid-1.2.23.jar:mysql-connector-java-8.0.33.jar" Main
编译并运行(Linux/macOS)Windows 路径分隔符为 ;,需调整 classpath。

验证成功标志:控制台输出 Connection OK: true

第三章:连接池核心参数详解

3.1 基本连接参数(url、username、password、driverClassName)

参数名称语法(Java)用途代码示例注意事项
urlsetUrl(String url)指定数据库连接地址ds.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC");必须符合 JDBC 规范;建议显式设置时区和字符编码(如 characterEncoding=UTF-8)。
usernamesetUsername(String username)数据库登录用户名ds.setUsername("admin");生产环境建议通过配置中心或加密方式管理。
passwordsetPassword(String password)数据库登录密码ds.setPassword("secret123");强烈建议使用 ConfigFilter 加密存储,避免明文泄露。
driverClassNamesetDriverClassName(String className)指定 JDBC 驱动类ds.setDriverClassName("com.mysql.cj.jdbc.Driver");MySQL 8+ 必须使用 com.mysql.cj.jdbc.Driver,旧版为 com.mysql.jdbc.Driver

提示:在 Spring Boot YAML 中,这些参数位于 spring.datasource.* 下,而非 druid 子节点。

3.2 池容量控制(initialSize、minIdle、maxActive)

参数名称语法(Java)用途代码示例注意事项
initialSizesetInitialSize(int size)连接池初始化时创建的物理连接数ds.setInitialSize(5);启动即建立连接,避免首次请求延迟;建议等于 minIdle
minIdlesetMinIdle(int minIdle)池中保持的最小空闲连接数ds.setMinIdle(5);Druid 实际以 maxActive 为准,部分版本中 minIdle 被忽略;建议设为与 initialSize 相同。
maxActivesetMaxActive(int maxActive)最大活跃连接数(含正在使用的)ds.setMaxActive(20);曾用名 maxTotal,新版本兼容两者;必须 ≤ 数据库 max_connections(如 MySQL 默认 151)。

调优建议:

  • 小型应用:maxActive = 10~20
  • 中大型应用:maxActive = 20~50,结合压测调整
  • 避免盲目设高(如 100+),会导致数据库上下文切换开销剧增

3.3 超时与空闲连接管理(maxWait、timeBetweenEvictionRunsMillis、minEvictableIdleTimeMillis)

参数名称语法(Java)用途代码示例注意事项
maxWaitsetMaxWait(long millis)获取连接的最大等待时间(毫秒)ds.setMaxWait(3000); // 3秒超时抛出 SQLException;建议设为 1000~5000ms,实现”快速失败”。
timeBetweenEvictionRunsMillissetTimeBetweenEvictionRunsMillis(long millis)空闲连接检测线程运行间隔ds.setTimeBetweenEvictionRunsMillis(60000); // 60秒设为 ≤0 则禁用后台清理;建议 30~60 秒。
minEvictableIdleTimeMillissetMinEvictableIdleTimeMillis(long millis)连接空闲多久后可被回收ds.setMinEvictableIdleTimeMillis(300000); // 5分钟仅对超过 minIdle 的连接生效;应 < 数据库 wait_timeout

注意:Druid 还支持 maxEvictableIdleTimeMillis(最大空闲时间),用于强制回收长期空闲连接。

3.4 连接有效性验证(validationQuery、testOnBorrow、testWhileIdle)

参数名称语法(Java)用途代码示例注意事项
validationQuerysetValidationQuery(String sql)用于检测连接是否有效的 SQL 语句ds.setValidationQuery("SELECT 1");必须是轻量级查询;MySQL 用 SELECT 1,Oracle 用 SELECT 1 FROM DUAL
testOnBorrowsetTestOnBorrow(boolean test)借出连接时是否验证有效性ds.setTestOnBorrow(false);设为 true 会显著降低性能,生产环境不推荐开启。
testWhileIdlesetTestWhileIdle(boolean test)空闲连接在后台检测时是否验证ds.setTestWhileIdle(true);推荐开启,配合 timeBetweenEvictionRunsMillis 使用,兼顾性能与安全。
validationQueryTimeoutsetValidationQueryTimeout(int seconds)验证查询的超时时间(秒)ds.setValidationQueryTimeout(5);防止验证 SQL 卡住;设为 >0 才生效。

最佳实践:

ds.setValidationQuery("SELECT 1");
ds.setTestWhileIdle(true);
ds.setTestOnBorrow(false);
ds.setTestOnReturn(false);

3.5 PreparedStatement 缓存(poolPreparedStatements、maxOpenPreparedStatements)

参数名称语法(Java)用途代码示例注意事项
poolPreparedStatementssetPoolPreparedStatements(boolean pool)是否启用 PreparedStatement 缓存ds.setPoolPreparedStatements(true);对 Oracle/DB2 提升显著;MySQL 建议关闭(驱动层已优化)。
maxOpenPreparedStatementssetMaxOpenPreparedStatements(int max)单个连接最多缓存的 PS 数量ds.setMaxOpenPreparedStatements(100);设为 >0 时自动启用 poolPreparedStatements;过高会增加内存占用。

MySQL 特别说明:

  • MySQL Connector/J 8.x 已内置高效 PS 缓存
  • Druid 再次缓存可能造成冗余,通常设为 false
  • 若业务大量使用相同参数化 SQL(如 MyBatis),可尝试开启并压测对比

第四章:监控与统计功能(StatFilter)

4.1 StatFilter 启用与配置

配置方式语法 / 方法用途代码示例注意事项
别名方式(推荐)setFilters("stat")通过字符串别名启用 StatFilterds.setFilters("stat");别名 stat 映射到 com.alibaba.druid.filter.stat.StatFilter;支持组合如 "stat,wall"
Bean 方式(细粒度控制)setProxyFilters(List<Filter> filters)通过 Java Bean 注入 StatFilter 实例StatFilter statFilter = new StatFilter();
statFilter.setMergeSql(true);
ds.setProxyFilters(Arrays.asList(statFilter));
filtersproxyFilters 是叠加关系,不是互斥。
连接属性方式setConnectionProperties(Properties props)通过连接属性开启 Stat 功能Properties props = new Properties();
props.setProperty("druid.stat.mergeSql", "true");
ds.setConnectionProperties(props);
所有 druid.stat.* 属性均支持此方式。
JVM 参数方式启动参数全局生效-Ddruid.stat.mergeSql=true -Ddruid.filters=stat适用于无法修改代码的场景,但优先级低于程序配置。

注意:仅设置 filters="stat" 不会自动开启 SQL 日志输出,需额外配置日志框架(如 SLF4J)或开启 statementLogEnabled

4.2 Web 监控页面集成(StatViewServlet)

配置项语法(Spring Boot YAML)用途示例值注意事项
servlet 路径spring.datasource.druid.stat-view-servlet.url-pattern设置监控页面访问路径/druid/*默认即为此值;可自定义如 /monitor/*
启用开关spring.datasource.druid.stat-view-servlet.enabled是否启用 Web 监控true默认为 true;生产环境建议显式控制。
访问白名单spring.datasource.druid.stat-view-servlet.allow允许访问的 IP 地址(逗号分隔)127.0.0.1,192.168.1.0/24留空表示允许所有;生产环境必须限制
访问黑名单spring.datasource.druid.stat-view-servlet.deny拒绝访问的 IP(优先级高于 allow)192.168.1.100可用于封禁特定 IP。
登录用户名spring.datasource.druid.stat-view-servlet.login-username监控页面登录账号admin必须配合密码使用,否则无需认证。
登录密码spring.datasource.druid.stat-view-servlet.login-password监控页面登录密码druid123生产环境必须设置强密码。

手动注册 Servlet(非 Spring Boot)

步骤代码示例说明
注册 StatViewServlet@WebServlet(urlPatterns = "/druid/*", initParams = {
@WebInitParam(name = "loginUsername", value = "admin"),
@WebInitParam(name = "loginPassword", value = "druid123"),
@WebInitParam(name = "allow", value = "127.0.0.1")
})
public class DruidStatViewServlet extends StatViewServlet {}
需在 web.xml 或使用 @WebServlet 注解

⚠️ 安全警告:未设密码的 /druid 页面可能泄露数据库结构、SQL 执行详情,存在严重信息泄露风险。

4.3 SQL 执行统计、慢查询分析(slowSqlMillis、logSlowSql)

参数名称语法(Java)用途代码示例注意事项
slowSqlMillissetSlowSqlMillis(long millis)定义”慢 SQL”的阈值(毫秒)statFilter.setSlowSqlMillis(1000);默认为 3000ms;建议根据业务响应要求调整(如 500ms)。
logSlowSqlsetLogSlowSql(boolean log)是否将慢 SQL 输出到日志statFilter.setLogSlowSql(true);需确保日志框架已配置,且级别为 INFO 或 DEBUG。
mergeSqlsetMergeSql(boolean merge)合并未参数化 SQL(如 id=1, id=2id=?statFilter.setMergeSql(true);极大提升统计准确性;可通过 filters="mergeStat" 快速启用。
statementLogEnabledsetStatementLogEnabled(boolean enabled)是否记录所有 Statement 执行日志ds.setStatementLogEnabled(true);性能影响较大,仅限开发/测试环境使用。
statementExecutableSqlLogEnablesetStatementExecutableSqlLogEnable(boolean enable)是否打印带参数的实际 SQLds.setStatementExecutableSqlLogEnable(true);需配合 statementLogEnabled=true 使用。

YAML 配置示例:

spring:
  datasource:
    druid:
      filter:
        stat:
          enabled: true
          slow-sql-millis: 1000
          log-slow-sql: true
          merge-sql: true

4.4 监控访问控制(allow、deny、loginUsername、loginPassword)

控制维度配置项作用机制示例注意事项
IP 白名单allow仅允许列表中的 IP 访问监控页面127.0.0.1,10.0.0.0/8支持 CIDR 格式;留空 = 允许所有。
IP 黑名单deny拒绝列表中的 IP(即使在 allow 中)192.168.1.100优先级高于 allow
基本身份认证loginUsername + loginPasswordHTTP Basic Auth 登录验证loginUsername: admin
loginPassword: securePass!
必须同时设置两者才生效;密码建议使用复杂字符。
资源排除exclusions(WebStatFilter)不统计静态资源请求*.js,*.css,/druid/*防止监控数据被无关请求污染。

WebStatFilter 配置(用于 URI 监控)

配置项YAML 示例说明
exclusionsspring.datasource.druid.web-stat-filter.exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"过滤不监控的资源路径
session-stat-max-countspring.datasource.druid.web-stat-filter.session-stat-max-count: 1000最大会话统计数
profile-enablespring.datasource.druid.web-stat-filter.profile-enable: true开启请求耗时采样(可查看 SQL 执行链路)

完整安全建议:

  1. 生产环境必须设置 allowloginUsername/loginPassword
  2. 禁止将 /druid 路径暴露到公网
  3. 通过 Nginx 或 API 网关增加额外鉴权层

第五章:安全防护机制

5.1 SQL 防火墙(WallFilter)原理与配置

配置方式语法 / 方法用途代码示例注意事项
别名启用setFilters("wall")快速启用 WallFilter(默认 MySQL 规则)ds.setFilters("wall");自动根据 driverClassName 选择方言(如 Oracle、PostgreSQL)。
指定方言wallConfig.setDbType(String dbType)显式设置数据库类型WallConfig config = new WallConfig();
config.setDbType(JdbcConstants.MYSQL);
WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(config);
支持常量:JdbcConstants.MYSQLORACLEPOSTGRESQL 等。
允许多语句wallConfig.setMultiStatementAllow(boolean allow)是否允许单次执行多条 SQL(如 ; DROP TABLEconfig.setMultiStatementAllow(false); // 默认 false生产环境必须设为 false,防止堆叠注入攻击。
允许删除表wallConfig.setDeleteAllow(boolean allow)是否允许 DELETE 无 WHERE 条件config.setDeleteAllow(false);默认 true,建议关闭以防止误删全表。
允许更新表wallConfig.setUpdateAllow(boolean allow)是否允许 UPDATE 无 WHERE 条件config.setUpdateAllow(false);同上,防止全表更新。
允许函数调用wallConfig.setSchemaCheck(boolean check)是否校验 schema/函数权限config.setSchemaCheck(true);可限制调用 LOAD_FILE()SLEEP() 等危险函数。

YAML 配置示例(Spring Boot):

spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          db-type: mysql
          config:
            multi-statement-allow: false
            delete-allow: false
            update-allow: false

注意:WallFilter 默认策略较宽松,需根据业务需求收紧规则。

5.2 数据库密码加密(ConfigFilter 与 ConfigTools)

组件方法 / 工具用途代码示例注意事项
ConfigToolsConfigTools.encrypt(String password)生成公钥加密后的密文String encrypted = ConfigTools.encrypt("myPassword123");输出格式如 bXlQYXNzd29yZDEyMw==(Base64 编码的 AES 密文)。
ConfigFiltersetConnectionProperties("config.decrypt=true")告知 Druid 自动解密密码ds.setConnectionProperties("config.decrypt=true");必须配合 password=密文 使用。
公钥指定config.decrypt.key(可选)使用自定义公钥(非默认)props.setProperty("config.decrypt.key", "MFwwD...");默认使用内置密钥;自定义需同时在加密和解密端一致。
Maven 插件加密mvn druid:encrypt -Ddruid.password=myPass通过 Maven 命令行加密执行后输出密文到控制台需在 pom.xml 中引入 druid-maven-plugin

完整 Java 配置示例

步骤代码
1. 加密密码(开发阶段)String pwd = ConfigTools.encrypt("root123"); // 得到密文
2. 配置数据源DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost/test");
ds.setUsername("root");
ds.setPassword("密文");
Properties props = new Properties();
props.setProperty("config.decrypt", "true");
ds.setConnectionProperties(props);

安全提示:

  • 密文仍可能被反编译获取,建议结合配置中心(如 Apollo、Nacos)动态下发
  • 不要将密文硬编码在 Git 仓库中

5.3 防注入规则自定义

自定义方式方法 / 配置用途示例注意事项
白名单 SQLwallProvider.addWhiteSql(String sql)添加允许执行的 SQL 模板wallProvider.addWhiteSql("SELECT * FROM users WHERE id = ?");需在初始化后、连接池启动前调用。
黑名单关键词wallConfig.setBlackList(List<String>)禁止包含特定关键词的 SQLList<String> black = Arrays.asList("SLEEP", "BENCHMARK");
config.setBlackList(black);
区分大小写;支持正则(需开启 blackListRegex=true)。
自定义回调wallFilter.setSqlStatCallback(SqlStatCallback callback)拦截 SQL 并自定义处理逻辑wallFilter.setSqlStatCallback((sql, result) -> {
if (!result.isExecute()) {
log.warn("Blocked SQL: " + sql);
}
});
可用于告警、审计日志记录。
规则文件加载wallConfig.setRuleFile(String path)从外部文件加载规则config.setRuleFile("classpath:wall-rules.xml");文件需符合 Druid Wall 规则 XML 格式。

自定义 WallProvider 示例(高级)

场景代码片段
动态白名单WallProvider provider = WallProvider.create(JdbcConstants.MYSQL);
provider.getConfig().setSelectAllColumnAllow(false);
// 从数据库或配置中心加载白名单
List<String> allowed = loadFromConfigCenter();
allowed.forEach(provider::addWhiteSql);
WallFilter wallFilter = new WallFilter();
wallFilter.setProvider(provider);

规则优先级:

白名单 > 黑名单 > 内置规则

即:若 SQL 在白名单中,即使含黑名单关键词也会放行。

典型禁用项建议:

  • SELECT *(设 selectAllColumnAllow=false
  • 无 WHERE 的 DELETE/UPDATE
  • 多语句(multiStatementAllow=false
  • 危险函数:LOAD_FILEEXECUTExp_cmdshell(SQL Server)

第六章:扩展与高级功能

6.1 Filter 链机制与自定义 Filter 开发

概念 / 方法语法 / 接口用途代码示例注意事项
Filter 接口com.alibaba.druid.filter.Filter所有 Druid Filter 的基接口public class MyCustomFilter implements Filter { ... }必须实现 connection_connectstatement_execute 等拦截方法。
方法拦截命名规则xxx_before / xxx_after在目标 JDBC 方法执行前后插入逻辑public void statement_execute_before(StatementProxy stmt, String sql) {
System.out.println("Executing: " + sql);
}
方法名需严格匹配(如 connection_close_before)。
注册 Filter(Java)ds.setProxyFilters(List<Filter>)将自定义 Filter 加入链List<Filter> filters = new ArrayList<>();
filters.add(new MyCustomFilter());
ds.setProxyFilters(filters);
执行顺序 = 列表顺序;StatFilter/WallFilter 可共存。
注册 Filter(别名)ds.setFilters("stat,myfilter")通过 SPI 别名注册需在 META-INF/druid-filter.properties 中声明:
myfilter=com.example.MyCustomFilter
适用于模块化部署,避免硬编码依赖。
获取原始连接conn.getRawConnection()在 Filter 中访问底层 ConnectionConnection raw = ((DruidPooledConnection) conn).getRawConnection();谨慎使用,可能绕过连接池管理。

自定义 Filter 完整示例:

public class AuditLogFilter extends FilterEventAdapter {
    @Override
    public void statementExecuteBefore(StatementProxy statement, String sql) throws SQLException {
        System.out.println("[AUDIT] SQL: " + sql);
    }
}

6.2 日志集成(Log4j、SLF4J、JDK Logging)

日志框架配置方式用途示例配置注意事项
SLF4J(推荐)自动适配Druid 默认优先使用 SLF4J 输出日志无需额外配置,引入 slf4j-api + 实现(如 Logback)即可若同时存在多个日志框架,需排除冲突依赖。
Log4j2设置系统属性强制使用 Log4j2启动参数:-Ddruid.logType=log4j2需确保 log4j-core 在 classpath。
JDK Logging默认回退当无其他日志框架时使用无需配置格式简陋,不建议生产使用。
关闭日志-Ddruid.logType=none禁用所有 Druid 内部日志java -Ddruid.logType=none -jar app.jar适用于性能敏感场景。
StatFilter 日志statFilter.setLogSlowSql(true)输出慢 SQL 到日志见第四章 4.3 节日志级别为 WARN(慢 SQL)或 INFO(普通统计)。

Maven 依赖示例(SLF4J + Logback):

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.12</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.4.14</version>
</dependency>

6.3 JMX 监控支持

功能配置方式用途代码 / 配置示例注意事项
启用 JMXds.setEnableJmx(true)将连接池指标注册为 MBeanDruidDataSource ds = new DruidDataSource();
ds.setEnableJmx(true);
默认为 false;Spring Boot 中需显式开启。
JMX 对象名ds.setJmxObjectName(String name)自定义 MBean 名称ds.setJmxObjectName("com.example:type=MyDB");避免名称冲突;格式需符合 JMX 规范。
查看指标使用 jconsole 或 jvisualvm实时监控活跃连接、等待数等启动应用后运行 jconsole → 连接进程 → MBean 标签需确保应用未禁用 JMX(如 -Dcom.sun.management.jmxremote)。
关键 MBean 属性可读取的运行时指标ActiveCount, PoolingCount, ConnectCount, CloseCount, WaitThreadCount支持通过 JMX API 编程获取。
Spring Boot 集成management.endpoints.jmx.exposure.include=*通过 Actuator 暴露 JMX 端点application.yml 中配置需引入 spring-boot-starter-actuator

YAML 配置(Spring Boot + JMX):

spring:
  datasource:
    druid:
      enable-jmx: true
management:
  endpoints:
    jmx:
      exposure:
        include: "*"

6.4 多数据源配置

实现方式配置方法用途示例说明注意事项
手动定义多个 Bean@Bean("db1") DruidDataSource ds1()
@Bean("db2") DruidDataSource ds2()
简单多数据源(无事务跨库)分别配置不同 URL/用户名需手动管理 @Qualifier 注入。
Dynamic DataSource(推荐)使用 dynamic-datasource-spring-boot-starter支持注解切换数据源@DS("slave") public List<User> list() { ... }与 Druid 完美兼容,自动包装为 DruidDataSource。
AbstractRoutingDataSource继承 Spring 抽象类自定义路由逻辑(如分库)重写 determineCurrentLookupKey() 返回数据源 key需自行初始化多个 DruidDataSource 实例。
YAML 配置(Spring Boot)spring.datasource.dynamic.datasource.master.url=...
spring.datasource.dynamic.datasource.slave.url=...
声明式多数据源依赖 com.baomidou:dynamic-datasource-spring-boot-starter主从、读写分离开箱即用。
事务管理@Transactional + DataSourceTransactionManager保证单数据源事务一致性每个数据源需独立配置 TransactionManager不支持跨数据源分布式事务(需 Seata 等)。

手动多数据源 Java 配置示例

文件内容
DataSourceConfig.java@Configuration
public class DataSourceConfig {
@Bean("master")
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource master() { return new DruidDataSource(); }

@Bean("slave")
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slave() { return new DruidDataSource(); }
}
application.ymlspring:
datasource:
druid:
master:
url: jdbc:mysql://localhost/master
username: root
password: 123
slave:
url: jdbc:mysql://localhost/slave
username: root
password: 123

最佳实践:

  • 读写分离 → 使用 dynamic-datasource
  • 分库分表 → 结合 ShardingSphere
  • 简单双库 → 手动 @Bean + @Qualifier

第七章:生产环境最佳实践

7.1 性能调优建议(连接数、超时、KeepAlive)

调优维度参数名称推荐值说明注意事项
连接池容量initialSize= minIdle(如 5~10)避免启动时首次请求延迟不宜过大,防止数据库连接堆积
minIdle5~10保持最小空闲连接Druid 实际以 maxActive 为主,此值影响不大
maxActive20~50(压测后确定)最大并发连接数必须 ≤ 数据库 max_connections(MySQL 默认 151)
获取超时maxWait1000~3000 ms获取连接最大等待时间超时应快速失败,避免线程阻塞雪崩
空闲检测timeBetweenEvictionRunsMillis30000~60000 ms后台清理线程间隔设为 ≤0 则禁用清理
minEvictableIdleTimeMillis300000 ms(5分钟)空闲连接可回收时间应 < 数据库 wait_timeout(MySQL 默认 8 小时)
连接保活keepAlivetrue启用连接保活机制需配合 keepAliveBetweenTimeMillis 使用
keepAliveBetweenTimeMillis1800000 ms(30分钟)连接空闲多久后执行保活 SQL仅对超过 minIdle 的连接生效
validationQuerySELECT 1(MySQL)保活/验证 SQL必须轻量、无副作用
预编译缓存poolPreparedStatementsfalse(MySQL)
true(Oracle)
是否启用 PS 缓存MySQL 驱动已优化,重复缓存反增开销

压测建议:

  • 使用 JMeter / Gatling 模拟峰值流量
  • 监控数据库 Threads_connected 和应用 ActiveCount
  • 调整 maxActive 直至吞吐稳定、错误率 < 0.1%

7.2 故障检测与自动恢复(removeAbandoned、breakAfterAcquireFailure)

参数名称推荐值用途代码示例注意事项
removeAbandonedtrue自动回收疑似泄漏的连接ds.setRemoveAbandoned(true);仅用于诊断,非根治方案
removeAbandonedTimeout1800 秒(30分钟)连接未归还超过此时间则强制回收ds.setRemoveAbandonedTimeout(1800);应远大于业务最长事务时间
logAbandonedtrue记录连接泄漏时的调用栈ds.setLogAbandoned(true);日志级别为 WARN;可用于定位代码问题
breakAfterAcquireFailuretrue获取连接失败后中断后续获取ds.setBreakAfterAcquireFailure(true);防止数据库宕机时线程持续重试耗尽资源
failFasttrue启动时立即测试连接有效性ds.setFailFast(true);避免应用启动成功但数据库不可用
asyncInittrue异步初始化连接池ds.setAsyncInit(true);加快 Spring Boot 启动速度

典型场景:

  • 数据库重启 → breakAfterAcquireFailure=true 可快速失败,配合 Hystrix/Sentinel 熔断
  • 连接泄漏 → removeAbandoned=true + logAbandoned=true 辅助排查

7.3 与 Spring Boot / MyBatis / JPA 集成方案

框架组合集成方式关键依赖配置要点注意事项
Spring Boot + Druid使用 druid-spring-boot-starter<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.23</version>
</dependency>
配置前缀:spring.datasource.druid.*自动注册 DataSource、StatViewServlet、WebStatFilter
+ MyBatis引入 mybatis-spring-boot-starter<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
Mapper 扫描:@MapperScan("com.example.mapper")无需额外配置,MyBatis 自动使用 Druid DataSource
+ JPA (Hibernate)使用 Spring Data JPA<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Hibernate 自动适配 DataSource建议关闭 JPA 自建连接池:spring.jpa.database-platform=...
监控集成开启 StatFilter + Web UI已包含在 starter 中spring.datasource.druid.stat-view-servlet.enabled=true生产环境务必设密码和 IP 白名单
多数据源结合 dynamic-datasource<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>
使用 @DS("slave") 注解切换自动包装为 DruidDataSource,支持监控

YAML 完整示例(Spring Boot + MyBatis + Druid):

spring:
  datasource:
    url: jdbc:mysql://db:3306/app
    username: user
    password: ENC(xxx) # 若使用加密
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      initial-size: 5
      max-active: 30
      max-wait: 3000
      validation-query: SELECT 1
      test-while-idle: true
      stat-view-servlet:
        enabled: true
        login-username: admin
        login-password: securePass!
        allow: 10.0.0.0/8
mybatis:
  mapper-locations: classpath:mapper/*.xml

7.4 容器化部署注意事项(Docker / Kubernetes)

场景配置项 / 策略说明示例 / 建议注意事项
Docker 镜像基础镜像选择使用官方 JDK 镜像FROM eclipse-temurin:17-jre-alpine避免使用 latest 标签
配置外置使用环境变量替代硬编码配置ENV SPRING_DATASOURCE_URL=jdbc:mysql://host/db推荐结合 ConfigMap(K8s)
健康检查/actuator/health 或自定义端点K8s Liveness/Readiness 探针livenessProbe:
httpGet:
path: /druid/health.html
port: 8080
initialDelaySeconds: 60
Druid 无内置 /health,需通过 Actuator 或自定义 Servlet
资源限制CPU/Memory 限制防止单 Pod 耗尽节点资源resources:
limits:
memory: "512Mi"
cpu: "500m"
maxActive 应与内存配比(每连接约 1~2MB)
网络策略限制 /druid 访问仅允许运维网段访问监控页networkPolicy:
ingress:
- from:
- ipBlock:
cidr: 10.10.0.0/16
避免将 stat-view-servlet 暴露到公网
日志收集stdout/stderr 输出便于 Fluentd / Loki 采集确保日志框架输出到控制台避免写入容器内文件(Pod 重建丢失)
连接池预热启动时执行 SQL避免冷启动延迟@PostConstruct public void warmUp() { ... }可查询 SELECT 1 初始化连接

Kubernetes Deployment 片段示例:

env:
  - name: SPRING_DATASOURCE_DRUID_STAT-VIEW-SERVLET_LOGIN-PASSWORD
    valueFrom:
      secretKeyRef:
        name: druid-secret
        key: password
resources:
  requests:
    memory: "256Mi"
    cpu: "200m"
  limits:
    memory: "512Mi"
    cpu: "500m"
livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 60
  periodSeconds: 30

安全加固:

  • 禁用 filters="stat" 的远程访问(或严格限 IP)
  • 数据库密码通过 Secret 注入,禁止明文写入 YAML
  • 启用 WallFilter 防注入

第八章:底层原理与架构解析

8.1 DruidDataSource 初始化流程

阶段方法 / 步骤作用关键代码逻辑注意事项
双重检查锁init() 入口确保仅初始化一次if (inited) return;
lock.lockInterruptibly();
if (inited) return;
使用 ReentrantLock 非公平锁,避免死锁(参考 issue #2980)
驱动加载DruidDriver.getInstance()注册 Druid 自身为 JDBC Driverstatic { registerDriver(new DruidDriver()); }支持 jdbc:druid://... 协议(较少使用)
配置注入configFromPropety(Properties)从 Properties 加载参数setUrl(props.getProperty("druid.url"));Spring Boot 中由 DruidDataSourceAutoConfigure 完成绑定
Filter 初始化for (Filter f : filters) f.init(this);初始化所有 Filter包括 StatFilter、WallFilter 等SPI 加载的 Filter 通过 initFromSPIServiceLoader() 注入
数据库类型识别JdbcUtils.getDbType(jdbcUrl, null)自动推断 DB 类型返回 DbType.mysqloracle影响 SQL 解析器、防火墙规则选择
连接池预热异步创建初始连接提升首次请求性能if (asyncInit && createScheduler != null) { submitCreateTask(true); }同步初始化会阻塞 init() 直到 initialSize 连接建立完成
状态标记inited = true;标记初始化完成最后一步,释放锁之后 getConnection() 不再触发初始化

初始化顺序图(简化):

new DruidDataSource() → setXXX() → init() →
    1. 加锁 + 双重检查
    2. 加载驱动、解析 URL
    3. 初始化 Filters
    4. 校验参数(如 maxActive > 0)
    5. 启动后台线程(evictor、createScheduler)
    6. 标记 inited = true

8.2 连接获取与归还机制

连接获取(getConnection())

方法作用关键逻辑注意事项
getConnection()公开入口调用 getConnection(maxWait)maxWait 默认 -1(无限等待)
getConnection(long maxWaitMillis)主流程1. 调用 init()(若未初始化)
2. 若有 Filter,走 filterChain.dataSource_connect()
3. 否则调用 getConnectionDirect()
Filter 链可拦截并修改行为
getConnectionDirect(long maxWaitMillis)直接获取1. 循环尝试 getConnectionInternal()
2. 若 testOnBorrow=true,验证连接有效性
3. 无效则 discardConnection() 并重试
支持重试(notFullTimeoutRetryCount 次)
getConnectionInternal(long maxWaitNanos)核心实现1. 若池空且未满,直接创建物理连接
2. 否则从 connections[] 数组尾部取(takeLast()/pollLast()
3. 若无可用连接,等待 notEmpty 条件
使用 ReentrantLock + Condition 实现生产者-消费者模型

连接归还(close())

方法作用关键逻辑注意事项
DruidPooledConnection.close()代理 close1. CAS 设置 closing=1 防重复关闭
2. 触发 ConnectionEventListener
3. 调用 recycle()
实际不关闭物理连接,而是归还池中
recycle()归还主逻辑1. 若未被标记为 abandoned,调用 dataSource.recycle(this)
2. 清空 holder、conn 引用
防止内存泄漏
DruidDataSource.recycle(DruidPooledConnection)池回收1. 重置连接状态(autoCommit、catalog 等)
2. 放回 connections[] 数组头部
3. 唤醒等待线程(notEmpty.signal()
重置操作确保下次获取时状态干净

关键数据结构:

  • DruidConnectionHolder[] connections:连接池底层数组
  • ReentrantLock lock + Condition notEmpty, empty:并发控制
  • AtomicInteger activeCount:活跃连接计数

8.3 Filter-Chain 执行模型

组件作用执行流程示例方法
Filter 接口定义拦截点所有扩展 Filter 必须实现此接口connection_connect, statement_execute
FilterChainImpl责任链实现1. 构造时传入 Filter 列表
2. 调用 xxx() 时依次执行 filter.xxx_before() → 下一 filter → filter.xxx_after()
dataSource_connect(DataSourceProxy, long)
方法命名映射自动匹配statement.execute(sql)statementExecuteBefore(stmt, sql)executeQuery()statementExecuteAfter(...)方法名需严格遵循驼峰转下划线规则
执行顺序用户定义filters 列表顺序即执行顺序StatFilter → WallFilter → 自定义 Filter
短路机制支持中断某 Filter 可抛出异常或返回 false 中断链WallFilter 拦截 SQL 时直接抛 SQLException

Filter 方法签名示例

JDBC 操作对应 Filter 方法参数说明
Connection conn = ds.getConnection()connection_connect(DataSourceProxy, long maxWait)可修改 maxWait 或抛异常拒绝连接
stmt.executeQuery(sql)statement_executeQuery(StatementProxy, String sql)可记录日志、修改 SQL、校验权限
rs.next()resultSet_next(ResultSetProxy)可统计行数、脱敏数据

设计优势:

  • 无侵入式扩展
  • 可组合多个功能(监控 + 防火墙 + 审计)
  • 性能损耗可控(仅在启用时生效)

8.4 SQL 解析器(SQLParser)工作原理

组件作用技术细节应用场景
SQLParser 接口SQL 语法解析入口基于手写递归下降解析器(非 ANTLR)支持 MySQL、Oracle、PostgreSQL 等方言
SQLStatementParser语句级解析将 SQL 字符串转为 AST(抽象语法树)SELECT * FROM tSQLSelectStatement 对象
SchemaStatVisitor语义分析遍历 AST 提取表名、列名、条件等用于 WallFilter 的权限校验、SQL 统计
词法分析Tokenization识别关键字、标识符、常量等SELECTToken.SELECT
语法分析Parsing构建语法树,校验合法性非法 SQL(如 SELECT FROM)抛 ParserException
方言适配DbType 驱动不同数据库使用不同 Parser 实现MySqlStatementParser, OracleStatementParser

解析流程示例(SELECT id, name FROM users WHERE age > 18)

步骤输出
1. 词法分析[SELECT, id, ,, name, FROM, users, WHERE, age, >, 18]
2. 语法分析构建 SQLSelectStatement 对象
3. 语义分析(Visitor)提取:表 users;列 id, name, age;条件 age > 18

性能优化:

  • 缓存解析结果(SQLParseCache)
  • 合并相似 SQL(mergeSql=true 时基于 AST 去参数化)

典型应用:

  • WallFilter:检查是否访问了未授权表
  • StatFilter:统计各表访问频率
  • SQL 防注入:识别 UNION SELECTSLEEP() 等危险模式