Spring Boot项目Service注入Mapper失败原因分析与修复步骤完整指南
这篇文章是我在实际项目中踩过的坑汇总,希望能帮你少走弯路。
一、先理解Spring的依赖注入流程
在Spring Boot里,Service注入Mapper失败,本质上是Spring容器找不到那个Mapper Bean。Spring创建Bean的顺序是:
- 扫描组件(
@Component、@Service、@Mapper等) - 实例化Bean
- 注入依赖(
@Autowired) - 初始化Bean
如果第1步就没扫到Mapper,或者第2步实例化失败,第3步自然也就报错了。
最常见的错误提示长这样:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userMapper in com.example.service.UserService required a bean of type
'com.example.mapper.UserMapper' that could not be found.
Action:
Consider defining a bean of type 'com.example.mapper.UserMapper' in your configuration.
看到这个别慌,我们先按排查优先级来。
二、最常见的原因:Mapper扫描路径没配置对
这是新手最容易踩的坑,几乎占了我遇到的案例的70%。
2.1 主启动类包位置不对
Spring Boot默认只扫描主启动类所在包及其子包。如果你的目录结构是这样的:
com.example ← 主启动类在这里
└── DemoApplication.java
com.example.mapper ← Mapper在这里,属于兄弟包!
└── UserMapper.java
com.example.service ← Service在这里
└── UserService.java
那com.example.mapper和com.example.service都是com.example的子包,没问题,能扫到。
但如果你的结构是:
com.example.demo ← 主启动类在这里
└── DemoApplication.java
com.example.mapper ← 和主包是兄弟关系!扫不到!
└── UserMapper.java
com.example.service ← 同样扫不到!
└── UserService.java
这就出问题了。DemoApplication在com.example.demo里,Spring只会扫描com.example.demo及其子包,com.example.mapper和com.example.service根本不在扫描范围内。
修复方法——方案A:调整主启动类位置
把DemoApplication.java放到根包com.example下:
com.example ← 主启动类移到这里
├── DemoApplication.java
├── mapper
│ └── UserMapper.java
└── service
└── UserService.java
修复方法——方案B:用@MapperScan指定扫描路径
如果不想动目录结构,就在主启动类上加@MapperScan:
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mapper") // 告诉Spring去这里找Mapper
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
如果有多个Mapper包:
@MapperScan({"com.example.mapper", "com.example.admin.mapper"})
一个经验: 很多老项目用
mybatis-spring-boot-starter,它会自动扫描@Mapper注解的接口。但如果你用的是mybatis-plus或者自定义配置,自动扫描可能不生效,必须显式加@MapperScan。
三、Mapper接口上没加@Mapper注解
每个Mapper接口加上@Mapper注解,是最简单直接的方式:
package com.example.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper // 加上这个
public interface UserMapper {
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
List<User> selectAll();
}
加了之后,MyBatis-Spring-Boot会自动为这个接口生成代理对象,注册到Spring容器里。
3.1 @Mapper vs @MapperScan 怎么选?
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
@Mapper |
Mapper少(< 5个) | 简单直观,每个Mapper一目了然 | Mapper多了每个都要加,烦 |
@MapperScan |
Mapper多 | 配一次,全搞定 | 要记扫描路径 |
我的建议: 小项目用@Mapper,大项目用@MapperScan,别纠结。
四、XML映射文件路径没配对
很多项目用@Select注解开发,但如果项目用了XML方式写SQL,就可能出现这个问题。
4.1 典型目录结构
src/main/resources
├── application.yml
└── mapper
├── UserMapper.xml
└── OrderMapper.xml
对应的Mapper接口:
package com.example.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> selectByCondition(@Param("name") String name, @Param("age") Integer age);
}
对应的XML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByCondition" resultType="com.example.entity.User">
SELECT * FROM t_user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
</mapper>
如果XML路径配错了,MyBatis就找不到SQL语句,运行时才会报错。在application.yml里配置:
mybatis:
mapper-locations: classpath:mapper/*.xml # 扫描resources/mapper/下的所有xml
type-aliases-package: com.example.entity # 实体类包路径
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
一个容易忽略的点:
mapper-locations的路径是相对于resources目录的。如果你的XML在resources/mapper/xml/下面,就要写classpath:mapper/xml/*.xml。
五、循环依赖问题
这个比较隐晦,报错信息也不够直白。
5.1 什么是循环依赖
@Service
public class UserService {
@Autowired
private OrderService orderService; // 依赖OrderService
}
@Service
public class OrderService {
@Autowired
private UserService userService; // 依赖UserService
}
UserService依赖OrderService,OrderService又依赖UserService,形成一个环。Spring默认不能解决循环依赖。
5.2 报错表现
BeanCurrentlyInCreationException:
Error creating bean with name 'userService':
Requested bean is currently in creation:
Is there an unresolvable circular reference?
5.3 修复方案
方案A:用@Lazy延迟加载(推荐,改动最小)
@Service
public class UserService {
@Autowired
@Lazy // 延迟加载,先创建其他Bean
private OrderService orderService;
}
方案B:重构代码,拆掉循环依赖
如果两个Service互相依赖,说明设计上有问题。可以抽出第三个Service:
// 把公共逻辑抽出来
@Service
public class UserOrderService {
public void doSomething() {
// 原本 UserService 和 OrderService 互相调用的逻辑放这里
}
}
方案C:开启循环依赖支持(不推荐生产环境用)
spring:
main:
allow-circular-references: true # Spring Boot 2.6+
六、事务注解@Transactional导致的代理问题
这个坑比较深,很多开发者遇到都懵了。
6.1 现象
同一个类内部,方法A调用方法B,方法B里注入了Mapper,但调用时Mapper为null或者事务不生效。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void methodA() {
methodB(); // 调用本类方法B
}
@Transactional
public void methodB() {
userMapper.insert(user); // 这里可能出问题
}
}
6.2 原因
Spring的@Transactional是通过代理对象实现的。当你通过this.methodB()调用时,走的是原始对象,不经过代理,所以事务不生效。更关键的是,如果UserService自己注入自己(自己依赖自己),就会出问题。
修复: 把methodB拆到另一个Service里,或者用@Autowired注入自身(虽然不优雅但能解决)。
七、多数据源配置问题
项目有多个数据源时,Mapper可能被注册到了错误的SqlSessionTemplate里。
7.1 多数据源配置示例
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource ds)
throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(ds);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factory.getObject();
}
@Bean
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource ds)
throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(ds);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
return factory.getObject();
}
}
7.2 Mapper指定数据源
@Mapper
@DS("secondary") // MyBatis-Plus的多数据源注解
public interface OrderMapper {
// ...
}
如果没有@DS注解,Mapper会走默认的primary数据源。如果primary数据源里没有对应的表,就会报Table doesn't exist。
八、Spring Boot版本和MyBatis版本不兼容
版本兼容问题虽然不常见,但一旦遇到排查起来很头疼。
8.1 常见版本组合
<!-- pom.xml -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<!-- Spring Boot 3.x 用 mybatis-spring-boot-starter 3.x -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Spring Boot 2.x 用 mybatis-spring-boot-starter 2.x -->
<!--
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
-->
</dependencies>
8.2 版本不匹配的报错
如果Spring Boot 3.x用了mybatis-spring-boot-starter 2.x,会报:
ClassNotFoundException: org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
因为MyBatis的自动配置类在3.x版本里包路径变了。
九、实体类包路径没配置
Mapper能注入,但查询结果映射失败,或者根本查不到数据。
mybatis:
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
如果你的实体类在com.example.model而不是com.example.entity,就要改对应的包路径。
十、排查流程图(实战总结)
遇到注入失败,按这个顺序排查,基本都能解决:
1. 看报错信息 → 是"找不到Bean"还是"循环依赖"还是其他?
↓
2. 检查主启动类位置 → 是否在根包?
↓
3. 检查@MapperScan → 路径是否正确?是否配置了?
↓
4. 检查Mapper接口 → 是否有@Mapper注解?
↓
5. 检查XML映射文件 → 路径是否配对?namespace是否正确?
↓
6. 检查是否有循环依赖 → 用@Lazy或重构
↓
7. 检查多数据源配置 → 是否正确指定了数据源?
↓
8. 检查版本兼容性 → Spring Boot和MyBatis版本是否匹配?
十一、一个完整的可运行示例
src/main/java/com/example/
├── DemoApplication.java
├── mapper/
│ └── UserMapper.java
├── service/
│ └── UserService.java
└── entity/
└── User.java
src/main/resources/
├── application.yml
└── mapper/
└── UserMapper.xml
DemoApplication.java:
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mapper") // 关键:指定Mapper扫描路径
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
UserMapper.java:
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> selectAll();
User selectById(@Param("id") Long id);
int insert(User user);
}
UserService.java:
package com.example.service;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // 这里能注入成功
public List<User> getAllUsers() {
return userMapper.selectAll();
}
public void addUser(User user) {
userMapper.insert(user);
}
}
application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
十二、最后说几句
这些问题里,@MapperScan路径配错和主启动类包位置不对占了绝大多数。如果你刚接手一个项目遇到这个问题,先检查这两项,基本能解决。
如果还是不行,把完整的报错信息贴出来,或者检查mvn dependency:tree看有没有版本冲突。
Spring的依赖注入机制其实不难理解,核心就是三点:扫到、实例化、注入。只要这三个环节任何一个出问题,都会报”找不到Bean”。顺着这个思路去排查,基本不会走弯路。
祝你的项目不再报Required a bean of type xxx that could not be found!