最后更新: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>

namespaceid 只要有一个对不上,通常就会出现:

Invalid bound statement (not found)

二、先选对 MyBatis Starter 版本

不要只看别人文章里的版本号直接复制。MyBatis Spring Boot Starter 的大版本需要与 Spring Boot 对应。

按 MyBatis 官方当前兼容说明:

Spring BootMyBatis Spring Boot StarterJava
Spring Boot 4.0.x4.0.xJava 17 及以上
Spring Boot 3.2 - 3.53.0.xJava 17 及以上
Spring Boot 2.72.3.xJava 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 属性
idid
usernameusername
created_atcreatedAt

如果没有开启驼峰映射,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 &lt; #{endTime}

例如 SQL 使用位运算与号时,要写成 &amp;

WHERE permission_bits &amp; #{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 环境下表名大小写是否一致

这说明 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 自动创建 SqlSessionFactorySqlSessionTemplate,并扫描 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 通常在 resultTyperesultMap 中选择一个,不要同时配置。

5. 为什么 SQL 在数据库客户端能执行,放进 XML 就报错?

先看是不是 XML 语法问题,例如 <& 没有转义。其次检查参数名称、数据库连接的 schema,以及客户端和应用是否连接了同一个环境。

6. MyBatis 会自动建表吗?

不会。

MyBatis 主要负责 SQL 执行与对象映射,不会像某些 ORM 那样根据实体类自动维护表结构。建表可以使用 SQL 脚本、Flyway 或 Liquibase。

7. MyBatis Starter 会自动创建哪些对象?

在检测到可用 DataSource 后,Starter 会自动配置 SqlSessionFactorySqlSessionTemplate,并把扫描到的 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 问题不需要反复修改所有配置。

相关文章

Spring Boot 连接 MySQL

Spring Boot 配置文件怎么写

MyBatis Invalid bound statement 怎么处理

Spring Boot DataSource 报错怎么处理

Spring Boot JDK 兼容表

Maven 下载依赖失败怎么处理

Maven 依赖冲突怎么排查

参考资料

MyBatis Spring Boot Starter 官方文档

MyBatis-Spring Mapper 扫描文档

MyBatis 3 XML 映射器中文文档

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 等常见错误排查