news 2026/9/13 4:04:15

Spring Boot 3 集成 Druid 踩坑指南:从 javax 到 jakarta 的迁移实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 3 集成 Druid 踩坑指南:从 javax 到 jakarta 的迁移实战

把 Spring Boot 3 升级提上日程之后,我遇到的第一个硬茬不是业务代码,而是数据源。Spring Boot 3 基于 Spring Framework 6,底层的 Servlet 规范整体从 javax 换到了 jakarta,这一换,导致大量老版本的 starter 直接失效。今天这篇踩坑记录,就围绕 springboot3 集成 Druid 这个事,把我实际踩过的坑、查过的日志、试过的方案全捋一遍:从依赖选型和 javax/jakarta 冲突,到连接池被 Hikari 顶替、监控台 404、filter 不生效、慢 SQL 没输出,再到多数据源场景应该怎么处理。如果你是正在迁移 Boot3、或者刚在 IDEA 里建了一个 Spring Boot 3 项目准备接 Druid 连接池的 Java 开发者,这篇应该能帮你少走不少弯路。

1. 准备阶段先避雷:依赖选错直接起不来

1.1 先分清两个“Druid”——连接池还是 OLAP 数据库

搜资料之前先说明一个非常容易混淆的点。你搜“Druid 性能对比”,搜出来的结果大概率是 Apache Druid,那个负责 OLAP 分析、搞 Segment 和 Rollup 的列式数据库。而 Java 开发常说的 Druid,是阿里巴巴开源的数据库连接池com.alibaba:druid,两者除了名字一样没有任何关系。

这个乌龙我见过不止一次:有人按“Apache Druid”的教程配了半天,回头发现项目里根本没有这个依赖;也有运维同事问“你们接 Druid 是不是想把报表查询都扔进去”。所以开篇第一件事:确认你要的是com.alibaba:druid坐标,别把依赖坐标写错。

1.2 Boot3 项目必须引入 druid-spring-boot-3-starter

Spring Boot 2 时代,大家习惯了直接引com.alibaba:druid-spring-boot-starter,这个 starter 在 Boot 2 下一切正常。但放到 Spring Boot 3 里,如果还引这个旧 starter,项目会在启动过程中直接报ClassNotFoundException: javax.servlet.Filter,页面都进不去。

原因后面第 2 节详细说,这里先给结论:Boot3 项目请使用官方提供的专用 starter,坐标如下:

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.20</version> </dependency>

从 1.2.16 开始官方就有这个 Boot3 专用 starter 了,我这边用得最多的是 1.2.20,后面如果有新版本建议直接往上升。注意,这个 starter 本身对 Spring Boot 3.0、3.1、3.2、3.3 基本都是兼容的,因为 Boot3 的自动装配机制没有大变,变的只是包名和低版本 JDK 支持。

另外如果你是 Gradle 项目,坐标一样,写成implementation 'com.alibaba:druid-spring-boot-3-starter:1.2.20'就行。

1.3 用 mvn dependency:tree 确认没有两套 Servlet API 打架

依赖选对了只是第一步。实际项目里很容易因为某个传递依赖把老 jar 带进来,所以我每新建或升级一个 Boot3 项目,都会先跑一遍依赖树:

mvn dependency:tree -Dincludes=javax.servlet:javax.servlet-api mvn dependency:tree -Dincludes=jakarta.servlet:jakarta.servlet-api

正常情况,Boot3 项目里只应该出现jakarta.servlet相关的 jar,javax.servlet一行都不该有。如果出现了,八成是某个老 starter 传递进来的,把它排除掉,否则后面会遇到各种“启动一半就挂”的怪问题。我遇到过最离谱的一次,是旧版本的 MyBatis 分页插件把javax.servlet-api带了进来,结果 Tomcat 启动时两个 Servlet 容器类互相干扰,报的错根本看不出是依赖问题。

2. 启动期最刺眼的报错:javax.servlet 全家桶集体失踪

2.1 报错日志长什么样

用错依赖的时候,Spring Boot 3 启动会在 Web 容器初始化阶段直接抛异常,典型日志是这些:

java.lang.ClassNotFoundException: javax.servlet.Filter java.lang.NoClassDefFoundError: javax/servlet/ServletContextListener

有时候还会故意伪装成别的错,比如Failed to instantiate [org.springframework.boot.web.servlet.ServletContextInitializer],但往 Cause 里翻一定能看到 javax 的字样。我第一次看到这个报错时,第一反应是“这个类我没用过啊”,然后花了不少时间去看自己写的 Filter,完全走偏了。

2.2 根因:Spring Boot 3 全面迁到 jakarta 命名空间

这个坑的根因要从 Servlet 规范的变迁说起。一直以来的 Java Web 开发,Servlet 相关的类都在javax.servlet包里,大家写 Filter、Listener 都这么写习惯了几十年。但从 Jakarta EE 9 开始,整个命名空间从javax.*迁移到了jakarta.*,Spring Boot 3 底层用的是 Jakarta Servlet 5.0,所以运行时容器里只有jakarta.servlet,没有javax.servlet

老版本的druid-spring-boot-starter是照着 Boot2 生命周期编译的,它的自动配置类里静态引用了javax.servlet下的类。类加载的时候找不到这些类,直接抛上面那串异常。

这不是版本号冲突那种简单“打架”,而是整个坐标体系换了。所以千万不要想着手动塞一个javax.servlet-api进依赖来“补全”,那只会让程序里同时存在两套 Servlet API,JVM 加载哪个全看运气,行为更不可控。正确做法就一条:所有第三方组件都用支持 Jakarta 的新版本。

2.3 和 knife4j 是同款问题,解决方案一并说

这里顺带提一个 Boot3 迁移时几乎人人都会撞的组件:knife4j。它的报错机制和 Druid 一模一样,也是因为老版本基于 javax 和 SpringFox,Boot3 下根本起不来。解决方案也同样简单——用 4.x 的 Jakarta 版本:

<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.5.0</version> </dependency>

很多人在同一个项目里同时报 Druid 和 knife4j 的错,其实根因都是同一个:javax → jakarta 迁移。把这条底层逻辑理清之后,Boot3 生态里很多“莫名奇妙”的依赖报错都能一眼看穿。

3. 连上数据库后发现日志里还是 HikariPool,Druid 被“截胡”

3.1 现象描述与第一反应

依赖换对了,项目顺利启动,数据库也能连上,看起来一切正常。但我随手翻了翻启动日志,发现里面有这样一条:

Starting HikariPool ...

我当时心里一紧:我明明引入的是 Druid starter,怎么跑的还是 Hikari?更别提打开/druid/index.html之后,SQL 监控列表干干净净,一行记录都没有。这是 Boot3 + Druid 集成里最隐蔽的一类坑:项目能用,但是 Druid 根本没成为真正在用的连接池。

3.2 三种典型的截胡原因

后来我总结下来,Druid 被“静默顶替”基本是下面三种情况之一:

第一种,HikariCP 依赖还在 classpath 里,而spring.datasource.type没有显式声明。Spring Boot 的自动装配逻辑里,DataSource 的创建是“谁的条件先满足谁上”,类路径里有 Hikari 时它经常抢跑。稳妥做法是把 Hikari 直接排除掉,或者显式声明type,两边都做最保险。

第二种,项目里存在自定义的@Bean DataSource配置类。比如老项目迁移时,把 Boot2 时代手写的DruidConfig一起复制了过来,里面用DataSourceBuilder或直接new HikariDataSource()创建了一个 dataSource。手动注册的 bean 优先级高于 starter 的自动装配,你的 Druid 配置根本轮不到执行。

第三种,依赖树里看着很正常,但是spring-boot-starter-jdbc或者spring-boot-starter-data-jpa把 HikariCP 作为默认连接池带进来了。Spring Boot 的默认连接池就是 Hikari,这是它文档里写死的行为。

排查方法也很直接:先跑mvn dependency:tree看 Hikari 的依赖来源,再全局搜一下项目里有没有自己声明的DataSourcebean。我那次就是项目里藏了一个老同事写的数据源配置类,把 starter 的自动装配完全盖住了。

3.3 我的最终配置模板(单数据源版)

经过几次折腾,我目前的单数据源配置模板长这样,直接抄就能用:

spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: root druid: db-type: mysql initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 keep-alive: true validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20

这里有个很多人问的点:urlusernamepassword放在spring.datasource下面,而池子参数放在spring.datasource.druid下面,为什么不直接全放druid子树里?

因为 starter 的自动配置是先读取spring.datasource下的公共数据源属性,再通过@ConfigurationProperties("spring.datasource.druid")把 Druid 特有属性绑定上去。你如果非把url写进druid子树,某些版本下会绑定不到,启动时直接报Failed to configure a DataSource: 'url' attribute is not specified。所以按上面这个结构来,最稳。

另外,如果你是 MySQL 8,driver-class-name必须用com.mysql.cj.jdbc.Driver,老的那个com.mysql.jdbc.Driver已经被移除了。URL 里的allowPublicKeyRetrieval=true也不能省,否则 MySQL 8 默认的caching_sha2_password认证方式会报Public Key Retrieval is not allowed,这个报错在网上被问烂了。

4. 监控台打不开:StatViewServlet 注册这件事

4.1 最省事路线:starter 的 stat-view-servlet 配置

Druid 最吸引人的地方就是带了一个可视化监控台,能看到连接池实时状态、SQL 执行统计、URI 访问统计。但不少人把依赖配好之后,访问/druid/index.html得到一个大大的 404。

如果你用的是druid-spring-boot-3-starter,监控台的开启其实不用写一行 Java 代码,配置里把开关打开就行:

spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin reset-enable: false

然后重启项目,浏览器访问http://localhost:8080/druid/index.html,就能看到登录页。这里提醒一下:如果项目里配了server.servlet.context-path,比如/api,那入口地址就是/api/druid/index.html,别在/druid/index.html上找半天。

重置功能reset-enable我建议设成false,防止有人恶作剧一键把监控统计清空。生产环境里login-usernamelogin-password不要用弱口令,这东西直接暴露了所有 SQL 语句和调用频率,属于敏感信息。

4.2 手动注册 Servlet 时容易踩的两个点

网上很多教程教你在配置类里手动注册StatViewServlet

@Configuration public class DruidConfig { @Bean public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() { ServletRegistrationBean<StatViewServlet> registration = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); registration.addInitParameter("loginUsername", "admin"); registration.addInitParameter("loginPassword", "admin"); registration.addInitParameter("resetEnable", "false"); return registration; } }

这条路在 Boot3 下目前也是能走的,只要你用的 Druid 是 1.2.16 之后的核心包。但我自己不太推荐手写,原因有两个。

第一个坑是“双重注册”。如果你同时开了 yaml 里的stat-view-servlet.enabled: true,又手写了这个 bean,某些版本会报 Servlet 名称冲突,或者监控页面出现统计重复。这种问题排查起来很烦,因为日志里给的错误信息并不直观。

第二个坑是老教程的 import 问题。很多博客写的是import javax.servlet.*相关代码,拿到 Boot3 项目里直接编译不过。改的时候要注意,ServletRegistrationBean这个类在 Spring Boot 3 里并没有换包名,但你自己写的 Filter 或者监听器如果要实现接口,必须换成jakarta.servlet下的类型。IDEA 的自动导入经常给出一长串候选,一定要选jakarta.servlet开头的那个。

4.3 WebStatFilter 没配好,监控数据照样是 0

有一个比监控台 404 更隐蔽的问题:监控台能打开,登录也能进,但页面上的 “URI 监控”“Session 监控” 全是空的。这个基本就是 WebStatFilter 没启用。

WebStatFilter 负责的是 Web 层的访问统计,它跟统计 SQL 执行的 StatFilter 是两码事。配置同样走 yaml:

spring: datasource: druid: web-stat-filter: enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' session-stat-enable: true profile-enable: true

exclusions必须把/druid/*排除掉,否则监控页面本身的请求也会被算进访问统计里,数据会非常难看的。另外这个排除列表是用逗号分隔的,不要加空格,加了空格可能导致静态资源还是被过滤拦截,页面加载慢不少。

5. 过滤器和慢 SQL:StatFilter/WallFilter 被静默跳过的现场

5.1 filters 简写在 Boot 场景下为什么不靠谱

老一代 Druid 教程都喜欢写一句配置:

spring: datasource: druid: filters: stat,wall,log4j2

这种写法在纯 Spring 项目里没有任何问题,Druid 会自己解析这个名字列表然后加载对应的过滤器。但在 Spring Boot 的自动装配场景下,这套简写经常“半生效”:统计页里 Stat 数据缺斤少两,Wall 防火墙有时开有时关,而且你很难说是哪里配置冲突了。

我个人的建议是:Boot 项目里直接放弃filters简写,改用filter.*属性树逐项开关,声明式配置更可控,也方便别人阅读和维护:

spring: datasource: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 2000 merge-sql: true wall: enabled: true slf4j: enabled: true

这里有个小细节:filter.stat.enabled默认其实是true,但如果你在filters简写里也写了stat,两边就会出现两份配置在打架的情况。踩过这个以后,我所有项目都只用filter.*这一种方式描述过滤器。

5.2 慢 SQL 统计没输出的三板斧排查

log-slow-sql: true配好之后,如果慢 SQL 日志依然没出来,按下面三步排查,基本能定位:

第一,确认阈值设置合理。slow-sql-millis: 2000表示超过 2000 毫秒才算慢 SQL。测试时可以先故意跑一个几百毫秒的查询,如果没触发再往下查。

第二,确认日志级别没被压掉。慢 SQL 是通过com.alibaba.druid.filter.stat.StatFilter这个 logger 打印的,如果你的全局日志级别被调到了 ERROR,那 WARN 级别的慢 SQL 日志就看不到了。建议在配置里确认:

logging: level: com.alibaba.druid.filter.stat.StatFilter: warn

第三,确认 SQL 真的经过了 Druid 数据源。回到第 3 节说的,如果你的应用实际用的是 Hikari,那 StatFilter 压根没被挂上去,配置什么都是白搭。判断方法最简单:看/druid/sql.html页面有没有 SQL 记录,有记录说明链路通了,没有记录就回头查数据源到底是不是 Druid。

顺带一提,merge-sql: true会把结构相同的 SQL 合并统计,比如只差查询条件的语句会被归到一条记录里,这样统计页面不会爆炸式增长。但合并之后看单条 SQL 的平均耗时意义会变小,这个要心里有数。

5.3 WallFilter 报错和 dbType 探测失败的连带问题

WallFilter 是 Druid 的 SQL 防火墙,可以拦截很多注入攻击。但开着 WallFilter 的时候,有一个经典报错:

java.sql.SQLException: could not load dbType

这个报错看着吓人,其实根因是 WallFilter 在做 SQL 解析时拿不到数据库类型。Druid 默认会从连接获取元数据推断数据库类型,但在某些场景下这个过程会失败——比如连接还没建立成功、或者驱动返回的 metadata 不完整。

解决方案就是在配置里手动指定数据库类型,也就是第 3 节模板里的db-type: mysql。如果你用的是 PostgreSQL 或 Oracle,改成对应的pgsqloracle就行。

另外我自己在把wall过滤器用于生产环境的时候,会对multi-statement-allow格外留意。这个参数控制是否允许多条 SQL 用分号拼在一起执行,默认是 false,也就是不允许。某次项目里用到了批量执行的 SQL,一开 WallFilter 就开始报错,最后发现是这个参数挡住了。改之前要想清楚你是不是真的需要多条 SQL 拼接,如果只是批量插入,正规做法是用addBatch,而不是去开这个开关。

6. 多数据源与后续扩展:starter 自动装配的边界

6.1 多数据源时 druid starter 自动装配为什么不够用

druid starter 的自动装配逻辑,本质上只处理“一个 DataSource”的场景。当你的项目需要连接两套数据库,或者读写分离主从两个数据源时,自动装配的单个 dataSource bean 就满足不了需求了。

这时候有两个选择。第一,继续用 starter 管理主数据源,第二个数据源手动创建;第二,干脆全部手动创建,把配置从spring.datasource.druid.*里拆出来,放到自定义的前缀下面,逐个绑定。

我个人的习惯是第二套方案,因为它在多数据源场景下更直观,也是个排障友好的结构:

app: datasource: primary: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo username: root password: root db-type: mysql initial-size: 5 max-active: 20 secondary: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo_archive username: root password: root db-type: mysql

对应的 Java 配置:

@Configuration public class DataSourceConfig { @Bean(name = "primaryDataSource", initMethod = "init") @ConfigurationProperties(prefix = "app.datasource.primary") public DruidDataSource primaryDataSource() { return new DruidDataSource(); } @Bean(name = "secondaryDataSource", initMethod = "init") @ConfigurationProperties(prefix = "app.datasource.secondary") public DruidDataSource secondaryDataSource() { return new DruidDataSource(); } }

6.2 手动声明时容易漏掉的几个点

手动创建DruidDataSource的时候,有三个点稍微不注意就会掉进坑里。

第一,initMethod = "init"不能省。DruidDataSource 有一个 init 方法负责真正建立连接池,Spring 托管时如果不声明 initMethod,连接池可能不初始化。你自己在代码里new DruidDataSource()然后不用 Spring 管理的话,则要手动调用 init,很多新版 IDE 还会给你标黄提示。

第二,如果监控台还要继续用,注意两个数据源都要能区分开。Druid 的连接池监控页可以聚合显示多个数据源,但展示时靠的是数据源内部的名字,建议在配置里给每个数据源设置清晰的名字。否则页面上全是dataSource-1dataSource-2这种,你根本分不清是哪个库。

第三,多数据源场景下,MyBatis 或 JPA 的SqlSessionFactory也要分别绑定到对应的 dataSource,这块属于老生常谈,但每次都会有人忘了配。

7. Boot3+Druid 常见异常速查表

最后把这一年多踩坑经验汇总成一张表,遇到了直接对照着看,省得再翻一遍日志。

症状根因处理办法
启动报ClassNotFoundException: javax.servlet.Filter引了 Boot2 时代的 druid-spring-boot-starter换成 druid-spring-boot-3-starter,1.2.16 以上
启动日志出现Starting HikariPool...HikariCP 未被排除,或自定义 DataSource bean 覆盖了自动装配排除 Hikari 依赖、删掉/config 自定义数据源 bean、显式声明 type
启动报Failed to configure a DataSource: 'url' attribute is not specifiedurl 没写,或写错层级把 url/username/password 放到 spring.datasource 下
/druid/index.html404stat-view-servlet 没启用,或没带 context-path检查 yaml 配置、确认入口前缀
监控台能看到页面但 SQL 全空filter.stat 没启用,或应用实际用的是 Hikari开启 filter.stat.enabled,确认数据源是 Druid
开了 log-slow-sql 但没日志logger 级别被压低,或 SQL 没走 Druid调整 StatFilter 日志级别,确认连接池链路
Public Key Retrieval is not allowedMySQL 8 的 caching_sha2_password 认证URL 加allowPublicKeyRetrieval=true
could not load dbTypeDruid 无法自动推断数据库类型手动配置db-type
引入 knife4j 后启动失败用了 Boot2 时代的 knife4j 版本换 4.x 的 openapi3-jakarta starter
提示 Servlet 重复注册或统计重复即开了 yaml 开关又手动注册了 StatViewServlet只保留一种注册方式

最后聊两句我现在的工作习惯。新项目接 Druid,我基本是“starter + yaml 一条龙”,不再手写数据源配置类,除非真有多数据源需求。老项目升级 Boot3,我第一件事永远是跑依赖树、找 javax 相关传递依赖,然后才是改业务代码。慢 SQL 排查我也不是只看日志,而是先把 Druid 监控台的 SQL 页面当第一道筛子,从里面看命中次数和耗时分布,再决定要不要针对某条 SQL 深挖。这套流程踩了这么多坑之后跑下来,已经稳定很长一段时间了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 4:03:11

RemoveWindowsAI 社区支持避坑指南

RemoveWindowsAI 社区支持避坑指南 【免费下载链接】RemoveWindowsAI Force Remove Copilot, Recall and More in Windows 11 项目地址: https://gitcode.com/GitHub_Trending/re/RemoveWindowsAI 脚本跑得正顺&#xff0c;结尾突然弹出一串报错&#xff0c;搜索引擎一无…

作者头像 李华
网站建设 2026/9/13 4:02:56

Lexe 性能调优实战:改对几个环境变量,Lambda 冷启动快 23 倍

Lexe 性能调优实战&#xff1a;改对几个环境变量&#xff0c;Lambda 冷启动快 23 倍 【免费下载链接】RemoveWindowsAI Force Remove Copilot, Recall and More in Windows 11 项目地址: https://gitcode.com/GitHub_Trending/re/RemoveWindowsAI Lambda 冷启动又慢又贵…

作者头像 李华
网站建设 2026/9/13 4:02:51

砂锅木瓜:CookLikeHOC 砂锅菜系列中的陈皮冰糖糖水甜品复刻指南

砂锅木瓜&#xff1a;CookLikeHOC 砂锅菜系列中的陈皮冰糖糖水甜品复刻指南 【免费下载链接】CookLikeHOC &#x1f962;像老乡鸡&#x1f414;那样做饭。已添加2026年发布的《老乡鸡菜品溯源报告 2.0中新出现的菜品。主要部分于2024年完工&#xff0c;非老乡鸡官方仓库。文字来…

作者头像 李华
网站建设 2026/9/13 4:02:06

RK3399 Android 7.1 实现 WiFi STA+AP 并发全栈解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 4:00:51

Arm mango:面向Arm嵌入式项目的源码工程健康规范

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华