1. 这个报错到底在说什么
先说结论:No qualifying bean of type 'org.springframework.jdbc.core.JdbcTemplate' available expected at least...这句话翻译过来就是:Spring 容器里面,找不到一个JdbcTemplate类型的 Bean。
但这句话背后藏着的真正含义是:你的代码里某个地方正在用@Autowired或者构造器注入 JdbcTemplate,Spring 在做依赖注入的时候,把容器里所有 Bean 翻了个底朝天,就是没找到匹配的对象。
我最早遇到这个报错的时候,第一反应是“我是不是少写了个注解?”后来才明白,这个错几乎从不在你一上来就写的“Hello World”里出现,而总是在项目结构已经复杂起来的时候突然冒出来:可能是加了多数据源配置,可能是引入了某个依赖,也可能是测试类里没有加载容器,反正就是那一瞬间,Spring 告诉你:你要的东西我没有。
很多教程会直接甩一句“你少了 spring-boot-starter-jdbc 依赖”,但实测下来,这个报错的产生原因远不止依赖缺失一种。要理解它,就得先搞清楚 Spring 管理 Bean 的基本流程。我尽量不用术语轰炸,用大白话讲清楚,然后再带你一步步定位自己到底踩了哪个坑。如果你正被这个报错卡住,先别急着到处复制粘贴配置,花十分钟把这篇文章看完,大概率能自己解决。
1.1 一段典型“翻车现场”的推演
假设你建了一个 Spring Boot 项目,从一开始就打算直接用 JdbcTemplate 操作数据库。你很自然地写了这样一段代码:
@Service public class UserService { @Autowired private JdbcTemplate jdbcTemplate; }启动项目后,控制台刷出一个红色大块异常:
*************************** APPLICATION FAILED TO START *************************** Description: No qualifying bean of type 'org.springframework.jdbc.core.JdbcTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}第一次见到这玩意的同学通常会懵,因为代码、注解看起来都没问题。凭直觉在UserService上加一个@Component?没有用。把private JdbcTemplate jdbcTemplate改成@Resource?大概率还是不行。因为你是在向容器“要”一个本来就存在不存在的 Bean,换注解只是换个方式要,东西没有就是没有。
这种“翻车现场”的可信版本我见过几十次,处理思路其实非常统一:先确认有没有数据源,再确认自动配置开没开,最后确认包扫描扫没扫到。三步走完,90% 的问题都能落地。
1.2 容器、Bean 与依赖注入的基本盘
要理解这个报错,得先接受一个事实:Spring 容器就是一个“对象仓库”,你平时new出来的对象,在这里都交给容器创建和管理,这些被管理的对象就叫 Bean。
JdbcTemplate不是凭空存在的,它是 Spring 框架提供的一个“数据库操作工具类”,本身不会自己跑进容器。正常情况下,它是由 Spring Boot 的自动配置类——JdbcTemplateAutoConfiguration在检测到DataSource(数据源)存在的时候,自动创建并放进容器的。
所以这个报错的完整逻辑链是:
- 容器里得先有
DataSource这个 Bean,也就是数据库连接池,比如 HikariCP。 - 有了 DataSource 之后,
JdbcTemplateAutoConfiguration才会自动创建一个JdbcTemplate。 - 你的代码
@Autowired JdbcTemplate,才能拿到。
链条上任一环节断掉,都会报出标题这个错误。这也是我常说“这是最诚实的报错之一”的原因,它直白地告诉你容器里没有这个东西,至于为什么没有,它不负责告诉你,只能靠我们自己查。
2. 排查清单:按顺序干掉每个嫌疑点
这个报错的定位思路有点像排查家里水管为什么不出水,得逐段检查。以下的排查顺序是我在实际项目中反复验证过的,基本按照“依赖 → 数据源 → 自动配置 → 包扫描”的优先级来,不会走弯路。
2.1 依赖是否齐套:spring-jdbc 与 starter-jdbc
第一步,拿起你的pom.xml或者build.gradle,检查是否引入了 JdbcTemplate 所依赖的包。
Spring Boot 项目中,最省心的方式是直接加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>这个 starter 会传递依赖spring-jdbc和spring-tx等核心模块,也是自动配置JdbcTemplate的前提。
但我要提一个很多人踩过的隐蔽坑:如果你用的是 Maven,并且引入的数据库驱动依赖是mysql:mysql-connector-java,而驱动版本不符合 Spring Boot 的依赖管理版本,Spring Boot 的自动配置会因为数据源类型匹配不上而“无声退出”。它不会直接报错,只是什么都不作为,最终结果就是容器里没有DataSource,自然也没有JdbcTemplate。
检查依赖是否齐套时,可以顺手用 Maven 的依赖树命令看一眼:
mvn dependency:tree -Dincludes=org.springframework:spring-jdbc如果结果里为空,或者显示omitted for conflict with...,那就说明依赖这边有问题,先把版本捋顺。
Gradle 项目则用:
gradle dependencies --configuration runtimeClasspath | grep spring-jdbc这一步骤的目标就一句话:让spring-jdbc真正出现在运行期 classpath 里。别只看编译期通过就万事大吉。
2.2 DataSource 是否存在:数据库配置的三种情况
第二步,确认容器里有没有DataSource。这一步很关键,因为JdbcTemplate的自动配置依赖它。
最简单粗暴的验证方式,是在启动类或者任意配置类上临时写一段:
@Bean public DataSource dataSource() { return DataSourceBuilder.create().build(); }如果你手动定义了这个 Bean,项目启动没有报“DataSource 找不到”,说明你的数据源是存在的。那问题就不在这。但要注意,生产环境不要这么写,这只是排查手段,后面我会讲更规范的做法。
如果你的代码里没有显式配置 DataSource,那就要靠 Spring Boot 自动配置了。这里又分三种情况:
第一种,你有application.yml里配置了数据库连接信息:
spring: datasource: url: jdbc:mysql://localhost:3306/test username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver有配置但有 YAML 写错的情况也很常见,比如缩进不对、用户名密码被引号包裹但里面有多余空格、URL 里带了不存在的数据库名。某个值解析失败,数据源创建不出来,JdbcTemplate自然就没了。
第二种,你配置了,但配置了多个数据源。Spring Boot 官方对多数据源场景是直接关闭DataSourceAutoConfiguration的,因为一个框架理解不了两个库该用哪个。这种情况下容器里没有默认 DataSource,你也没有手动给 JdbcTemplate 指定@Qualifier,就会报这个错。
第三种,你想用内嵌数据库快速起步,比如 H2,依赖也有,配置也给了,但 Spring Boot 版本对 H2 的版本兼容没处理干净,导致自动配置没有完成。这种情况排查起来最费时间,因为错误信息特别容易误导人,往往指向数据库驱动本身。
判断 DataSource 到底存不存在,可以在启动类里注入一个ApplicationContext打印所有 Bean 名:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); System.out.println("DataSource == null ? " + (context.getBean(DataSource.class) == null)); } }启动日志里如果DataSource == null ? true,说明数据源压根没起来,直接跳到 2.3 去查自动配置。
2.3 自动配置是否被“关”了
第三步,检查排除项。Spring Boot 自动配置非常强大,但有时候恰恰是自己的配置把自动配置排除了。排查思路如下:
在@SpringBootApplication注解上,检查 exclude 属性:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class }) public class DemoApplication { }部分团队为了规避多数据源启动报错,会加上这样的排除配置,然后忘了删掉。也有的人习惯在spring.factories或者@Import里做文章,引进了一些容易冲突的配置类。
还有一个很容易被忽略的点:你引入了spring-boot-starter-data-jpa,同时自己又手动定义了一个JdbcTemplate的 Bean,这种场景下报错反而是“找到多个 Bean”。但如果你引入的是spring-boot-starter-data-mongodb或者其他数据组件的依赖,它们有可能影响自动配置的执行顺序,导致 JdbcTemplate 的自动配置没跑。
排查方法:查看 Spring Boot 的自动配置报告。在启动时加一个参数:
--debug或者直接看启动日志中打印的“Positive matches”和“Negative matches”段落。查找JdbcTemplateAutoConfiguration是哪一类:
- 如果显示
Negative match,说明自动配置条件没满足,后面通常跟着原因,比如@ConditionalOnClass没找到某个类、@ConditionalOnSingleCandidate发现数据源不唯一。 - 如果显示
Positive match,说明自动配置生效了,那问题就不是“配置被关掉”,而是“Bean 没有被扫描到”。
日志里这部分信息非常详细,中文互联网上很少有人教你看这个,但它是排查 Spring Boot 疑难杂症的利器。
2.4 包扫描与组件发现
第四步,检查你的代码结构。这一步经常被忽略,因为报错信息里根本没有提示“包扫描”三个字,但它却是实际项目里非常常见的原因。
Spring Boot 的默认包扫描规则是:扫描主启动类所在包以及子包。
举个例子:
com.example.demo ├── DemoApplication.java └── service └── UserService.java这是正常的。但如果你把启动类放在com.example.startup,把UserService放在com.example.service,Spring 默认只会扫描com.example.startup包,UserService根本不会进入容器。可 JdbcTemplate 注入到UserService这个 Bean 里的逻辑是在实例化UserService的时候发生的,如果连UserService都没被容器扫描到,@Autowired都不会执行,那你也就看不到这个报错。
等等,这里有人就会问:那报错说明了UserService已经被扫描到了,它确实要注入 JdbcTemplate,只是容器里没有 JdbcTemplate?
对,这种情况也存在,而且更常见。多见于你使用了 Kotlin 写法、Lombok 生成的构造器、或者干脆是手写了构造方法:
@Service public class UserService { private final JdbcTemplate jdbcTemplate; public UserService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }如果你的 UserService 被扫描到,但 JdbcTemplate 缺失,那原因又绕回前面的数据源问题了。
但有一种包扫描相关的隐蔽情形是:你在@SpringBootApplication上加了scanBasePackages,但值写错了,或者写到了不包含config子包的路径。此时配置类里的@Bean JdbcTemplate不会生效,哪怕配置类就在项目里躺着。
@SpringBootApplication(scanBasePackages = "com.example.system")建议在排查时打印一下JdbcTemplateBean 的定义来源,用@SpringBootApplication的默认扫描路径,把“能省则省”的原则贯彻到底,除非真的有必要手写扫描路径。
3. 实战修复:三种能直接抄的解题路径
如果你按照上面的清单排查完,应该已经大概知道问题出在哪了。这一节我给出三种可以直接“抄作业”的修复方案,每种方案对应一类场景,大家可以按实际情况选用。
3.1 方案一:补齐自动配置,让 Spring Boot 自己干活
这种方案适合缺少数据源配置、或者自动配置被排除的情况。
先检查pom.xml中是否包含 JDBC starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>然后确认application.yml有完整的数据源配置:
spring: datasource: url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver接着确认启动类没有排除这两个配置类:
@SpringBootApplication public class DemoApplication { // 不要加 exclude 属性,保持默认 }这样一套操作下来,Spring Boot 会自动创建DataSource(默认使用 HikariCP),然后JdbcTemplateAutoConfiguration会跟着创建JdbcTemplate。容器里存在两个 Bean,你的@Autowired就能正常工作。
这种方案最大的好处是“零多余代码”,所有 Bean 的创建都由框架完成,以后维护成本最小。但要注意:不同版本的 Spring Boot 对数据源 URL 的格式要求可能略有差异,尤其是 MySQL 驱动升级到 8.x 以后,driver-class-name必须写成com.mysql.cj.jdbc.Driver,老写法com.mysql.jdbc.Driver在部分支持 MySQL 8 的驱动中会直接启动失败。我在用 Spring Boot 3.x + MySQL Connector/J 的时候,就因为这个老写法踩过一次。
3.2 方案二:手动声明 JdbcTemplate Bean
在某些情况下,自动配置会被压制,比如你已经有一个非常复杂的多数据源配置类,或者某种历史原因导致自动配置无法启用。这时可以绕开自动配置,手动声明JdbcTemplate:
@Configuration public class DataSourceConfig { @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/your_db"); dataSource.setUsername("root"); dataSource.setPassword("your_password"); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }这里有个细节值得说明:方法参数里的DataSource dataSource是 Spring 自动注入的,容器会先找到dataSource()方法创建出来的那个 Bean,再传给jdbcTemplate()。这种参数注入的方式比在方法体内直接再调用一次dataSource()更安全,因为后者在极端情况下可能触发“代理对象过早调用”的问题,前者则完全规避了。
这种方案的适用场景主要是:你已经有自定义数据源配置,不想让框架自动配置捣乱,也不想动现有的 Bean 体系。代价是自己得多维护一段配置代码,一旦数据库连接信息有变化,得改自己的配置。
注意:手动声明
JdbcTemplate时,如果和自动配置同时存在,Spring Boot 会让你编译通过、启动失败,报“expected single matching bean but found 2”。如果你真遇到了这个错误,说明要么是自动配置没被排除,要么是你之前已经手动配过一个 JdbcTemplate。此时可以把自动配置排除掉,或者找到之前的手动 Bean 删掉多余的。
3.3 方案三:处理多数据源时的手工装配
多数据源配置是产生这类报错的高发地。如果你配置了两个数据源,Spring Boot 的自动配置会因为@ConditionalOnSingleCandidate条件不满足而放弃创建JdbcTemplate。此时你必须手动指定,你的业务代码到底要注入哪个数据源对应的 JdbcTemplate。
先分别声明两个数据源:
@Configuration public class MultiDataSourceConfig { @Primary @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } }再分别声明两个 JdbcTemplate:
@Configuration public class JdbcTemplateConfig { @Primary @Bean(name = "primaryJdbcTemplate") public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean(name = "secondaryJdbcTemplate") public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } }使用方按名称装配即可:
@Service public class UserService { @Autowired @Qualifier("primaryJdbcTemplate") private JdbcTemplate jdbcTemplate; }这里最需要留意的是@Primary的用法。如果只有两个 JdbcTemplate 而没有给其中一个加@Primary,Spring 在@Autowired无注解使用时依然会因为“模棱两可”而启动失败。加了@Primary后,不指定@Qualifier时默认走主数据源。
踩过一次坑之后,我的处理原则是:多数据源场景下,所有 JdbcTemplate 注入处一律写@Qualifier,不要依赖@Primary兜底。因为半年后回来改代码的同事大概率不知道哪个是主数据源,你留一个显式名字他能少走很多弯路。
4. 换个场景再看这套逻辑:测试、Spring Cloud、版本升级
这部分内容是我后来在真实项目中反复体会到的,很多同学线上环境好好的,一到测试类或者升级框架版本就报这个错,简直是“同一个报错、百变原因”的完美样本。
4.1 测试类里报错:是不是忘加载容器了?
如果你是在跑单元测试的时候遇到这个报错,那要分两种来看。
第一种,你用的是@SpringBootTest,它本来会加载完整的 Spring 容器,但测试类所在的包和启动类不在同一个包路径下,也会出现容器扫描不全的问题。建议这样处理:
@SpringBootTest(classes = DemoApplication.class) class UserServiceTest { @Autowired private UserService userService; }显式指定classes之后,容器就会加载主启动类,自动配置也会跟着生效。
第二种,你用的是@WebMvcTest或者@DataJpaTest这类切片测试。切片测试只加载某一层的 Bean,不会加载完整的自动配置。如果被测试的 Service 里注入了 JdbcTemplate,那几乎一定会报这个错。
解决办法是给测试类标注上:
@Import(JdbcTemplateConfig.class)或者在需要时手动 Mock:
@MockBean private JdbcTemplate jdbcTemplate;这里我想多啰嗦一句:切片测试的定位是“专注单层逻辑”,如果你非要让 JdbcTemplate 也进来,那就失去了切片测试的意义。更多时候我们应该在 Service 层引入接口,把 DB 操作封装在 Repository 里,这样测试时 Mock Repository 就够了,完全不需要 JdbcTemplate。这个思路比任何配置修复都更优雅。
4.2 Spring Cloud 环境下被“传染”的启动失败
还有一个我实测遇到的场景是在微服务项目里。某个服务自己单独跑一点问题没有,但启动时带上配置中心的远程配置后,报出这个 JdbcTemplate 错误。
原因很简单:远程配置中心里覆盖了spring.datasource.url等配置,本地application.yml里写的值被覆盖或者清空了。Spring 容器在启动时拿到一个空的数据源配置,自动配置的条件无法满足,JdbcTemplate自然就不存在。
这种问题排查起来有个小技巧:在启动日志里搜索spring.datasource相关的配置来源,看最终生效的配置值是从哪个配置中心拉下来的,然后去检查配置中心对应环境是否缺少数据库配置。另外,如果项目里同时引入了spring-cloud-starter-bootstrap,还要小心 bootstrap 阶段的配置是否影响 DataSource 的创建,我见过一次因配置中心拉取超时导致整个数据源相关 Bean 全部没有创建的情况。
4.3 Spring Boot 2 到 3 的迁移差异
从 Spring Boot 2.x 升到 3.x 后,不少新人会遇到这个报错,原因是 Spring Boot 3 基于 Jakarta EE 9+,底层的一些实现有变化。特别是如果你把spring-boot-starter-jdbc里的版本覆盖了,或者把javax.sql.DataSource错写成了jakarta包的源类,都会造成 DataSource 类型匹配问题。
Spring Boot 3.x 的JdbcTemplateAutoConfiguration仍然会自动创建JdbcTemplate,但条件上更严格,要求JdbcTemplate类的存在和DataSource的单候选存在。如果你引用的spring-jdbc版本和 Boot 3 管理的不一致,也可能出问题。
这个问题上我没有特别好的“窍门”,只有一条建议:升级 Spring Boot 大版本,别手动指定spring-jdbc、spring-tx等组件的版本号,让框架统一管理。框架对这些核心组件的兼容性测试比你想象的要多得多,你手动指定某个版本,反而可能去到一个没经过充分测试的组合。
5. 常见问题速查表与经验总结
为了方便大家排查时快速定位,我整理了一份速查表。直接把你的情况对号入座就行。
| 场景 | 可能原因 | 推荐解法 |
|---|---|---|
| 刚建项目,仅引入 spring-boot-starter-web | 缺少 JDBC 相关依赖 | 添加 spring-boot-starter-jdbc |
| 有依赖缺数据源配置 | 自动配置无法创建 DataSource | 在 application.yml 配置数据库连接 |
| 有数据源配置但自动配置被排除 | exclude 属性或条件注解不满足 | 去掉排除项,开启自动配置 |
| 多数据源配置 | 自动配置条件不满足,无默认 JdbcTemplate | 手动定义每个数据源对应的 JdbcTemplate |
| 测试类中报错 | 切片测试未加载完整容器 | 用 @SpringBootTest 或 @Import 配置类 |
| 从配置中心拉配置后报错 | 配置覆盖导致数据源信息丢失 | 检查配置中心数据源配置 |
| 升级 Spring Boot 大版本 | 依赖版本不兼容 | 交给 Boot BOM 管理依赖版本 |
| JdbcTemplate 找到多个 | 自动配置和手动配置重复 | 排除自动配置或删除多余 Bean |
5.1 一个我多次受益的排查小动作
在上面的排查过程中,有一招我觉得值得单独拿出来说:学会读 Spring Boot 启动报告里的 Condition Evaluation Report。
你只需要在启动时增加参数:
--debug或者如果有 IDEA,在运行配置里将 Program arguments 加上--debug。启动完成后,日志里会出现类似这样的段落:
Negative matches: --------------- JdbcTemplateAutoConfiguration: Did not match: - @ConditionalOnSingleCandidate (found 2 DataSource Beans)看到found 2 DataSource Beans这句话,你就能立刻意识到这是多数据源导致的自动配置关闭,比满屏搜“No qualifying bean”高效一百倍。
这里我想强调一个观点:报错信息经常只是“果”,真正的原因藏在条件报告里。遇到这种 Bean 缺失问题,我从来不直接改代码,而是先看 Condition Evaluation Report,每次都能少走很多弯路。
5.2 正常情况下容器里究竟有哪些 Bean
如果你配好了一切,启动后容器里至少会有这些和数据库相关的 Bean:
dataSource jdbcTemplate transactionManager三个 Bean 缺一不可,如果有spring-boot-starter-jdbc且有配置,它们会自动出现。如果手动配置,也要保证三个都存在。尤其是transactionManager,它在 Spring 事务管理中很重要。如果你同时配置了 JdbcTemplate 但又需要事务,却没声明DataSourceTransactionManager对应的 Bean,后来还会陆陆续续出现No qualifying bean of type 'org.springframework.jdbc.datasource.DataSourceTransactionManager'之类的问题。这位也是同一个家族的bug,提前预防可以少踩一次坑。
5.3 为什么说这个报错其实是“善意”的
最后分享一点我自己的感悟。
一个成熟的框架不可能保证所有 Bean 都替你创建好,这才有了自动配置条件判断机制。JdbcTemplate 的自动配置条件,本质上是“至少有一个唯一的数据源可以干活”,这个设计非常合理。当你看到“No qualifying bean”的时候,其实是框架在告诉你:“你想要我用,但你没给我足够的信息。”
顺着这个思路,你就不会只想着找一段配置粘贴上去,而会更关注容器到底缺了什么、为什么缺。理解了这一点以后,这类“No qualifying bean of type...”报错基本就再也不会困扰你了,因为你会发现它背后的排查思路完全是一模一样的套路,换一个类名、换一个场景,逻辑照旧。
我个人在实际排查中的习惯是:遇到这类报错先查依赖,再查配置,再查扫描路径,最后看 Condition Evaluation Report,有条不紊地推进。看起来多花了几分钟,但实际上,磨刀不误砍柴工,这是一条能反复使用的解题路径。