1. 从连接池到Druid:为什么要在Spring Boot里选它
先亮个结论:如果你在Spring Boot项目里用JDBC、MyBatis或者JPA,连接池基本是绕不开的一环。而Druid在国内Java圈子里属于“老牌且能打”的选手,配合druid-spring-boot-starter这种官方桥接包,可以在Spring Boot的自动配置体系下用最少的代码把它跑起来,还能顺手拿到一套完整的监控和SQL分析面板。
很多人第一次接触Druid,可能是在别人项目的pom.xml里看到一段依赖,然后照着抄,结果发现配置写了一大堆还是有问题:监控页面进不去、连接数上不去、日志里报GetConnectionTimeoutException。这篇文章我就是用实际踩坑的经历,把springboot + druid-spring-boot-starter这套组合从引入依赖、参数配置、监控开启到问题排查完整拆一遍。
1.1 连接池到底在解决什么问题
先聊点基础的,不然直接用参数很容易犯迷糊。数据库连接的建立和销毁是非常“重”的操作,每一次TCP握手、认证、会话初始化都要消耗资源和时间。如果你的应用每个请求都新建连接、用完就关闭,在高并发下不光数据库要累趴下,应用自身的响应时间也会被拖垮。
连接池的思路就是提前创建一批连接放在池子里,谁要用就去“借”,用完“还”回来给下一个人用。这个道理类似共享单车:与其人手一辆自己养,不如把车集中管理、按需取用。池化之后,连接复用率大幅提高,系统吞吐量上去了,数据库端的连接开销也稳定了。
Spring Boot生态里常见的连接池有HikariCP、Tomcat JDBC Pool、Druid,还有老牌的C3P0、DBCP。到了Spring Boot 2.x之后,默认的数据源是HikariCP,因为它的性能好、实现轻量。那为什么还要换成Druid?核心原因有三个:一是Druid自带一整套“监控统计”能力,能看到SQL执行时间、慢SQL、活跃连接数、事务数等指标;二是Druid有内置的SQL防御能力,也就是WallFilter防火墙,可以拦截SQL注入;三是扩展能力强,密码加密、连接泄露检测、日志记录等功能都是现成的。
1.2 和直接用Druid原生API相比,starter解决了什么
要是走老路,用Druid原生方式接入Spring Boot,你需要手动创建DruidDataSource实例、手动配置Servlet(统计页面)、手动挂在Filter(Web请求拦截)、手动绑定DataSource到MyBatis或JPA,一套流程写下来光配置类就好几百行。而druid-spring-boot-starter的作用就是把这些都封装成了Spring Boot的自动配置:只要你引了依赖、在application.yml里写了spring.datasource.druid开头的配置,它就能自动创建数据源、注册监控Servlet、绑定统计过滤器。
我实测下来,这个starter在Spring Boot 2.x和3.x下的表现都很稳定,真正做到了“少写配置、先跑起来”。但是要注意,自动配置虽然省事,也意味着某些默认值你可能没注意到,比如不配置监控页面地址的话,Druid的Servlet不会默认对外暴露,需要你显式设置stat-view-servlet.enabled=true。
2. 快速集成:引入依赖与最简启动
2.1 Maven依赖的选型细节
以Maven项目为例,在pom.xml里加:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency>版本号这里我特意强调一下:druid-spring-boot-starter的版本要和你项目里的Spring Boot大版本匹配。Spring Boot 2.x对Java 8是主流兼容,一般用1.2.x的最新版本就行;Spring Boot 3.x由于基于Jakarta EE规范,需要1.2.18以上的版本才能更好地兼容,早期的1.2.8之类的版本会存在包路径不兼容的坑。建议引入依赖前先去Maven仓库看一眼当前release版本,别拿着网上两三年前的配置直接抄。
另外注意一个细节:druid-spring-boot-starter内部已经包含了druid核心库,不需要再单独引入com.alibaba:druid,重复引入反而可能出现版本冲突。如果你的项目里之前已经直接依赖过druid,最好排除掉旧的,统一走starter的版本。
2.2 最简配置跑通一个连接池
引入依赖后,在application.yml里加一段最基础的配置就能启动:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456这里把spring.datasource.type显式指定为DruidDataSource,是为了告诉Spring Boot优先使用Druid而不是默认的HikariCP。有些场景下即使不写type,starter也能自动装,但写上是更稳妥的做法,尤其是在配置类里通过@ConfigurationProperties读取数据源属性时,显式声明能避免类型推断混乱。
启动项目后,如果你用MyBatis,直接在Mapper里跑一条SQL,就能看到查询成功。这时候Druid已经生效了,只是监控功能还没开。想快速验证连接池是否在工作,直接看数据库端的show processlist,能看到来自应用IP的连接保持存活,不会随着请求结束被销毁。
3. 生产级参数配置:每个参数背后的逻辑
3.1 连接池容量:initialSize、minIdle、maxActive
连接池的容量参数直接决定了系统的并发承载能力。我最常用的基础配置如下:
spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000initial-size是启动时创建的初始连接数,min-idle是池中长期保持的最小空闲连接数,max-active是池中最大的活跃连接总数,max-wait是获取连接时的最大等待时间,单位毫秒。
这三个参数不是随便拍脑袋写的。假设你的系统峰值并发是100个请求同时访问数据库,每个请求占用连接平均100ms,那并发需要的连接数是100×0.1=10个,还要留出一些余量应对突发流量,所以max-active设置在20左右是合理的。如果把max-active设置得过大,比如200,而实际并发只有20,会造成连接长期空闲,白白占用数据库端的连接配额;设置得过小则会出现获取连接超时,报GetConnectionTimeoutException。
对于initial-size和min-idle,建议保持一致,这样避免了启动后动态创建连接的性能抖动。尤其是系统重启后第一次迎来流量高峰时,如果池子里连接数太少,大量请求会卡在“创建连接”这个环节,表现就是接口响应突然变慢,几秒后又恢复正常,这就是典型的连接初始化跟不上流量。
3.2 连接保活与检测:testWhileIdle、timeBetweenEvictionRunsMillis
数据库连接有一个很隐蔽的问题:网络断连或者数据库重启后,池中已有的连接可能已经失效,但应用不知道。下次从池里拿一条坏连接去执行SQL,就会抛异常。要解决这个问题,Druid提供了空闲连接检测机制。
spring: datasource: druid: test-while-idle: true test-on-borrow: false test-on-return: false time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1test-while-idle的意思是,当连接在池中处于空闲状态时,每隔一段时间检查一下连接是否有效;validation-query是用于验证的SQL,MySQL下用SELECT 1,Oracle下建议用SELECT 1 FROM DUAL,PG则用SELECT 1。
time-between-eviction-runs-millis表示扫描空闲连接的间隔,默认是60秒。min-evictable-idle-time-millis表示连接空闲超过这个时间才可能被回收。
这里有个容易踩的坑:test-on-borrow和test-on-return不要轻易开启。它们的含义是每次从池中借出连接时都做一次有效性检测、每次归还时也检测一次。虽然这样能最大程度保证拿到手的连接绝对可用,但每一次检测都是一次额外的数据库交互,在高并发下会明显拉长请求耗时。我见过有团队把这两个参数全开了,QPS上来后数据库的QPS直接翻了一倍,全是被检测SQL撑起来的。除非你的网络环境极不稳定,否则保持test-while-idle为true就够了。
3.3 慢SQL与连接泄露的防御参数
Druid比较实用的一项能力是慢SQL记录和连接泄露检测。
spring: datasource: druid: connection-properties: druid.stat.logSlowSql=true;druid.stat.slowSqlMillis=2000这段配置中druid.stat.logSlowSql表示记录慢SQL日志,druid.stat.slowSqlMillis表示SQL执行超过多少毫秒算“慢”。把它设为2000,意味着超过2秒的SQL都会被记录下来。注意这个配置是在connection-properties里以分号分隔写的,不是直接写在顶层,写错了不会报错但也不会生效。
连接泄露的检测靠的是remove-abandoned系列参数:
spring: datasource: druid: remove-abandoned: true remove-abandoned-timeout: 180 log-abandoned: trueremove-abandoned的作用是,如果某个连接被业务代码拿走了,长时间没有归还(超过remove-abandoned-timeout指定的秒数),Druid会强制回收这条连接。log-abandoned则会在回收时打印调用栈信息。
这个功能一定要慎用。remove-abandoned-timeout如果设置得太小(比如30秒),碰上正常的慢查询就有被误杀的风险,Druid强制关闭连接后,正在执行的SQL会被中断,业务直接报错。我一般设置成180秒,同时结合慢SQL日志来判断是否存在真正的连接泄露,而不是单纯依赖自动回收。
3.4 密码加密与多数据源注意事项
生产环境不建议把数据库明文密码放在配置文件里。Druid提供了ConfigTools工具来做密码加密,操作步骤是先执行:
java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools 123456输出结果会包含privateKey、publicKey和password,把加密后的password填到配置里,同时加上:
spring: datasource: druid: connect-properties: password: ${db_password} filter: config: enabled: true然后在Java启动参数里传入-Ddb_password=密文,或者在环境变量里配置。注意,ConfigTools生成的公钥和私钥跟具体机器无关,可以用于测试,但生产环境建议用独立的密钥对,并保管好私钥。
多数据源情况下,druid-spring-boot-starter的自动配置不会自动为你创建多个数据源。我们需要自己定义配置类,分别读取不同的配置前缀,手动构建DruidDataSource实例,再用@Primary标记主数据源。这时候starter提供的仍然是DruidDataSourceAutoConfigure的基础支持,具体的数据源可以由@ConfigurationProperties(prefix = "spring.datasource.druid.one")这样的方式来绑定属性。注意若依这类框架里的多数据源实现,其实也是这个套路,只是把数据源定义封装成了动态路由。
4. 监控与运维:把Druid的看板上手
4.1 开启监控页面
Druid最直观的价值是自带的监控页面,它不需要额外部署组件,只需要在配置里打开开关:
spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 reset-enable: false web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico这里stat-view-servlet.enabled必须为true,否则访问/druid/index.html会直接404。login-username和login-password是监控页面的登录账号密码,生产环境绝对要改掉默认值。reset-enable建议设为false,防止有人通过页面上的“重置”按钮清空统计数据。
web-stat-filter是用于监控Web请求的过滤器,它会记录每个URL的请求次数、耗时等信息。exclusions里的路径要写全,尤其是静态资源和监控页面自身,否则会递归统计,干扰数据。还有一点:这个Filter是Servlet级别的,如果项目使用WebFlux而非Servlet Web(即Spring Boot WebFlux应用),Druid的监控管理Servlet和Filter机制就不适用,不要在响应式项目里硬套。
4.2 监控页面里重点看哪些指标
打开/druid/index.html之后,左侧有数据源、SQL监控、URL监控、Spring监控、Wall拦截等菜单。我通常重点关注几个面板:
- 数据源面板:看
ActiveCount(活跃连接数)、PoolingCount(池中空闲连接数)、LogicConnectCount(累计逻辑连接数)。当活跃连接数持续逼近max-active时,就该考虑扩容或者优化SQL了。 - SQL监控面板:按执行次数排序,看哪些SQL的
ExecuteCount很大但FetchRowCount很小,这种通常是索引没命中;按耗时排序,看MaxTimes超高的SQL,优先优化。 - Wall面板:看有多少次SQL被防火墙拦截,如果拦的是正常业务SQL,那可能是防火墙白名单配置过严。
这里有一个很关键的细节:SQL监控默认是开启的,但如果你想精确看到SQL的执行参数(即预编译语句的绑定值),需要在JDBC URL后面拼一层参数或者在连接属性里打开druid.stat.mergeSql和druid.stat.slowSqlMillis。比如合并SQL可以避免大量只有参数不同的相同SQL被重复统计。
4.3 通过代码获取监控数据
除了看页面,Druid还提供了编程式的监控API,这就为集成监控系统(比如Prometheus、Admin)提供了可能:
DruidDataSource dataSource = (DruidDataSource) dataSourceManager.getDataSource(); DruidPooledConnection connection = dataSource.getConnection(); // 获取数据源统计 DruidDataSourceStatManager.getInstance().getDataSourceStatList();更常用的方式是启用Druid的Spring监控,在配置里打开:
spring: datasource: druid: aop-patterns: com.example.demo.service.*aop-patterns配置后,starter会为指定包路径下的Spring Bean方法做耗时统计,在监控页面的“Spring监控”菜单里能看到每个方法的调用次数和耗时。这个功能对定位Service层慢调用很有帮助,不过要注意切面会影响一点性能,不用全包扫描,定位到具体业务包即可。
5. 常见问题与排查经验
5.1 启动日志显示HikariCP而不是Druid
一种常见情况是:引入了starter,但是在日志里发现启动时打印的DataSource仍是HikariCP。原因通常是application.yml里的配置前缀不对,或者没显式指定spring.datasource.type。
检查思路:先确认引入的starter版本是否正常,然后在application.yml里明确写上:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource如果写了之后还是不行,排查配置类里是否手动创建了DataSource。有些项目里会写@Bean返回DataSource,这个时候自动配置就失效了,需要手动注入Druid的属性绑定。还有一种可能是自定义的DataSourceConfigurer里用了DataSourceBuilder,它会按照spring.datasource.hikari前缀去构建Hikari数据源,解决办法是改用DruidDataSourceBuilder。
5.2 监控页面能打开但登录后页面空白或统计为0
这个问题在Druid版本升级后比较常见。首先是页面空白:多半是访问的路径不对,正确路径是/druid/index.html,直接访问/druid会返回列表页但不是正常面板。其次是统计为0:确认web-stat-filter.enabled为true,并且URL过滤模式覆盖到了你的接口路径,同时检查exclusions是否把业务URL排除掉了。
还有一类隐蔽的问题:如果你用的是Spring Boot 3.x + Jakarta Servlet,Druid老版本(低于1.2.16)的监控Servlet会因为javax.servlet和jakarta.servlet包路径差异而注册失败,日志里看不到明显报错,但页面就是404。升级到1.2.20左右就能解决。
5.3 连接超时:GetConnectionTimeoutException
这个异常的字面意思是“在max-wait时间内没有从池里拿到连接”。排查看三类原因:
第一类是max-active太小,连接全被占满,这种情况看监控面板的活跃连接数是否顶到max-active;第二类是连接泄漏,有连接借出去没归还,这种情况看remove-abandoned日志里有没有长时间未归还的调用栈;第三类是数据库本身连接数打满了,比如MySQL的max_connections默认只有151,应用连接池加上管理工具连接一多就爆了,需要在数据库侧确认show variables like 'max_connections'。
如果排查后发现确实需要高并发,但max-active又不想给太大,可以在应用侧加上合理的连接获取等待时间,把max-wait调大到30000,让高峰期排队而不是立刻报错,同时配合监控做容量评估。
5.4 WallFilter拦截了正常SQL
WallFilter是Druid的防SQL注入防火墙,默认配置下它对某些写法比较敏感。常见的情况是:批量插入语句被拦、LIMIT后面带表达式被拦、自定义函数调用被拦。解决办法有两条路径:
如果你确认业务SQL是安全的,可以针对性的在配置里放行:
spring: datasource: druid: filter: wall: enabled: true config: multi-statement-allow: true condition-op-or-allow: true但我不建议一上来就全部放行,尤其是multi-statement-allow(多语句执行)和none-base-statement-allow(非基础语句),开启后防火墙基本形同虚设。更稳妥的方式是先看Wall面板里被拦截的SQL原文,分析是不是ORM框架生成的写法特殊,再做小范围放行。如果项目用的是MyBatis,参数绑定走的是PreparedStatement,大部分注入风险已经被框架拦截了,WallFilter更多是兜底。
5.5 慢SQL日志里的时间与业务实际体验不符
这种情况是:Druid记录某条SQL执行耗时2秒,但接口整体耗时5秒,多出来的3秒去哪了?很可能是连接获取等待时间。Druid的SQL监控记录的是“从连接上执行SQL”的耗时,不包含从池里获取连接的时间。所以当我看到SQL监控里没有慢SQL,但接口却慢时,第一反应就是去看数据源面板的“获取连接等待”相关指标,或者把整个请求链路加上Spring监控,确认耗时是不是卡在getConnection上。
解决这类问题往往不是优化SQL,而是调大max-active、减少连接占用时间,比如检查事务里有没有做耗时的外部HTTP调用。这是一个容易忽略的坑:有些人会把外部接口调用放在事务中间,连接数据库的事务一直占着连接不放,其他线程只能干等。
6. 实操中的几点个人体会
最后分享几个只有实际用下来才会注意到的细节。
第一,druid-spring-boot-starter的版本更新节奏不算快,但它和Spring Boot的兼容矩阵比较清晰,升级Spring Boot大版本时一定同步检查starter版本。我踩过Spring Boot 2.6升级到3.0后,旧版druid在注册监控Servlet时静默失败,排查了整整一个下午才发现是javax和jakarta的坑。
第二,Druid的监控数据默认是存在内存里的,应用重启就清零。想要保留历史数据做趋势分析,可以自己定时把DruidDataSourceStatManager里的统计信息采集到本地存储或监控平台。我自己写过一个定时任务,每5分钟拉一次指标存到MySQL,后面做性能分析好用得多。
第三,如果只是追求“最轻快的连接池”,HikariCP确实更快,但Druid赢在“可控”。连接池这种东西,不是越快越好,而是出了问题你能看得见、查得清。Druid的监控面板就像给数据库访问装了一个行车记录仪,平时用不上,出问题时它就是救命的那份录像。
第四,配置文件的优先级问题值得留意。Spring Boot的配置加载有优先级,如果你同时用application.yml和application-xxx.yml做环境隔离,一定要确认当前激活的profile里有没有覆盖掉druid的关键参数。我曾经遇到过生产环境max-active意外变成了2,就是因为profile配置文件里误写了一个覆盖值,上线后第一个高峰就把数据库连接打满了。
把这些细节都处理好之后,Druid在Spring Boot项目里会非常省心:跑起来几乎不需要太多代码,维护时又有数据可看、有日志可查。希望这篇整理能让你少走几个弯路。