SpringBoot整合MyBatis怎么配置
SpringBoot 整合 MyBatis 的代码并不多,但 Mapper 扫描、XML 路径、数据库驱动、驼峰映射和事务边界任何一处配错,启动或查询时就会报错。这篇从依赖和目录结构讲起,按当前项目实践梳理配置、Mapper、XML、事务和常见排查方法,不照抄旧版本配置。
SpringBoot 和 MyBatis 的整合,表面上就是加几个依赖、写一个 Mapper、配一段 YAML。
但真正开始写业务后,最常见的报错往往都集中在这些地方:
- Mapper 没有被 Spring 扫描到
- XML 文件路径不对
- namespace 和方法名对不上
- 数据库下划线字段没有映射到 Java 驼峰属性
- 参数名在 XML 里取不到
- 事务注解写了,但实际没有生效
- 为了打印 SQL,生产环境开了过于详细的日志
这篇从一条最小可用链路开始,讲清 SpringBoot、MyBatis 和 MySQL 是怎么接起来的。
版本方面不再照抄旧项目里的 SpringBoot 2.3.3 和 MyBatis Starter 2.1.3。
新项目应该使用项目当前的 SpringBoot BOM 和兼容的 MyBatis Spring Boot Starter,具体版本以项目依赖管理和官方兼容矩阵为准。
整合链路是什么
一次 Controller 查询,大致会经过下面这条链路:
HTTP 请求
│
▼
Controller
│ 调用
▼
Service
│ 事务边界、业务编排
▼
Mapper 接口代理
│ MyBatis 生成代理实现
▼
Mapper XML / 注解 SQL
│
▼
DataSource 连接池
│
▼
MySQL
SpringBoot 主要负责自动配置和 Bean 管理。
MyBatis 负责把 Mapper 方法和 SQL 绑定起来。
数据库驱动和 DataSource 负责真正建立 MySQL 连接。
三者任何一层没有接好,问题都会在启动或第一次调用时暴露出来。
推荐目录结构
一个简单项目可以这样组织:
src/main/java/com/mebugs/demo
├── DemoApplication.java
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── mapper
│ └── UserMapper.java
└── model
└── User.java
src/main/resources
├── application.yml
└── mapper
└── UserMapper.xml
目录不是唯一答案,但 Mapper 接口、XML 和实体职责最好保持清晰。
后面排查扫描和 XML 路径时,目录结构越规整,越不容易绕晕。
依赖怎么配
依赖版本不要从几年前的文章直接复制。
SpringBoot 项目通常由 parent 或 dependency management 统一管理 Spring 依赖版本,MyBatis Starter 选择与当前 Boot 大版本兼容的版本。
核心依赖的职责大致如下:
<dependencies>
<!-- Web 接口 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis 与 SpringBoot 的整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- MySQL 驱动,现代项目通常使用这个坐标 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
老项目里仍可能看到 mysql:mysql-connector-java,是否替换要看项目使用的 SpringBoot 和驱动版本,不要只改坐标而不做完整回归。
如果项目使用其他数据库,只需要替换对应 JDBC 驱动和连接地址,MyBatis 的 Mapper 机制基本不变。
数据源和 MyBatis 配置
一个基础 application.yml 可以这样写:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: demo_user
password: change-me
mybatis:
mapper-locations: classpath:/mapper/**/*.xml
type-aliases-package: com.mebugs.demo.model
configuration:
map-underscore-to-camel-case: true
这几项分别解决:
spring.datasource.url:数据库连接地址和连接参数username/password:数据库账号信息,生产环境不建议硬编码mapper-locations:告诉 MyBatis 去哪里找 XML Mappertype-aliases-package:给实体类型配置别名,减少 XML 中的完整类名map-underscore-to-camel-case:把user_name映射到userName
生产环境建议使用环境变量、Secret 或配置中心注入数据库密码。
不要把真实密码提交到 Git。
配置文件中的日志
开发环境可以临时打开 MyBatis SQL 日志:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
这个配置适合本地快速看 SQL,不建议直接用于生产。
生产环境应该使用项目统一日志体系,并注意参数、Token、密码等敏感信息脱敏。
驼峰映射不是万能的
开启 map-underscore-to-camel-case 后,常见字段可以自动转换:
数据库字段:user_id
Java 属性:userId
数据库字段:created_at
Java 属性:createdAt
但复杂查询、别名、嵌套对象和特殊字段仍然建议显式写 resultMap。
如果查询结果全部为 null,排查时不要只盯着 Java 实体类,也要检查 SQL 列名、别名、XML 的 resultType 和实际返回字段。
Mapper 怎么扫描
Mapper 接口需要被 Spring 注册成 Bean。
有两种常见方式。
每个接口加 @Mapper
@Mapper
public interface UserMapper {
User findById(Long id);
}
这种方式直观,但每个 Mapper 都要加注解。
启动类统一扫描
项目 Mapper 较多时,通常在启动类或配置类上统一扫描:
@SpringBootApplication
@MapperScan(basePackageClasses = UserMapper.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
示例使用 basePackageClasses 表达扫描范围,实际项目也可以使用明确的包名配置。
不建议写过宽的扫描范围,更不要扫描整个根包后让多个模块的 Mapper 互相混在一起。
两种方式不要混乱
项目可以选择 @Mapper,也可以选择 @MapperScan。
如果两种方式同时使用,通常不会因此直接出错,但团队规范会变得不清楚。
建议在项目里统一一种方式,新增 Mapper 时不需要猜。
Mapper 和 XML 怎么对应
Java Mapper 接口:
public interface UserMapper {
User findById(Long id);
int updateName(Long id, String name);
}
对应 XML:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mebugs.demo.mapper.UserMapper">
<select id="findById" resultType="User">
SELECT id, user_name, created_at
FROM sys_user
WHERE id = #{id}
</select>
<update id="updateName">
UPDATE sys_user
SET user_name = #{name}
WHERE id = #{id}
</update>
</mapper>
这里必须对上四个地方:
- XML 的
namespace对应 Mapper 接口全限定名 - XML 的
id对应接口方法名 - 方法参数名和 XML 参数表达式能对应
- XML 文件位于
mapper-locations能扫描到的路径
任何一个不匹配,都可能出现 Invalid bound statement 或参数找不到。
参数命名要明确
单个简单参数有时可以直接使用参数名,但多参数方法建议显式加 @Param:
int updateName(@Param("id") Long id,
@Param("name") String name);
XML 中对应:
<update id="updateName">
UPDATE sys_user
SET user_name = #{name}
WHERE id = #{id}
</update>
如果不加 @Param,编译参数名、MyBatis 参数解析和实际 XML 写法可能对不上。
这个问题常见报错就是找不到某个参数的 getter。
#{} 和 ${} 不要混用
#{} 会使用预编译参数,适合值条件:
WHERE id = #{id}
${} 是字符串直接替换,适合少数必须动态替换 SQL 结构的场景,例如经过白名单校验的排序字段。
ORDER BY ${safeOrderColumn}
如果把用户输入直接放到 ${},就可能造成 SQL 注入。
排序字段、表名和列名不能直接相信前端传值,应该先映射到后端定义的白名单。
Service 和事务边界
Mapper 只负责数据访问,事务边界通常放在 Service 层。
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
public void rename(Long id, String name) {
userMapper.updateName(id, name);
// 这里还可以继续写其他数据库操作
}
}
SpringBoot 检测到 DataSource 后,通常会自动配置事务管理器。
@Transactional 能否生效,还取决于调用是否经过 Spring 代理。
如果在同一个类内部通过 this.rename(...) 调用,可能绕过代理,和其他 Spring AOP 注解一样需要留意自调用问题。
事务里只放数据库操作和必要的业务判断,不要把长时间远程调用、文件操作和用户等待塞进事务。
常见启动和运行报错
找不到 Mapper Bean
常见现象:
No qualifying bean of type UserMapper
优先检查:
- Mapper 是否加了
@Mapper @MapperScan的包路径是否正确- 启动类是否位于合理的根包
- 是否把接口写成了普通类或放错模块
Invalid bound statement
常见现象:
Invalid bound statement (not found)
优先检查:
- XML 是否被打进最终 classpath
mapper-locations是否写对namespace是否是接口全限定名id是否和方法名一致- XML 是否有拼写或 XML 语法错误
可以检查构建产物中是否真的存在:
target/classes/mapper/UserMapper.xml
不要只在 IDE 里确认文件存在,最终运行的是构建后的 classpath。
字段映射为 null
排查顺序可以是:
- 直接执行 SQL,确认数据库确实有值
- 查看返回列名和 Java 属性名
- 确认驼峰映射配置是否被当前环境加载
- 检查 XML 的
resultType或resultMap - 对复杂查询使用显式
resultMap
P27 原文配套的驼峰映射问题适合作为本篇排查章节,不单独重复发布。
配置改了但不生效
先确认实际生效的配置文件和环境:
- 当前激活的 profile
- 配置文件是否位于 classpath
- YAML 缩进是否正确
- 属性前缀是否属于当前 starter 版本
- 是否有环境变量或配置中心覆盖
配置项名称不能只凭旧文章记忆。
升级 SpringBoot 或 MyBatis Starter 后,先看对应版本文档和自动配置报告。
整合时的检查清单
项目第一次接入时,可以按这张清单走:
- SpringBoot、MyBatis Starter 和数据库驱动版本兼容
- DataSource 能正常建立连接
- Mapper 通过
@Mapper或@MapperScan注册 - XML 位于
mapper-locations扫描路径 - namespace、id、参数名一一对应
- 读写字段确认驼峰映射或显式 resultMap
- Service 层定义事务边界
#{}用于值参数,${}只接受白名单结构参数- SQL 日志只在开发或受控排查环境开启
- 数据库密码不提交到代码仓库
小结
SpringBoot 整合 MyBatis,核心就是把四个连接点接好:
- 依赖和版本
- DataSource 和数据库
- Mapper 接口与 XML
- Service 事务边界
最容易出错的地方不是 SQL 本身,而是扫描路径、XML 路径、namespace、方法名、参数名和字段映射。
一句话总结:SpringBoot 负责把组件装起来,MyBatis 负责把方法和 SQL 绑起来,真正稳定的整合还需要清楚的目录、明确的映射和合适的事务边界。
不要把几年前的版本号和配置原样复制到新项目里。先确认项目当前的 SpringBoot、MyBatis Starter 和数据库驱动版本,再按兼容关系落配置,少踩很多旧坑。
