《Spring Boot 入门》附录 D:常见问题排查

本附录是按症状索引的排查手册,把启动、配置、数据库与事务、测试四类共二十余个常见问题整理成「症状 → 可能原因 → 排查步骤 → 解法 → 详见章节」,覆盖端口占用、Bean 创建失败、配置不生效、YAML 报错、连接超时、事务不回滚、测试注解变更等,末尾附错误关键字总索引表。

本篇目标:按症状而不是按知识体系组织排查方法,把启动、配置、数据库与事务、测试四类问题整理成可直接照做的清单,并在末尾给出错误关键字到章节的总索引。
适用版本:Spring Boot 4.1.x(Java 21)

附录 D 常见问题排查

排查 Spring Boot 问题的第一原则:先读日志的最后 30 行。启动失败时,框架抛出的异常链非常完整,Caused by 会一路指到根因,绝大多数问题在日志里已经写明了答案。第二原则:从最具体的异常往上看,别被最外层的包装异常带偏。

本附录按症状索引。每条给出「症状 → 可能原因 → 排查步骤 → 解法 → 详见章节」五段。建议先用 D.5 的关键字表定位到小节,再逐条核对。

D.0 通用排查流程

在跳进具体症状之前,先固定一套顺序,能省下大量试错时间。

  1. 读日志尾部。启动失败时,异常链的最后一段 Caused by 就是根因;不要从最外层的包装异常开始猜。
  2. 定位是哪个阶段失败。是环境(JDK、Maven)、启动装配(Bean、端口、数据源)、运行期(SQL、事务),还是测试阶段?阶段不同,排查入口不同。
  3. 看「实际生效值」而不是「你以为写的值」。配置类问题几乎都能靠 /actuator/env、/actuator/configprops 或启动时的 --debug 报告还原真相。
  4. 复现到最小。把问题缩到一个最小的可运行例子,往往在缩的过程中答案就出现了。
  5. 一次只改一处。同时改多个配置会让「哪个改动起了作用」无法归因。

下面四类按这个流程组织,每一条都能独立照做。

D.1 启动类问题

启动阶段的问题集中在端口、Java 环境、主类、Bean 装配与数据源五处。

D.1.1 端口被占用:Port 8080 was already in use

  • 症状:启动到内嵌 Tomcat 阶段失败,日志出现 Web server failed to start. Port 8080 was already in use.
  • 可能原因:上一次启动的进程没退干净;或同机跑了另一个服务。
  • 排查步骤:lsof -i :8080 找出占用进程;ps 确认是不是残留的 Java 进程。
  • 解法:结束旧进程,或改 server.port;集成测试里常设 server.port: 0 让系统随机分配。
  • 详见:3.2 内嵌服务器与启动过程 。

D.1.2 JAVA_HOME 未生效,编译或启动用了错的 JDK

  • 症状:java -version 显示的版本不是 21;或 Maven 报 release version 21 not supported。
  • 可能原因:JAVA_HOME 指向旧 JDK;shell 里 PATH 与 JAVA_HOME 不一致。
  • 排查步骤:echo $JAVA_HOME 与 java -version 对照;mvn -version 会打印它实际使用的 JDK。
  • 解法:把 JAVA_HOME 指向 JDK 21 的安装目录,重新开一个终端;Spring Boot 4.x 要求 Java 17+,本系列统一用 21。
  • 详见:2.1 JDK 与 Maven 环境准备 。

D.1.3 Unable to find a suitable main class

  • 症状:打包或运行时报 Unable to find a single main class from the following candidates。
  • 可能原因:工程里有多个带 main 方法的类,或有多个 @SpringBootApplication。
  • 排查步骤:搜索 public static void main,确认只有一个入口;检查是否误留了测试用的启动类。
  • 解法:删掉多余入口,或在插件里显式指定主类。
  • 详见:3.3 可执行 jar 。
<!-- 显式指定主类 -->
<configuration>
    <mainClass>com.example.book.BookApplication</mainClass>
</configuration>

D.1.4 Bean 创建失败:BeanCreationException

  • 症状:启动抛 org.springframework.beans.factory.BeanCreationException,外面套了好几层。
  • 可能原因:某个 Bean 的构造方法或 @PostConstruct 里抛了异常;配置项缺失导致初始化失败。
  • 排查步骤:从日志最底部的 Caused by 开始读,那里才是真正的根因;确认报错 Bean 的名字,回到它的定义处。
  • 解法:修掉根因异常,而不是在异常外面加 try/catch 掩盖。
  • 详见:5.1 IoC 容器与 Bean 。

D.1.5 No qualifying bean of type

  • 症状:No qualifying bean of type 'com.example.XxxService' available。
  • 可能原因:类没加 @Component / @Service;或它所在的包不在主类所在包的子包里,没被组件扫描覆盖。
  • 排查步骤:确认类上的注解;对照主类包路径,检查目标类是否在其之下。
  • 解法:补注解,或把类移到被扫描的包;必要时用 @ComponentScan 扩展范围。
  • 详见:5.2 依赖注入与作用域 。

D.1.6 循环依赖:BeanCurrentlyInCreationException

  • 症状:启动报 The dependencies of some of the beans in the application context form a cycle。
  • 可能原因:两个 Bean 互相构造注入。Spring Boot 2.6 起默认禁止循环引用。
  • 排查步骤:日志会画出依赖环(A ──> B ──> A),顺着它找出互相依赖的两个类。
  • 解法:优先重构——把公共逻辑抽成第三个 Bean;退而求其次用 @Lazy 打断环,或改 setter 注入。不要图省事打开 spring.main.allow-circular-references=true。
  • 详见:5.2 依赖注入与作用域 。

D.1.7 Failed to configure a DataSource

  • 症状:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
  • 可能原因:classpath 上有 JPA/JDBC,但既没配 spring.datasource.url,也没有可用的内嵌数据库。
  • 排查步骤:确认依赖里是否有 spring-boot-starter-data-jpa;确认有没有配 url;确认是否加了 H2 之类内嵌库。
  • 解法:补上 spring.datasource.url,或加入 H2;如果这个模块根本不需要数据库,用 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 排除。
  • 详见:12.1 数据源配置 。

D.2 配置类问题

配置相关的问题最隐蔽,因为「配置没生效」通常不报错,只是行为不对。

D.2.1 配置写了但没生效

  • 症状:改了 application.yml,行为却和没改一样。
  • 可能原因:键名拼错;被更高优先级的来源(命令行、环境变量)覆盖;文件不在被加载的位置。
  • 排查步骤:启动时加 --debug 查看自动配置报告;引入 Actuator 后用 /actuator/configprops 与 /actuator/env 看实际生效值与来源。
  • 解法:以 /actuator/env 的生效值为准,逐层核对来源优先级。
  • 详见:6.3 外部化配置与优先级 。

D.2.2 YAML 语法报错:cannot start any token

  • 症状:while scanning for the next token found character '\t' that cannot start any token,或 mapping values are not allowed here。
  • 可能原因:YAML 里用了 Tab 缩进(这是最高频的原因);或冒号后没留空格、缩进层级不一致。
  • 排查步骤:让编辑器显示不可见字符,搜索 Tab;对照报错里的行号看缩进。
  • 解法:YAML 一律用空格缩进;key: value 冒号后必须有空格。
  • 详见:6.1 YAML 与 profile 。

D.2.3 profile 没激活

  • 症状:application-dev.yml 里的配置没起作用。
  • 可能原因:spring.profiles.active 没设,或被启动参数覆盖;profile 名拼写不一致。
  • 排查步骤:看启动日志第二行——No active profile set, falling back to 1 default profile: "default" 说明没激活;The following 1 profile is active: "dev" 才是生效了。
  • 解法:用 --spring.profiles.active=dev 或环境变量激活,并核对文件名 application-dev.yml 的拼写。
  • 详见:6.1 YAML 与 profile 。

D.2.4 环境变量没被识别

  • 症状:设置了 SPRING_DATASOURCE_URL,应用读到的还是 yml 里的旧值。
  • 可能原因:变量名不符合 relaxed binding 规则;变量没导出到当前进程。
  • 排查步骤:printenv | grep SPRING 确认变量存在;核对转换规则——spring.datasource.hikari.maximum-pool-size 对应 SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE(点变下划线、kebab-case 转大写下划线)。
  • 解法:按规则改写变量名;容器里注意变量要传给应用进程。
  • 详见:6.3 外部化配置与优先级 。

D.2.5 InvalidConfigDataPropertyException

  • 症状:启动直接失败,提示 Property 'spring.profiles.active' imported from location ... is invalid in a profile specific resource。
  • 可能原因:把 spring.profiles.active 写进了 application-xxx.yml 这类 profile 专属文档里,而该属性只允许出现在主配置中。
  • 排查步骤:在 profile 专属文件里搜索 spring.profiles.active 或 spring.profiles.include。
  • 解法:把它移到主 application.yml,或用 spring.config.activate.on-profile 配合正确的属性组织方式。
  • 详见:6.1 YAML 与 profile 。

D.2.6 多份配置文件的加载顺序混乱

  • 症状:同一属性在多个文件里都有,生效的却是「不应该生效」的那份。
  • 可能原因:不清楚外部化配置的优先级;或 profile 专属文件与主文件同名键冲突。
  • 排查步骤:记住大方向——命令行参数 > 环境变量 > 外部配置文件 > 打包进 jar 的配置;/actuator/env 会明确列出每个来源。
  • 解法:把差异项收敛到 profile 文件,通用项留在主文件,避免同名键在多处定义。
  • 详见:6.3 外部化配置与优先级 。

D.3 数据库与事务类问题

这一类问题往往到运行期才暴露,排查时要同时看应用日志与数据库状态。

D.3.1 连接超时或连接被拒

  • 症状:HikariPool-1 - Connection is not available, request timed out,或 Connection refused。
  • 可能原因:数据库没启动;url / 端口 / 主机写错;连接池被耗尽;防火墙拦截。
  • 排查步骤:先用 psql / mysql 等客户端直连验证网络与凭据;再看 /actuator/metrics/hikaricp.connections.active 判断是否池满。
  • 解法:修 url 或启动数据库;池被耗尽时排查长事务与未关闭的连接,再评估调大 maximum-pool-size。
  • 详见:12.1 数据源配置 。

D.3.2 Table not found

  • 症状:运行期查询报 Table "BOOK" not found(H2)或 relation "book" does not exist(PostgreSQL)。
  • 可能原因:ddl-auto 为 none 且没有迁移脚本;迁移脚本没被执行;实体映射的表名与真实表名不一致。
  • 排查步骤:查 flyway_schema_history 看脚本是否执行;用 @Table(name = "...") 对照实体与真实表名。
  • 解法:补迁移脚本并确认 Flyway starter 已引入;修正 @Table 映射。
  • 详见:15.1 Flyway 入门 。

D.3.3 ddl-auto 误用

  • 症状:生产重启后数据丢失(create / create-drop);或改字段名后旧列残留、查询结果为空(update)。
  • 可能原因:把开发期的 create-drop 或 update 带到了生产。
  • 排查步骤:检查生效配置里的 spring.jpa.hibernate.ddl-auto。
  • 解法:有迁移脚本时只取 validate 或 none;绝不用 update 让 Hibernate 与迁移脚本争抢 schema。
  • 详见:15.1 Flyway 入门 。

D.3.4 事务没有回滚

  • 症状:方法抛异常,数据却已经写进去了。
  • 可能原因:抛的是受检异常(默认只回滚运行时异常);方法自调用绕过代理;方法不是 public;事务方法被同类内部调用。
  • 排查步骤:确认异常类型;确认调用是否经过 Spring 代理(跨 Bean 调用才生效)。
  • 解法:需要回滚受检异常时用 @Transactional(rollbackFor = Exception.class);把事务方法抽到独立 Bean 再调用。
  • 详见:14.3 事务失效的常见场景 。

D.3.5 懒加载异常:LazyInitializationException

  • 症状:could not initialize proxy - no Session。
  • 可能原因:open-in-view 关了,又在事务外访问未加载的关联对象。
  • 排查步骤:确认 spring.jpa.open-in-view 的值;定位访问关联属性的代码位置。
  • 解法:在事务内用 join fetch 或 @EntityGraph 提前加载;或改用 DTO 投影,别把实体直接抛到事务外。
  • 详见:13.1 关联映射 。

D.3.6 N+1 查询

  • 症状:一次列表查询在日志里打出成百上千条 SQL,接口响应变慢。
  • 可能原因:循环里逐个访问懒加载关联,每条触发一次查询。
  • 排查步骤:打开 SQL 日志(logging.level.org.hibernate.SQL: debug)数一数条数。
  • 解法:用 join fetch / @EntityGraph 一次取回;或设 hibernate.default_batch_fetch_size 批量加载。
  • 详见:13.2 JPQL 与原生 SQL 。

D.3.7 时间字段时区错乱

  • 症状:入库时间比本地时间差 8 小时,或同一时刻在不同环境显示不一致。
  • 可能原因:JVM 时区、数据库时区、JDBC 连接时区三者不一致;用了 java.util.Date 而非 java.time。
  • 排查步骤:date 看服务器时区;查 JDBC URL 是否带时区参数;检查实体里时间字段的类型。
  • 解法:统一用 java.time.Instant / LocalDateTime,并显式约定时区(如连接串里指定 serverTimezone)。
  • 详见:12.2 实体与 Repository 。

D.4 测试类问题

测试相关的问题多来自 4.x 的注解变更,这是迁移时的高发区。

D.4.1 测试注解报错:@MockBean 不存在

  • 症状:编译或运行测试时报找不到 @MockBean。
  • 可能原因:4.x 已移除 @MockBean / @SpyBean,沿用 3.x 写法会失败。
  • 排查步骤:搜索测试类里的 @MockBean / @SpyBean。
  • 解法:改用 @MockitoBean / @MockitoSpyBean。注意新注解不能用在 @Configuration 类上,要写在测试类字段上。
  • 详见:17.2 切片测试 。

D.4.2 @SpringBootTest 里 MockMvc 注入失败

  • 症状:@Autowired MockMvc 报找不到 Bean,测试启动失败。
  • 可能原因:4.x 的 @SpringBootTest 不再自带 MockMvc,缺少自动配置。
  • 排查步骤:检查测试类上是否只有 @SpringBootTest。
  • 解法:补上 @AutoConfigureMockMvc;同理,要用 TestRestTemplate 需加 @AutoConfigureTestRestTemplate,新的 RestTestClient 对应 @AutoConfigureRestTestClient。
  • 详见:17.2 切片测试 。

D.4.3 测试上下文加载慢

  • 症状:每个测试类都重新启动 Spring 上下文,整套测试跑很久。
  • 可能原因:不同测试类的配置不一致,导致 Spring 的上下文缓存无法命中。
  • 排查步骤:运行测试时看日志里出现了几次 Started ... Application;次数越多说明缓存命中越差。
  • 解法:统一测试配置(相同的 @SpringBootTest 属性与 mock 组合),减少 @DirtiesContext 的使用,优先用切片测试。
  • 详见:17.3 集成测试 。

D.4.4 测试数据互相污染

  • 症状:单独跑测试通过,一起跑就失败;用例之间有数据残留。
  • 可能原因:多个测试共用同一个数据库,前一个用例写入的数据影响了后一个。
  • 排查步骤:把失败用例单独跑一遍,能通过就说明是数据依赖问题。
  • 解法:给测试方法加 @Transactional 让它自动回滚;或用 @Sql 准备与清理数据;或用随机化的测试数据避免碰撞。
  • 详见:17.1 单元测试 。

D.4.5 切片测试里自定义 Bean 注入失败

  • 症状:@DataJpaTest / @WebMvcTest 里 @Autowired 自己的组件时报找不到 Bean。
  • 可能原因:切片测试只加载相关层,不会加载完整的应用上下文,自定义组件默认不在其中。
  • 排查步骤:确认测试用的是切片注解还是 @SpringBootTest;切片测试的扫描范围是有限的。
  • 解法:用 @Import 显式引入需要的配置类;确实需要全上下文时改用 @SpringBootTest。
  • 详见:17.2 切片测试 。

D.5 错误关键字 → 章节总索引

先在左列找到日志里的关键字,再跳到对应小节。

错误关键字类别章节
Port ... was already in use启动D.1.1
release version 21 not supported启动D.1.2
Unable to find a single main class启动D.1.3
BeanCreationException启动D.1.4
No qualifying bean of type启动D.1.5
BeanCurrentlyInCreationException启动D.1.6
Failed to configure a DataSource启动D.1.7
配置不生效(无报错)配置D.2.1
cannot start any token配置D.2.2
No active profile set配置D.2.3
环境变量未生效(无报错)配置D.2.4
InvalidConfigDataPropertyException配置D.2.5
多文件同名键冲突配置D.2.6
Connection is not available, request timed out数据库D.3.1
Table ... not found / relation ... does not exist数据库D.3.2
ddl-auto 引发的数据异常数据库D.3.3
事务未回滚(无报错)事务D.3.4
LazyInitializationException数据库D.3.5
N+1(日志 SQL 过多)数据库D.3.6
时间字段差 8 小时数据库D.3.7
@MockBean / @SpyBean 找不到测试D.4.1
MockMvc 无法注入测试D.4.2
测试上下文重复加载测试D.4.3
测试数据互相污染测试D.4.4
切片测试里 Bean 找不到测试D.4.5

小结

排查 Spring Boot 问题,先把四类症状分开:启动类看端口、JDK、主类、Bean 装配与数据源;配置类看键名、YAML 缩进、profile 与环境变量;数据库与事务类看连接、表结构、ddl-auto、事务代理与懒加载;测试类重点记住 4.x 的注解变更——@MockBean / @SpyBean 已移除,@SpringBootTest 不再自带 MockMvc。所有问题里,最容易浪费时间的不是报错,而是「不报错的静默错误」:配置没生效、事务没回滚、N+1,都要靠主动观测(Actuator、SQL 日志)而不是等它抛异常。查完症状回到 附录 B 核对配置键名,构建相关问题见 附录 C 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计