Spring Boot 3正式版发布之后,我手上的老项目一直在评估升级。项目原来跑在Spring Boot 2.7上,数据库连接池一直用Druid,工具链也都围着Druid的监控体系转。本以为升级到Spring Boot 3最多改改依赖版本,结果实际操作下来差点被劝退:javax.servlet找不到、循环依赖起不来、监控页面404、配置参数全部不生效……零零散散的坑加起来,足足折腾了两三天才把整套环境理顺。这篇就把Spring Boot 3集成Druid过程中真正会踩的坑、报错原文、解决方式全部记录下来,同时把最终能直接用的配置模板也一并放出来。无论是正在做Spring Boot 3迁移的老项目,还是打算新项目直接上Boot 3加Druid的同事,这篇文章应该能帮你少走几圈弯路。
1. 先搞清楚:这个Druid到底是谁
1.1 项目升级背景与问题全景
老项目的技术栈比较常规:Spring Boot 2.7.18、JDK8、MySQL 8、MyBatis-Plus,连接池用的是阿里巴巴Druid,对应的Maven依赖是druid-spring-boot-starter,版本一直锁在1.2.6。整体运行了一年多,Druid的监控页面、慢SQL记录、防火墙功能都用得比较顺。
升级目标定的是Spring Boot 3.2.x加JDK17,Druid这边我提前看了一眼,发现官方从1.2.18开始推出了druid-spring-boot-3-starter,专门适配Spring Boot 3,于是就把版本提到1.2.20。本来以为换个坐标就行,结果启动时连续出现四类问题:
java.lang.ClassNotFoundException: javax.servlet.FilterBeanCurrentlyInCreationException,应用上下文循环依赖直接启动失败Failed to configure a DataSource: 'url' attribute is not specified- Druid监控页面404,
/druid/index.html怎么访问都是白屏
这些问题单独看任何一个都不算复杂,但它们会连环触发。比如javax问题没解决,后面循环依赖和url不识别都会跟着一起来。所以排查时必须按顺序来,不能看到报错就盲目改配置。
1.2 两个容易被忽略的前提
先说第一个容易混淆的点:连接池Druid和数据库Apache Druid是两码事。每次在社区搜“Druid性能对比”,搜出来的大都是Apache Druid和StarRocks、ClickHouse这类OLAP数据库的对比,不少人因此看懵了。本文说的Druid,是阿里巴巴开源的数据库连接池组件,负责管理JDBC连接、连接监控、SQL防火墙,跟OLAP数据库完全不是一类东西。这块概念先理清楚,后面搜索问题时才知道该往哪个方向查。
第二个前提是:Spring Boot 3把Java EE的包名从javax.*整体迁移到了jakarta.*。以Servlet为例,原来的javax.servlet.Filter变成了jakarta.servlet.Filter,javax.servlet.http.HttpServlet变成了jakarta.servlet.http.HttpServlet。Spring Framework 6和Spring Boot 3内部全部基于Jakarta EE 9展开,老版本的Druid starter内部编译时依赖的还是javax包,到了新的类加载环境下就找不到类了。这是大多数老连接池组件不兼容Boot 3的根源,理解了这一点,后面就能解释为什么必须换starter。
2. 环境准备与技术选型:别再用旧版starter了
2.1 JDK和Spring Boot版本选择
Spring Boot 3强制要求JDK17及以上,所以升级前先把JDK切到17或者21。我这次用的是JDK17,原因很实际:JDK21虽然也是LTS,但项目里部分第三方库的字节码增强、反射操作在21上偶尔会有兼容提醒,而JDK17是目前Spring Boot 3.x社区验证最广泛的运行环境。
创建项目时,IDEA里直接用Spring Initializr选Spring Boot 3.2.x、Java 17就行。这里有个细节:IDEA默认的Spring Initializr如果网络不畅,会很慢甚至拉不到依赖,建议改用自己的Maven镜像,或者在IDEA里设置自定义Initializr地址。我习惯直接建一个空的Maven项目,然后手动维护pom.xml,这样哪个依赖升级了、哪个版本有不兼容,心里都有数。
Spring Boot次版本的选择也有讲究。3.0.x是过渡版本,很多组件当时适配还不到位;3.1.x比较稳定,但对新特性的支持少一些;3.2.x是目前最主流的稳定线,AOT编译、虚拟线程等特性都有不错的表现。Druid从1.2.18开始支持Boot 3,我自己用下来,1.2.20这个版本在3.2.x上表现最稳,1.2.18和1.2.19在某些场景下还有过滤器和配置绑定的零碎问题。
2.2 Maven依赖的正确姿势
升级最核心的一步,就是把旧依赖替换掉:
<!-- 错误示例:这个坐标是给Spring Boot 2.x用的 --> <!-- <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> --> <!-- 正确示例:Spring Boot 3专用starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.20</version> </dependency>注意,druid-spring-boot-3-starter内部会传递依赖Druid核心包,版本一般和starter保持一致,不需要额外再显式引进一个druid依赖。如果你在pom里同时看到druid和druid-spring-boot-starter,一定要检查是否混用了两个不同代际的坐标,这是很多冲突的源头。
除了Druid之外,还有几个配套依赖需要同步调整。MySQL 8的驱动从mysql-connector-java改成了mysql-connector-j,坐标里的groupId也变了;MyBatis-Plus官方从3.5.3开始单独提供mybatis-plus-spring-boot3-starter,配Boot 3必须用它而不是老的mybatis-plus-boot-starter。这些细节单独看都不起眼,但组合在一起,就是Spring Boot 3升级瞬间从“换个版本号”变成“连环踩坑”的原因。
2.3 为什么Spring Boot 3会让老版Druid直接废掉
很多时候我们排错只看到了表面的ClassNotFoundException,没意识到它背后是整个Java EE命名空间的迁移。Spring Boot 3基于Spring Framework 6,Spring Framework 6基于Jakarta EE 9规范,Servlet API由javax.servlet变成jakarta.servlet。Druid的starter内部要加载com.alibaba.druid.support.http.stat.StatViewServlet等类,这些类在编译和运行时要依赖Servlet API,老版本底层绑定的是javax,放在Spring Boot 3环境里,类加载器自然找不到。
这不是单纯升级一个依赖版本就能跳过的问题,必须使用官方重新适配过的druid-spring-boot-3-starter。从Druid 1.2.18开始,官方同时维护两个分支的starter:一个继续服务Boot 2,一个专供Boot 3。使用新分支之后,内部的Servlet组件、自动配置类都会引用jakarta包,和Spring Boot 3的类加载机制才能对上。
3. 核心踩坑实战:四个报错的完整修复过程
3.1 报错一:java.lang.ClassNotFoundException: javax.servlet.Filter
报错日志最典型的一段长这样:
Caused by: java.lang.NoClassDefFoundError: javax/servlet/Filter at java.base/java.lang.ClassLoader.defineClass1(Native Method) ... Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter这个报错出现的位置通常在Druid启动时初始化过滤器,或者Tomcat容器装配Web组件时。原因很明确:项目classpath里没有javax.servlet.Filter这个类,因为Spring Boot 3内嵌的Tomcat 10+已经完全基于Jakarta Servlet。
解决方案就是把pom里的Druid依赖替换成druid-spring-boot-3-starter。但如果替换之后仍然报错,就要检查项目里是否存在其他遗留的Servlet依赖,比如老版本的servlet-api、javax.servlet.jsp-api,或者某些第三方包内部传递依赖了javax.servlet。我遇到过一种情况:某个内部工具包在pom里显式引了javax.servlet-api:4.0.1,结果Druid升级后依然报类找不到。这种就得在该工具包的依赖声明里排除掉javax.servlet-api,或者用mvn dependency:tree查一下冲突来源。
实操时建议先跑一下:
mvn dependency:tree -Dincludes=javax.servlet,jakarta.servlet把依赖树里所有javax.servlet相关的包找出来,逐个判断是哪些组件带进来的。能排除就排除,不能排除就需要评估该组件是否兼容Boot 3。
3.2 报错二:BeanCurrentlyInCreationException,循环依赖直接启动失败
升级过程中第二个拦路虎是启动报一堆循环依赖,日志中间有一段类似:
The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | druidDataSource (field private ...) └─────┘Spring Boot 2.6之后默认把spring.main.allow-circular-references设为false,Spring Boot 3延续了这个策略,一旦出现循环依赖就直接拒绝启动。而老项目里为了让Druid和一些基础配置共享数据源信息,以前写了不少“绕圈子”的Bean依赖,在Boot 2.6之前其实也能跑,升级之后就原形毕露了。
出现这个报错后,网上最常见的建议是:
spring: main: allow-circular-references: true这个配置的确能让项目先跑起来,但我强烈不建议一上来就加。循环依赖一旦放开,Spring IoC容器会以“三段式缓存”方式提前暴露Bean,这种模式在AOP代理、事务切面生效时很容易导致代理对象和原始对象不一致,出现事务不生效、切面只执行一次的诡异问题。
正确的处理思路是找到循环依赖的起源点。我的项目中循环依赖大的源头就是我自己的DruidConfig配置类:我手动声明了DruidDataSource的@Bean,同时又在另一个配置类里注入了这个DataSource来构建JdbcTemplate,而Druid的自动配置类也在初始化DataSource,多个配置路径互相引用,就形成了环。
解决方式有两种:
一是完全走Druid starter的自动配置,不自己写DruidDataSource的@Bean。当你在配置里写spring.datasource.url、spring.datasource.druid.url时,starter会自动声明好数据源,你只需要在配置类里用@Autowired或者构造器注入DataSource即可。这种方式最省心,也是官方推荐用法。
二是如果项目必须自定义DataSource,那就需要显式使用@ConfigurationProperties(prefix = "spring.datasource.druid")绑定参数,然后在创建Bean时把循环依赖的来源拆掉。比如JdbcTemplate不要直接注入自定义DruidDataSource,而是改注入DataSource接口类型,让Spring在容器中自动寻找合适的实现。
3.3 报错三:Failed to configure a DataSource: 'url' attribute is not specified
这个报错很经典,Spring Boot给出的提示是:
Description: Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class在我这次升级场景里,出现的原因不是没写url,而是配置路径没对上。老项目里原来用的是spring.datasource.url,同时还在pom里通过spring.datasource.type指定了com.alibaba.druid.pool.DruidDataSource。到了Boot 3,Druid starter的自动配置读取的是spring.datasource.druid.*前缀,和Boot默认的spring.datasource.*前缀两者叠加,很容易造成数据源类型被识别成Hikari或者空白,导致url参数没有被正确带入。
处理办法是统一配置前缀。如果用了druid-spring-boot-3-starter,最稳妥的写法是:
spring: datasource: druid: url: jdbc:mysql://localhost:3306/app_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver注意,driver-class-name在Druid starter里也可以写到spring.datasource.druid.driver-class-name下。如果同时保留spring.datasource.url和spring.datasource.druid.url,可能有版本差异导致优先级不可控,建议去掉前者,只保留后者。
另外还有一种情况是代码里手动new DruidDataSource()然后不设url。这种情况多见于网上零散示例代码,把配置写死在Java类里。升级到Boot 3后,建议一律改成配置文件绑定,方便统一管理。
3.4 报错四:Druid监控页面404
数据源正常跑起来了,下一步就是看监控页。默认访问http://localhost:8080/druid/index.html,结果404,页面资源根本找不到。这个问题的原因分两层:
第一层是druid-spring-boot-3-starter和老的druid-spring-boot-starter不同,监控页面默认是关闭的,必须显式打开stat-view-servlet。
第二层是即使开了,如果项目里同时引入了其他Web组件(比如knife4j、Spring Security),也没法直接访问。我升级的第二个阶段就遇到knife4j和Druid监控的路径冲突,knife4j会把所有/doc.html相关资源路径拦截下来,虽然不影响Druid的JSON接口,但Druid监控页面的静态资源一直被重定向,页面白屏。
正确的配置如下:
spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 allow: 127.0.0.1 deny: web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.css,*.gif,*.jpg,*.png,*.ico这里要特别提醒两点:
login-username和login-password一定要设,不要用默认的空密码。这个页面暴露了所有SQL执行记录和慢SQL明细,裸奔在公网上等于把数据库内部结构展示给别人。allow和deny的白名单配置要注意格式,是IP字符串,多个用逗号分隔。如果配了deny有值,且和allow有交集,deny优先级更高。
如果开了之后还是404,先排查是不是路径写错了,再用浏览器开发者工具看控制台,是HTML请求404还是静态资源404。如果是静态资源404,大概率是web-stat-filter的exclusions配置把CSS、JS文件过滤掉了,补充一下扩展名就行。
4. 最终可直接复用的配置模板
4.1 pom.xml完整依赖清单
把上面几个坑都踩平之后,我整理出了下面这套可复现的依赖清单。它适用于Spring Boot 3.2.x + JDK17 + MySQL 8 + MyBatis-Plus + Druid,如果项目里不用MyBatis-Plus,把对应坐标删掉即可。
<properties> <java.version>17</java.version> <spring-boot.version>3.2.5</spring-boot.version> <druid.version>1.2.20</druid.version> <mybatis-plus.version>3.5.5</mybatis-plus.version> <knife4j.version>4.4.0</knife4j.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>${knife4j.version}</version> </dependency> </dependencies>几个容易踩的版本联动点再强调一次:
druid-spring-boot-3-starter最低是1.2.18,但1.2.20更稳,1.2.23之后又合入过一些监控页面相关的修复,可以按需升级。mysql-connector-j是MySQL官方新坐标,千万别再用mysql-connector-java。- MyBatis-Plus必须用
mybatis-plus-spring-boot3-starter,否则会有SessionFactory初始化失败的问题。 - knife4j直接用适配Jakarta的
knife4j-openapi3-jakarta-spring-boot-starter,版本选4.4.0以上。
4.2 application.yml完整配置与参数解释
下面是我最终上线使用的配置,参数偏向生产环境,注释里写了每个参数的作用和取舍原因。
spring: datasource: druid: url: jdbc:mysql://localhost:3306/app_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # 初始化连接数:项目启动时立即建立的连接数,推荐与min-idle一致 initial-size: 5 # 最小空闲连接数:低于这个值会触发连接创建 min-idle: 5 # 最大活跃连接数:并发超过这个值,多余请求会排队等待 max-active: 20 # 获取连接的最大等待时间(毫秒),超过则抛出异常,避免线程无限阻塞 max-wait: 60000 # 检测空闲连接的间隔,单位毫秒 time-between-eviction-runs-millis: 60000 # 连接在池中最小空闲时间,超过才可能被回收 min-evictable-idle-time-millis: 300000 # 检测连接是否可用的SQL,MySQL用select 1,Oracle用select 1 from dual validation-query: SELECT 1 # 获取连接时是否检测有效性(推荐false,配合test-while-idle更高效) test-while-idle: true test-on-borrow: false test-on-return: false # 是否缓存PreparedStatement,PSCache对MySQL性能提升有限,但能降低CPU pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 # 配置监控统计拦截的filters,stat是监控统计,wall是SQL防火墙,log4j2是日志输出 filters: stat,wall,slf4j # 通过connectProperties属性来打开mergeSql和慢SQL记录 connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 # 监控页面 stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 allow: 127.0.0.1 deny: # Web应用监控过滤器 web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.css,*.gif,*.jpg,*.png,*.ico # 防火墙(Wall Filter)单独配置,防止SQL注入 wall: enabled: true config: multi-statement-allow: false none-base-statement-allow: false解释几个容易忽略的点:
max-wait建议设置,不设的话高并发下获取不到连接会快速失败或一直卡住;设了之后虽然会抛异常,但至少能及时暴露问题。test-while-idle配合time-between-eviction-runs-millis是最常见的连接可用性检测方式,不建议再把test-on-borrow打开,每次取连接都执行SELECT 1在高QPS场景下白白增加数据库负担。
filters里的stat必须写在最前面,否则监控数据统计不到;wall是SQL防火墙,一般建议生产开启,但如果你有大量多条SQL一次提交的需求,multi-statement-allow要谨慎打开,否则会被判断为非法语句直接拦截。
4.3 DruidConfig配置类写法(注意别重复注册)
使用druid-spring-boot-3-starter之后,理论上不需要手动声明DruidDataSource。但很多老项目确实喜欢自己创建一个配置类,方便动态添加过滤器或对数据源做额外处理。这里给出一版不会和自动配置冲突的写法:
@Configuration @Slf4j public class DruidConfig { /** * 手动声明数据源:完全接管Druid的初始化。 * 注意前缀必须是spring.datasource.druid,否则yml里的连接池参数绑定不上。 */ @Bean @ConfigurationProperties(prefix = "spring.datasource.druid") public DruidDataSource druidDataSource() { DruidDataSource dataSource = new DruidDataSource(); // 这里不需要再手动setUrl等,@ConfigurationProperties会自动绑定 return dataSource; } /** * 配置监控页面Servlet。 * 如果已经在yml里配置了stat-view-servlet.enabled=true,这个Bean可能重复注册,二选一即可。 */ @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet() { StatViewServlet servlet = new StatViewServlet(); ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(servlet, "/druid/*"); bean.addInitParameter("loginUsername", "admin"); bean.addInitParameter("loginPassword", "admin123"); bean.addInitParameter("allow", "127.0.0.1"); return bean; } /** * 配置Web监控过滤器,统计请求对应的SQL执行情况。 */ @Bean public FilterRegistrationBean<WebStatFilter> webStatFilter() { WebStatFilter filter = new WebStatFilter(); FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(filter); bean.addUrlPatterns("/*"); bean.addInitParameter("exclusions", "/druid/*,*.js,*.css,*.gif,*.jpg,*.png,*.ico"); return bean; } }这里有几点务必要注意:
- 手动声明
DruidDataSource之后,yml里如果同时也设置了spring.datasource.druid.stat-view-servlet.enabled=true,相当于监控Servlet被注册了两次。虽然大多数情况下不会启动失败,但日志里会出现重复绑定警告,某些版本下会导致监控页面提示“初始化错误”。建议要么全部走自动配置,要么全部手动注册,不要混用。 @ConfigurationProperties(prefix = "spring.datasource.druid")绑定方式在Spring Boot 3中对类型校验更严格,配置里如果写了不存在的参数名,启动时会直接报绑定失败,所以不要随意发明新的配置项。StatViewServlet在Boot 3环境下构造函数不需要参数,但如果你用的是老版本Druid源码改过来的,可能会发现构造器里引用了javax.servlet.http.HttpServlet,编译都过不了,这就是版本没换到位。
5. 常见问题与排查技巧实录
5.1 问题速查表
我把升级过程中反复遇到的分支问题整理成了表格,方便对照排查:
| 现象 | 直接原因 | 解决方案 |
|---|---|---|
ClassNotFoundException: javax.servlet.Filter | 依赖还是老版druid-spring-boot-starter,或存在javax.servlet旧包 | 换成druid-spring-boot-3-starter,排除javax.servlet依赖 |
启动报循环依赖BeanCurrentlyInCreationException | 自定义DataSource和自动配置互相引用 | 关闭自定义@Bean,完全走starter自动配置;临时用allow-circular-references: true兜底 |
Failed to configure a DataSource: 'url' attribute is not specified | 配置前缀混乱,spring.datasource.url和spring.datasource.druid.url混用 | 统一使用spring.datasource.druid.url,删除spring.datasource.type |
| 监控页面404 | stat-view-servlet未开启,或路径被Web组件拦截 | yml中stat-view-servlet.enabled: true,核对url-pattern和exclusions |
| 监控页面能打开但没数据 | web-stat-filter未开启,或stat过滤器没加 | yml中web-stat-filter.enabled: true,filters里包含stat |
| 慢SQL一直没有记录 | slowSqlMillis配了,但缺druid.stat.mergeSql | connection-properties中同时配置mergeSql=true和slowSqlMillis=2000 |
| 和knife4j一起用,Druid页面样式丢失 | knife4j默认拦截包含/**的部分静态资源路径 | 调整knife4j的静态资源映射,或把Druid监控放在独立端口 |
5.2 独家排坑的三个小技巧
第一个技巧是升级完先打印Druid的连接池指标。Druid的DruidDataSource本身有getActiveCount()、getPoolingCount()这些方法,写一个临时接口返回这些指标,确认连接池确实正常创建了,再去处理监控页面。
@RestController public class DruidHealthController { @Resource private DataSource dataSource; @GetMapping("/druid/health") public Map<String, Object> health() { if (dataSource instanceof DruidDataSource druidDataSource) { return Map.of( "activeCount", druidDataSource.getActiveCount(), "poolingCount", druidDataSource.getPoolingCount(), "maxActive", druidDataSource.getMaxActive() ); } return Map.of("type", dataSource.getClass().getSimpleName()); } }Spring Boot 3的instanceof模式匹配写起来很舒服,也正好能验证注入的数据源到底是不是Druid的实例。如果显示类型是HikariDataSource,说明自动配置被绕过去了,回去检查有没有多写spring.datasource.type。
第二个技巧是在升级过程中把Druid的日志级别临时打开。在application.yml里加一行:
logging: level: druid: sql: DEBUG启动时就能看到Druid内部的连接获取、归还、SQL执行过程。升级阶段日志量会很大,但排查问题非常直观;上线前记得关回INFO。
第三个技巧是用/actuator/configprops检查配置是否真的绑定到了Druid对象上。Spring Boot的actuator里有一个配置属性端点,可以查看某个Bean绑定的前缀有哪些参数。如果你的spring.datasource.druid没有出现在configprops里,说明自动配置没生效。使用前先确保依赖里加了spring-boot-starter-actuator,并打开端点:
management: endpoints: web: exposure: include: configprops,health,info这个方法特别适合排查明明写了max-active: 50,但连接池实际最大连接数还是20这种玄学问题。
5.3 和knife4j、MyBatis-Plus一起用可能踩的联动坑
这次升级顺带把接口文档工具从老版Swagger换成了knife4j,因为Spring Boot 3下knife4j的适配更顺畅。knife4j-openapi3-jakarta-spring-boot-starter依赖的是springdoc的Jakarta实现,和Druid监控本身没有直接冲突,但有一个静态资源的映射问题:knife4j默认会处理/webjars/**和/doc.html,如果它把/druid/**也纳入了某个拦截路径,Druid监控页面的CSS和JS就加载不出来。遇到这种情况,在knife4j配置里调整资源处理路径,或者给Druid监控单独指定一个独立端口,都能解掉。
MyBatis-Plus接入也很容易踩坑。老的mybatis-plus-boot-starter在Spring Boot 3下启动时大概率报Invalid value type for attribute 'factoryBeanObjectType',顺着日志排查会发现结果是MyBatis-Plus版本太老,没有适配MyBatis 3.5.x对Spring Boot 3的改动。换成mybatis-plus-spring-boot3-starter之后,还要确认Druid的wall过滤器不会拦截MyBatis-Plus内置的一些动态SQL语法,比如<script>标签里的多语句批量插入。如果遇到被拦截,可以适当配置wall的none-base-statement-allow为true,但一定要评估安全影响,不要无脑放开。
结尾
踩过这几轮坑之后,我的结论是:Spring Boot 3加Druid本身并不复杂,但版本细节必须死磕。我个人在实际操作中最看重的是三件事:一是一定要换druid-spring-boot-3-starter,不要抱着老坐标试图修修补补;二是不要自己重复声明DruidDataSource和监控Servlet,让starter的自动配置做它该做的事;三是监控页面别图省事,登录密码和IP白名单必须配齐。最后再分享一个小技巧:升级完成后,写一个故意慢的查询接口,比如SELECT SLEEP(3),跑到Druid监控页面去看慢SQL记录。如果这条SQL能出现在慢SQL列表里,说明整个Druid监控链路是通的;如果没出现,回头检查stat过滤器和slowSqlMillis配置。这个验证方法我每次升级完都会跑一遍,基本能覆盖80%的Druid集成问题。