最后更新:2026-09-11
适用场景:Spring Boot 整合 MyBatis、MySQL、Mapper 接口、Mapper XML、@MapperScan、mapper-locations、Invalid bound statement
Spring Boot 整合 MyBatis,真正需要写的配置并不多。
大部分项目出问题,不是 MyBatis 不会用,而是依赖版本、Mapper 扫描、XML 路径、namespace 和方法名没有对上。尤其是 Invalid bound statement,看起来像 SQL 报错,实际往往连 SQL 都没有执行。
这篇文章从一个能直接运行的用户查询接口开始,把依赖、目录结构、数据源、Mapper 接口、XML 映射和排查顺序串起来。示例使用 MySQL,Spring Boot 3 和 Spring Boot 4 都会说明。
一、先看结论
Spring Boot 整合 MyBatis,至少需要下面几部分:
1. 引入 mybatis-spring-boot-starter
2. 引入对应数据库的 JDBC 驱动
3. 配置 spring.datasource
4. 创建 Mapper 接口并让 Spring 扫描到
5. 使用注解 SQL,或者让 Mapper XML 与接口正确对应
一个常见的项目结构如下:
src
├─ main
│ ├─ java
│ │ └─ com.example.demo
│ │ ├─ DemoApplication.java
│ │ ├─ controller
│ │ │ └─ UserController.java
│ │ ├─ domain
│ │ │ └─ User.java
│ │ ├─ mapper
│ │ │ └─ UserMapper.java
│ │ └─ service
│ │ └─ UserService.java
│ └─ resources
│ ├─ application.yml
│ └─ mapper
│ └─ UserMapper.xml
最容易出错的是这三个对应关系:
UserMapper.java 的完整类名
↓
UserMapper.xml 的 namespace
↓
Mapper 方法名与 XML 语句 id
例如:
package com.example.demo.mapper;
public interface UserMapper {
User findById(Long id);
}
对应 XML 必须写成:
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="findById" resultType="com.example.demo.domain.User">
SELECT id, username, email
FROM sys_user
WHERE id = #{id}
</select>
</mapper>
namespace 或 id 只要有一个对不上,通常就会出现:
Invalid bound statement (not found)
二、先选对 MyBatis Starter 版本
不要只看别人文章里的版本号直接复制。MyBatis Spring Boot Starter 的大版本需要与 Spring Boot 对应。
按 MyBatis 官方当前兼容说明:
| Spring Boot | MyBatis Spring Boot Starter | Java |
|---|---|---|
| Spring Boot 4.0.x | 4.0.x | Java 17 及以上 |
| Spring Boot 3.2 - 3.5 | 3.0.x | Java 17 及以上 |
| Spring Boot 2.7 | 2.3.x | Java 8 及以上 |
本文主要示例按 Spring Boot 3.5 和 Starter 3.0.5 编写。
如果你的项目已经升级到 Spring Boot 4.0,可以把 Starter 换成 4.0.x。官方发布的 4.0.1 可用于 Spring Boot 4.0 项目。
不建议在 Spring Boot 3 项目里强行使用 Starter 4.x,也不要为了跟着旧教程,把新项目降到已经结束维护的旧版本。
如果不确定当前项目到底用了哪个 Spring Boot 版本,可以先看 pom.xml 中的父工程:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/>
</parent>
Spring Boot 和 JDK 的对应关系,可以继续看:Spring Boot JDK 兼容表。
三、添加 Maven 依赖
1. Spring Boot 3.5 项目
在 pom.xml 中加入:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
这里三个依赖的作用分别是:
spring-boot-starter-web
用于编写 Controller 和 HTTP 接口
mybatis-spring-boot-starter
提供 MyBatis、MyBatis-Spring 和 Spring Boot 自动配置
mysql-connector-j
提供 MySQL JDBC 驱动
2. Spring Boot 4.0 项目
MyBatis Starter 改为 4.0.x,例如:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
其他代码和配置思路基本一致。
3. Gradle 写法
Spring Boot 3.5 项目可以这样写:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5'
runtimeOnly 'com.mysql:mysql-connector-j'
}
依赖下载失败时,不要反复删除整个本地仓库。先检查 Maven 镜像、网络和具体失败坐标,可以参考:Maven 下载依赖失败怎么处理。
四、准备数据库和测试表
先创建一个数据库:
CREATE DATABASE demo
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
然后创建测试表:
USE demo;
CREATE TABLE sys_user (
id BIGINT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) DEFAULT NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_sys_user_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
插入两条测试数据:
INSERT INTO sys_user (username, email, status)
VALUES
('zhangsan', '[email protected]', 1),
('lisi', '[email protected]', 1);
这里使用 sys_user,没有直接把表名写成 user。原因是 user 在部分数据库或工具中容易与系统表、保留字产生混淆。
五、配置 application.yml
在 src/main/resources/application.yml 中配置数据源和 MyBatis:
spring:
application:
name: demo
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.domain
configuration:
map-underscore-to-camel-case: true
这几个 MyBatis 配置分别表示:
mapper-locations
Mapper XML 文件的位置
type-aliases-package
实体类所在包,配置后 XML 可以使用简短类名
map-underscore-to-camel-case
把 created_at 自动映射为 createdAt
如果 XML 文件还会放在多层子目录中,可以改成:
mybatis:
mapper-locations: classpath*:mapper/**/*.xml
如果只有一层目录,classpath:mapper/*.xml 已经够用,也更容易看懂。
driver-class-name 一定要写吗?
通常不是必须的。
Spring Boot 可以根据 JDBC URL 推断驱动,下面这行大多数时候可以省略:
driver-class-name: com.mysql.cj.jdbc.Driver
不过在入门示例中明确写出来,排查依赖和驱动问题会直观一些。
如果数据源本身还没有连通,先看上一篇:Spring Boot 连接 MySQL。
六、创建实体类
创建 src/main/java/com/example/demo/domain/User.java:
package com.example.demo.domain;
import java.time.LocalDateTime;
public class User {
private Long id;
private String username;
private String email;
private Integer status;
private LocalDateTime createdAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
}
这里没有使用 Lombok,是为了让示例脱离 IDE 插件也能直接编译。
因为已经配置:
map-underscore-to-camel-case: true
所以数据库字段与 Java 属性可以这样自动对应:
| 数据库字段 | Java 属性 |
|---|---|
id | id |
username | username |
created_at | createdAt |
如果没有开启驼峰映射,created_at 通常不会自动填到 createdAt,这时需要在 SQL 中起别名,或者使用 resultMap 明确配置。
七、创建 Mapper 接口
创建 src/main/java/com/example/demo/mapper/UserMapper.java:
package com.example.demo.mapper;
import com.example.demo.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
User findById(Long id);
List<User> findAll();
List<User> findByStatus(@Param("status") Integer status);
}
@Mapper 的作用,是让 MyBatis Starter 找到这个接口并注册到 Spring 容器。
如果 Mapper 很多,也可以不在每个接口上写 @Mapper,改为在启动类上统一扫描:
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.example.demo.mapper")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
两种方式选一种即可:
Mapper 数量少:每个接口使用 @Mapper
Mapper 数量多:启动类使用 @MapperScan
同时写一般也能运行,但没有必要。
注意 @MapperScan 导入的是:
org.mybatis.spring.annotation.MapperScan
不要误导入其他框架中名字相似的注解。
八、创建 Mapper XML
创建 src/main/resources/mapper/UserMapper.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.example.demo.mapper.UserMapper">
<resultMap id="userResultMap" type="com.example.demo.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<result property="status" column="status"/>
<result property="createdAt" column="created_at"/>
</resultMap>
<sql id="userColumns">
id, username, email, status, created_at
</sql>
<select id="findById" resultMap="userResultMap">
SELECT
<include refid="userColumns"/>
FROM sys_user
WHERE id = #{id}
</select>
<select id="findAll" resultMap="userResultMap">
SELECT
<include refid="userColumns"/>
FROM sys_user
ORDER BY id DESC
</select>
<select id="findByStatus" resultMap="userResultMap">
SELECT
<include refid="userColumns"/>
FROM sys_user
WHERE status = #{status}
ORDER BY id DESC
</select>
</mapper>
这里有四个地方必须对上。
1. namespace 对应 Mapper 完整类名
<mapper namespace="com.example.demo.mapper.UserMapper">
它对应:
package com.example.demo.mapper;
public interface UserMapper {
}
不能只写:
<mapper namespace="UserMapper">
也不能写成 XML 文件路径。
2. select 的 id 对应接口方法名
Mapper 方法是:
User findById(Long id);
XML 中就应该是:
<select id="findById" resultMap="userResultMap">
大小写也要一致。
3. resultMap 对应实体属性
<result property="createdAt" column="created_at"/>
property 是 Java 属性,column 是查询结果中的字段名。
4. 参数名要能被 MyBatis 找到
单个简单参数通常可以直接写:
WHERE id = #{id}
多个参数建议在接口中明确使用 @Param:
List<User> findByNameAndStatus(
@Param("username") String username,
@Param("status") Integer status
);
XML 对应:
WHERE username = #{username}
AND status = #{status}
这样不会依赖编译器是否保留方法参数名,排查也更直接。
九、创建 Service 和 Controller
创建 src/main/java/com/example/demo/service/UserService.java:
package com.example.demo.service;
import com.example.demo.domain.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User findById(Long id) {
return userMapper.findById(id);
}
public List<User> findAll() {
return userMapper.findAll();
}
public List<User> findByStatus(Integer status) {
return userMapper.findByStatus(status);
}
}
再创建 src/main/java/com/example/demo/controller/UserController.java:
package com.example.demo.controller;
import com.example.demo.domain.User;
import com.example.demo.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> findById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
@GetMapping
public List<User> findAll(
@RequestParam(required = false) Integer status) {
if (status == null) {
return userService.findAll();
}
return userService.findByStatus(status);
}
}
启动项目后访问:
http://localhost:8080/users/1
正常时会得到类似结果:
{
"id": 1,
"username": "zhangsan",
"email": "[email protected]",
"status": 1,
"createdAt": "2026-09-11T10:30:00"
}
查询全部用户:
http://localhost:8080/users
按状态查询:
http://localhost:8080/users?status=1
如果接口返回 404,但应用没有异常,先确认数据库里是否存在对应 ID。它不一定是 MyBatis 配置失败。
十、使用注解写 SQL
简单 SQL 也可以直接写在 Mapper 接口中:
package com.example.demo.mapper;
import com.example.demo.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("""
SELECT id, username, email, status, created_at
FROM sys_user
WHERE id = #{id}
""")
User findById(Long id);
}
如果启用了:
mybatis:
configuration:
map-underscore-to-camel-case: true
created_at 可以映射到 createdAt。
注解和 XML 怎么选?
没有绝对答案,可以按 SQL 复杂度来选。
适合注解的场景:
SQL 很短
查询条件固定
接口数量少
不需要复杂动态 SQL
适合 XML 的场景:
SQL 较长
动态查询条件多
需要 resultMap
需要复用 SQL 片段
团队习惯把 Java 和 SQL 分开维护
一个项目里可以同时使用注解和 XML,但同一个 Mapper 方法不要重复定义两份 SQL。重复定义可能导致启动阶段出现映射冲突。
十一、动态 SQL 怎么写
例如按用户名和状态筛选:
List<User> search(
@Param("username") String username,
@Param("status") Integer status
);
XML 可以这样写:
<select id="search" resultMap="userResultMap">
SELECT
<include refid="userColumns"/>
FROM sys_user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY id DESC
</select>
<where> 会处理开头多余的 AND,比手动拼接字符串可靠。
不要为了动态条件把参数改成 ${username}:
WHERE username = '${username}'
${} 是直接字符串替换,如果参数来自用户输入,会带来 SQL 注入风险。
普通值参数应该使用:
WHERE username = #{username}
#{} 会通过预编译参数传值。
表名、字段名无法直接使用普通预编译参数时,也不要把前端传入的内容原样放进 ${}。应该在服务端做白名单映射。
十二、新增、修改和删除怎么写
Mapper 接口:
int insert(User user);
int updateStatus(
@Param("id") Long id,
@Param("status") Integer status
);
int deleteById(Long id);
XML:
<insert id="insert"
parameterType="com.example.demo.domain.User"
useGeneratedKeys="true"
keyProperty="id">
INSERT INTO sys_user (username, email, status)
VALUES (#{username}, #{email}, #{status})
</insert>
<update id="updateStatus">
UPDATE sys_user
SET status = #{status}
WHERE id = #{id}
</update>
<delete id="deleteById">
DELETE FROM sys_user
WHERE id = #{id}
</delete>
插入完成后,MySQL 生成的主键会写回:
user.getId()
因为 XML 中配置了:
useGeneratedKeys="true"
keyProperty="id"
生产项目中的删除接口要谨慎。至少要确认参数不为空,并根据业务决定是物理删除还是逻辑删除。
十三、事务应该写在哪里
需要多条写操作一起成功或失败时,在 Service 层使用 @Transactional:
package com.example.demo.service;
import com.example.demo.domain.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
public Long createUser(User user) {
userMapper.insert(user);
if (user.getEmail() == null || user.getEmail().isBlank()) {
throw new IllegalArgumentException("email 不能为空");
}
return user.getId();
}
}
在默认配置下,运行时异常会触发回滚。
事务通常放在 Service 的 public 方法上。不要把 @Transactional 随手加在 Mapper 接口、实体类或私有方法上。
同一个类内部直接调用自己的另一个事务方法,还可能绕过 Spring 代理。这类问题与 MyBatis XML 无关,不要看到数据没回滚就只检查 SQL。
十四、怎么查看 MyBatis 执行的 SQL
本地开发时,可以临时开启 MyBatis 标准输出日志:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
执行查询后,控制台会出现类似内容:
Preparing: SELECT id, username, email, status, created_at FROM sys_user WHERE id = ?
Parameters: 1(Long)
Total: 1
这能确认三个问题:
SQL 有没有真正执行
占位参数传了什么值
查询返回了多少行
不过 StdOutImpl 是直接输出到标准输出,不适合长期用于生产环境。项目已经使用 Logback 或 Log4j2 时,更推荐配置 Mapper 包的日志级别:
logging:
level:
com.example.demo.mapper: debug
排查完成后,应恢复合适的日志级别,避免生产环境记录过多 SQL 和参数。
十五、常见报错怎么排查
1. Invalid bound statement (not found)
典型错误:
org.apache.ibatis.binding.BindingException:
Invalid bound statement (not found):
com.example.demo.mapper.UserMapper.findById
这通常不是数据库连接失败,而是 MyBatis 没找到这个方法对应的映射语句。
按下面顺序检查:
1. UserMapper.xml 是否放在 src/main/resources 下
2. mybatis.mapper-locations 是否能匹配到 XML
3. XML 的 namespace 是否等于 Mapper 完整类名
4. select 的 id 是否等于 Mapper 方法名
5. XML 是否被打进最终 jar
6. 是否混用了 MyBatis 和 MyBatis-Plus 的配置
可以检查打包结果:
jar tf target/demo.jar | findstr UserMapper.xml
Windows PowerShell 也可以使用:
jar tf .\target\demo.jar | Select-String 'UserMapper.xml'
如果完全没有结果,说明 XML 没进入 jar。详细排查可以看:MyBatis Invalid bound statement 怎么处理。
2. Parameter 'xxx' not found
典型错误:
Parameter 'status' not found. Available parameters are [arg1, arg0, param1, param2]
多参数方法明确加上 @Param:
List<User> findByNameAndStatus(
@Param("username") String username,
@Param("status") Integer status
);
XML 中使用同样的名字:
WHERE username = #{username}
AND status = #{status}
3. UserMapper 无法注入
典型错误:
Parameter 0 of constructor in UserService required a bean of type
'com.example.demo.mapper.UserMapper' that could not be found
说明 Mapper 接口没有注册成 Spring Bean。
检查:
Mapper 接口上是否有 @Mapper
或者启动类上是否有 @MapperScan
扫描包路径是否写对
启动类是否放在合适的父包中
如果启动类位于:
com.example.demo.DemoApplication
业务类最好放在:
com.example.demo.controller
com.example.demo.service
com.example.demo.mapper
不要把启动类放进过深的子包,导致默认组件扫描漏掉其他类。
4. 数据库字段有值,Java 属性却是 null
例如数据库字段是:
created_at
Java 属性是:
createdAt
可以开启驼峰映射:
mybatis:
configuration:
map-underscore-to-camel-case: true
或者在 SQL 中写别名:
SELECT created_at AS createdAt
FROM sys_user
复杂映射更推荐使用 resultMap,不要把所有希望都寄托在自动映射上。
5. XML 报解析错误
Mapper XML 本质上仍然是 XML,SQL 中的部分符号需要转义。
例如小于号:
WHERE created_at < #{endTime}
例如 SQL 使用位运算与号时,要写成 &:
WHERE permission_bits & #{requiredBits} = #{requiredBits}
否则 XML 解析器会把 & 当成实体引用的开始。
也可以使用 CDATA 包裹一段复杂条件:
<![CDATA[
created_at <= #{endTime}
]]>
不要看到 XML 解析失败就去改数据库账号。此时应用通常还没有执行到连接数据库那一步。
6. Table doesn't exist
典型错误:
Table 'demo.sys_user' doesn't exist
检查当前连接的数据库是不是 demo:
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
还要检查:
表名是否写错
连接环境是否写错
测试库与开发库是否混淆
Linux 环境下表名大小写是否一致
7. Communications link failure
这说明 JDBC 连接没有建立成功,优先检查:
MySQL 是否启动
IP 和端口是否正确
容器内是否错误使用 localhost
防火墙和安全组是否放行
MySQL 是否允许远程连接
它与 Mapper XML 的 namespace 没有关系。
8. Access denied for user
典型错误:
Access denied for user 'demo_user'@'10.0.0.5'
这说明已经连接到 MySQL,但账号、密码、来源主机或授权存在问题。
先用同一地址和账号从应用所在机器测试登录:
mysql -h 192.168.1.10 -P 3306 -u demo_user -p
不要通过给应用使用 root 或开放全部权限来掩盖授权配置问题。
常见坑
1. 把 Mapper XML 放在 src/main/java
默认 Maven 构建会把 src/main/resources 中的文件复制到 classpath。
如果把 XML 随手放在 src/main/java,IDE 中可能看得到,打包后却不一定存在。最省事的做法是统一放在:
src/main/resources/mapper
2. 同时手动配置 SqlSessionFactory
MyBatis Spring Boot Starter 会根据 DataSource 自动创建 SqlSessionFactory 和 SqlSessionTemplate,并扫描 Mapper。
普通单数据源项目不需要再照着旧教程手写一套配置类。手动配置不完整时,反而可能让 mapper-locations 等自动配置失效。
多数据源项目另当别论,它通常需要分别配置数据源、事务管理器和 SqlSessionFactory。
3. 把 MyBatis 和 MyBatis-Plus 当成同一个依赖
MyBatis-Plus 建立在 MyBatis 之上,但 Starter、基础 Mapper 和配置项并不完全相同。
如果项目使用的是 MyBatis-Plus,应跟着对应版本的 MyBatis-Plus 文档配置,不要同时引入多个功能重叠的 Starter。
4. 在 XML 中滥用 ${}
${} 会直接拼接字符串。用户输入未经校验就进入 ${},可能造成 SQL 注入。
普通查询值使用 #{}。确实需要动态表名或排序字段时,在 Java 层做固定白名单转换。
5. 为了看 SQL 在生产环境永久开启标准输出
StdOutImpl 适合本地临时排查,不适合生产。大量 SQL 和参数既影响日志可读性,也可能泄露敏感信息。
6. 数据库连接还没通就开始修改 Mapper
如果报错是 Communications link failure、连接超时或 Access denied,先处理数据源和网络。
如果报错是 Invalid bound statement,再检查 Mapper 和 XML。
把错误分层,排查速度会快很多。
排查清单
遇到 Spring Boot 整合 MyBatis 无法启动、无法查询或映射失败时,可以按这个顺序检查:
[ ] Spring Boot、JDK、MyBatis Starter 大版本是否兼容
[ ] mybatis-spring-boot-starter 是否下载成功
[ ] mysql-connector-j 是否存在
[ ] spring.datasource.url、username、password 是否正确
[ ] MySQL 是否能从应用所在机器访问
[ ] Mapper 接口是否有 @Mapper,或是否配置 @MapperScan
[ ] Mapper XML 是否位于 src/main/resources
[ ] mapper-locations 是否能匹配 XML
[ ] XML namespace 是否等于 Mapper 完整类名
[ ] XML 语句 id 是否等于 Mapper 方法名
[ ] 多个参数是否使用 @Param
[ ] 数据库列名与 Java 属性是否正确映射
[ ] 最终 jar 中是否包含 Mapper XML
[ ] 是否误混用 MyBatis 与 MyBatis-Plus Starter
[ ] 是否根据根异常排查,而不是只看最外层报错
建议优先看异常最底部的 Caused by。Spring 启动日志很长,最上面的 BeanCreationException 往往只是外层结果,真正原因通常在最后几段。
常见问题 FAQ
1. Spring Boot 整合 MyBatis 必须写 XML 吗?
不是。
简单 SQL 可以使用 @Select、@Insert、@Update、@Delete 等注解。复杂查询、动态 SQL 和复杂结果映射更适合 XML。
2. @Mapper 和 @MapperScan 有什么区别?
@Mapper 标记单个 Mapper 接口,@MapperScan 扫描指定包下的 Mapper。
接口少时用 @Mapper 很直观,接口多时用 @MapperScan 更省事。通常不需要两者同时使用。
3. mapper-locations 不配置可以吗?
如果全部使用注解 SQL,可以不配置 XML 路径。
如果使用 XML,建议明确配置 mybatis.mapper-locations,同时把 XML 放在 src/main/resources 下,避免目录变化后难以判断实际扫描位置。
4. resultType 和 resultMap 有什么区别?
resultType 适合字段名与属性名能够直接对应的简单结果。
resultMap 可以明确描述字段到属性的映射,还能处理关联对象、集合和更复杂的结果。一个 select 通常在 resultType 与 resultMap 中选择一个,不要同时配置。
5. 为什么 SQL 在数据库客户端能执行,放进 XML 就报错?
先看是不是 XML 语法问题,例如 <、& 没有转义。其次检查参数名称、数据库连接的 schema,以及客户端和应用是否连接了同一个环境。
6. MyBatis 会自动建表吗?
不会。
MyBatis 主要负责 SQL 执行与对象映射,不会像某些 ORM 那样根据实体类自动维护表结构。建表可以使用 SQL 脚本、Flyway 或 Liquibase。
7. MyBatis Starter 会自动创建哪些对象?
在检测到可用 DataSource 后,Starter 会自动配置 SqlSessionFactory、SqlSessionTemplate,并把扫描到的 Mapper 注册到 Spring 容器。
普通单数据源项目一般不需要手动创建这些对象。
8. Spring Boot 4 可以继续使用 MyBatis Starter 3.x 吗?
不建议。
按照官方兼容表,Spring Boot 4.0 应使用 Starter 4.0.x。Spring Boot 3.2 至 3.5 使用 Starter 3.0.x。升级时要一起检查 JDK、Spring Boot 和 Starter 大版本。
最后总结
Spring Boot 整合 MyBatis 的核心,不是配置越多越好,而是把几层关系对齐:
DataSource 负责连接数据库
MyBatis Starter 负责自动配置
Mapper 接口定义 Java 方法
Mapper XML 定义 SQL 和结果映射
Service 负责业务与事务
Controller 提供 HTTP 接口
排查时也按层次来:
连不上数据库:检查 spring.datasource、网络和权限
Mapper 无法注入:检查 @Mapper 或 @MapperScan
找不到 SQL:检查 mapper-locations、namespace 和 id
属性为 null:检查列名、驼峰配置和 resultMap
参数找不到:检查 @Param 与 XML 占位符
只要先确认错误属于哪一层,大多数 MyBatis 问题不需要反复修改所有配置。
相关文章
MyBatis Invalid bound statement 怎么处理
参考资料
MyBatis Spring Boot Starter 官方文档
Spring Boot 官方文档:SQL Databases
MyBatis Spring Boot Starter GitHub
更新记录
2026-09-11:
- 创建文章《Spring Boot 整合 MyBatis》
- 增加 Spring Boot 3、Spring Boot 4 依赖版本说明
- 增加 MySQL 测试表、数据源与 MyBatis 配置
- 增加实体类、Mapper、XML、Service、Controller 示例
- 增加注解 SQL、动态 SQL、增删改和事务说明
- 增加 Invalid bound statement 等常见错误排查
Spring Boot 整合 MyBatis
https://java.li/archives/spring-boot-mybatis
评论