📌 前言

你是否有过这样的困惑?

  • 学 Java 连接数据库,JDBC 写了一堆重复代码,烦死了?

  • 听说 MyBatis 很好用,但不知道从哪开始?

  • 看了一堆教程,专业术语太多,看不懂?

别慌!这篇文章就是为你准备的。

我会用最通俗的语言手把手带你从零搭建一个 MyBatis 项目,每一步都有完整代码 + 详细注释,保证你跟着做就能跑起来!


一句话说清 MyBatis 是什么

MyBatis 就是一个帮你「简化数据库操作」的工具。

还记得用 JDBC 操作数据库吗?你需要:

  1. 加载驱动

  2. 获取连接

  3. 创建 Statement

  4. 写 SQL

  5. 处理结果集

  6. 关闭资源

每次都要写一大堆重复代码,累不累?

MyBatis 的作用就是:你只管写 SQL,剩下的脏活累活它帮你干!


MyBatis 的优缺点

说明

优点

SQL 写在配置文件里,和 Java 代码分离,方便维护

优点

学习成本低,上手快(比 Hibernate 简单多了)

优点

支持动态 SQL,灵活度高

优点

国内企业大量使用,面试必问

缺点

SQL 还是要自己写(但这也算优点,可控性强)

缺点

字段多的时候,映射配置写起来有点多


适用场景

  • 📦 中小型项目的数据库操作

  • 📦 对 SQL 性能要求较高、需要手动优化 SQL 的场景

  • 📦 互联网公司后端开发(国内主流选择)

💡 一句话总结:在国内做 Java 后端开发,MyBatis 几乎是必学技能,没有之一。


🛠️ 一、前期准备

工欲善其事,必先利其器。 在开始写代码之前,先把环境准备好。

1.1 你需要准备这些东西

工具

版本建议

说明

JDK

1.8 及以上

Java 运行环境

Maven

3.6 及以上

项目构建和依赖管理工具

MySQL

5.7 / 8.0

数据库

IntelliJ IDEA

社区版/旗舰版均可

Java 开发工具(推荐)


1.2 检查 JDK 是否安装

打开命令行(cmd),输入:

java -version

如果看到类似下面的输出,说明 JDK 已安装:

java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)

⚠️ 如果提示"不是内部或外部命令",说明 JDK 没安装或环境变量没配,去 [Oracle 官网](https://www.oracle.com/java/technologies/downloads/) 下载安装即可。


1.3 检查 Maven 是否安装

mvn -version

看到版本信息就 OK:

Apache Maven 3.8.6
Maven home: D:\apache-maven-3.8.6
Java version: 1.8.0_311

💡IDEA 自带 Maven,如果你不想单独安装,直接用 IDEA 内置的也行。


1.4 准备数据库

确保你的 MySQL 已经安装并启动。打开 MySQL 客户端(Navicat、SQLyog、命令行都行),执行以下 SQL,创建我们练习用的数据库和表

-- 1. 创建数据库
CREATE DATABASE mybatis_demo DEFAULT CHARACTER SET utf8mb4;

-- 2. 使用该数据库
USE mybatis_demo;

-- 3. 创建用户表
CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    password VARCHAR(50) NOT NULL COMMENT '密码',
    email VARCHAR(100) COMMENT '邮箱',
    age INT COMMENT '年龄',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户表';

-- 4. 插入一些测试数据
INSERT INTO user (username, password, email, age) VALUES
('张三', '123456', 'zhangsan@qq.com', 25),
('李四', '654321', 'lisi@qq.com', 30),
('王五', 'abcdef', 'wangwu@163.com', 28),
('赵六', '111111', 'zhaoliu@gmail.com', 22);

执行完后,查看一下数据是否插入成功:

SELECT * FROM user;

你应该看到 4 条数据:

+----+----------+----------+-------------------+------+---------------------+
| id | username | password | email             | age  | create_time         |
+----+----------+----------+-------------------+------+---------------------+
|  1 | 张三     | 123456   | zhangsan@qq.com   |   25 | 2024-01-01 10:00:00 |
|  2 | 李四     | 654321   | lisi@qq.com       |   30 | 2024-01-01 10:00:00 |
|  3 | 王五     | abcdef   | wangwu@163.com    |   28 | 2024-01-01 10:00:00 |
|  4 | 赵六     | 111111   | zhaoliu@gmail.com |   22 | 2024-01-01 10:00:00 |
+----+----------+----------+-------------------+------+---------------------+

✅ 数据库准备完毕!


1.5 本教程使用的依赖版本

提前告诉你我们会用到哪些依赖,心里有数:

依赖

版本

作用

mybatis

3.5.13

MyBatis 核心包

mysql-connector-java

8.0.33

MySQL 驱动,连接数据库用的

junit

4.13.2

单元测试

lombok

1.18.28

简化实体类代码(可选)


🏗️ 二、核心步骤 —— 从零搭建 MyBatis 项目

🎯 接下来是最核心的部分,跟着一步步做,每一步我都会解释为什么这么做。


步骤 1:用 IDEA 创建 Maven 项目

操作流程:

  1. 打开 IDEA → FileNewProject

  2. 左侧选择 Maven(不要勾选 Create from archetype)

  3. 填写项目信息:

    1. Namemybatis-demo

    2. GroupIdcom.example

    3. ArtifactIdmybatis-demo

  4. 点击 Create

创建完成后,你的项目结构应该长这样:

mybatis-demo
├── pom.xml                    ← Maven 配置文件
└── src
    ├── main
    │   ├── java               ← 放 Java 代码
    │   └── resources          ← 放配置文件
    └── test
        └── java               ← 放测试代码

步骤 2:在 pom.xml 中引入依赖

打开项目根目录下的 pom.xml替换为以下完整内容

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 打包方式为 jar -->
    <packaging>jar</packaging>

    <!-- 统一管理版本号 -->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- ==================== 核心依赖 ==================== -->

        <!-- 1. MyBatis 核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>

        <!-- 2. MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- ==================== 辅助依赖 ==================== -->

        <!-- 3. JUnit 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!-- 4. Lombok:自动生成 getter/setter/toString(可选但推荐) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>

        <!-- 5. SLF4J + Logback 日志(可选,方便看 SQL 执行日志) -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>

</project>

💡 添加完之后,点击 IDEA 右上角的 Maven 刷新按钮(🔄),让 IDEA 自动下载依赖。如果下载慢,可以配置阿里云镜像(百度搜"Maven 阿里云镜像配置")。


步骤 3:编写 MyBatis 核心配置文件

src/main/resources 目录下,新建文件mybatis-config.xml

💡 这个文件是 MyBatis 的"总指挥",告诉 MyBatis 怎么连数据库、去哪找 SQL 映射文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!-- MyBatis 核心配置文件 -->
<configuration>

    <!-- ====== 1. 全局设置 ====== -->
    <settings>
        <!-- 开启驼峰命名自动映射:数据库字段 create_time → Java 属性 createTime -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启日志,方便调试时看到执行的 SQL -->
        <setting name="logImpl" value="SLF4J"/>
    </settings>

    <!-- ====== 2. 类型别名:简化映射文件中的类名书写 ====== -->
    <typeAliases>
        <!-- 给 com.example.entity 包下所有类起别名(别名 = 类名,不区分大小写) -->
        <package name="com.example.entity"/>
    </typeAliases>

    <!-- ====== 3. 数据库环境配置 ====== -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器:使用 JDBC 原生事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源:使用 MyBatis 内置的连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- ⚠️ 注意:把下面的用户名和密码改成你自己的! -->
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&amp;serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!-- ====== 4. 注册映射文件(告诉 MyBatis 去哪找 SQL) ====== -->
    <mappers>
        <!-- 方式一:逐个注册(适合映射文件少的时候) -->
        <mapper resource="mapper/UserMapper.xml"/>

        <!-- 方式二:扫描整个包(映射文件多的时候推荐,但需要接口和XML同包同名) -->
        <!-- <package name="com.example.mapper"/> -->
    </mappers>

</configuration>

关键配置项解释:

配置项

作用

通俗理解

settings

MyBatis 全局行为设置

相当于"偏好设置"

mapUnderscoreToCamelCase

自动映射下划线到驼峰

create_time 自动对应 createTime

typeAliases

给类起短名

写 SQL 映射时不用写全类名

environments

数据库连接信息

告诉程序怎么连数据库

mappers

注册 SQL 映射文件

告诉程序 SQL 写在哪

⚠️特别注意:`username` 和 `password` 要改成你自己 MySQL 的账号密码!


步骤 4:添加日志配置文件(可选但推荐)

src/main/resources 目录下,新建文件 logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 打印 MyBatis 执行的 SQL 语句(非常有用!) -->
    <logger name="com.example.mapper" level="DEBUG"/>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

💡 有了这个配置,运行程序时你能在控制台看到 MyBatis 实际执行的 SQL 语句,方便调试。


步骤 5:创建实体类(Entity / POJO)

💡 实体类就是和数据库表一一对应的 Java 类。 数据库表有几个字段,Java 类就有几个属性。

src/main/java 下创建包 com.example.entity,然后新建类 User.java

package com.example.entity;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.Date;

/**
 * 用户实体类 —— 对应数据库中的 user 表
 *
 * @Data 注解会自动生成 getter、setter、toString、equals、hashCode 方法
 * @NoArgsConstructor 自动生成无参构造方法
 * @AllArgsConstructor 自动生成全参构造方法
 *
 * 如果你没有安装 Lombok 插件,就把这三个注解去掉,手动写 getter/setter/toString
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;          // 用户ID,对应表中的 id 字段
    private String username;     // 用户名,对应表中的 username 字段
    private String password;     // 密码,对应表中的 password 字段
    private String email;        // 邮箱,对应表中的 email 字段
    private Integer age;         // 年龄,对应表中的 age 字段
    private Date createTime;     // 创建时间,对应表中的 create_time 字段(驼峰自动映射)
}

⚠️ 不想用 Lombok? 没关系,手动写也行,这里给出不用 Lombok 的版本:

package com.example.entity;

import java.util.Date;

public class User {

    private Integer id;
    private String username;
    private String password;
    private String email;
    private Integer age;
    private Date createTime;

    // 无参构造
    public User() {}

    // 全参构造
    public User(Integer id, String username, String password,
                String email, Integer age, Date createTime) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.age = age;
        this.createTime = createTime;
    }

    // ===== getter 和 setter =====
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    public Date getCreateTime() { return createTime; }
    public void setCreateTime(Date createTime) { this.createTime = createTime; }

    // toString 方法,方便打印查看
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                ", createTime=" + createTime +
                '}';
    }
}

步骤 6:创建 Mapper 接口(DAO 层)

💡 Mapper 接口就是定义"你想对数据库做什么操作"的地方。只写方法签名,不写实现——具体的 SQL 写在 XML 映射文件里。

src/main/java 下创建包 com.example.mapper,然后新建接口 UserMapper.java

package com.example.mapper;

import com.example.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * 用户 Mapper 接口(相当于 DAO 层)
 *
 * 这里只定义方法,不写实现
 * 具体的 SQL 语句写在对应的 XML 映射文件中(UserMapper.xml)
 */
public interface UserMapper {

    // ==================== 查询操作 ====================

    /**
     * 根据 ID 查询单个用户
     * @param id 用户ID
     * @return User对象,找不到返回null
     */
    User getUserById(Integer id);

    /**
     * 查询所有用户
     * @return 用户列表
     */
    List<User> getAllUsers();

    /**
     * 根据用户名模糊查询
     * @param username 用户名关键字
     * @return 匹配的用户列表
     */
    List<User> getUsersByUsername(String username);

    // ==================== 新增操作 ====================

    /**
     * 新增用户
     * @param user 用户对象
     * @return 影响的行数(1表示成功)
     */
    int insertUser(User user);

    // ==================== 修改操作 ====================

    /**
     * 修改用户信息
     * @param user 用户对象(必须包含id)
     * @return 影响的行数
     */
    int updateUser(User user);

    // ==================== 删除操作 ====================

    /**
     * 根据 ID 删除用户
     * @param id 用户ID
     * @return 影响的行数
     */
    int deleteUserById(Integer id);

    // ==================== 多参数查询示例 ====================

    /**
     * 根据用户名和年龄查询(演示多参数传递)
     * @param username 用户名
     * @param age 年龄
     * @return 匹配的用户列表
     */
    List<User> getUserByNameAndAge(@Param("username") String username,
                                   @Param("age") Integer age);
}

💡 为什么只写接口不写实现类? 因为 MyBatis 会根据你写的 XML 映射文件,自动帮你生成实现类(通过动态代理技术)。你不需要关心实现细节,只管定义方法就行。


步骤 7:编写 XML 映射文件(最核心的一步!)

💡 这个 XML 文件是 MyBatis 的灵魂所在! 每个 Mapper 接口都有一个对应的 XML 文件,里面写具体的 SQL 语句。

src/main/resources 下创建目录 mapper,然后新建文件 UserMapper.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">

<!--
    namespace:命名空间,必须填写对应 Mapper 接口的全限定类名
    作用:把这个 XML 文件和 UserMapper 接口绑定在一起
-->
<mapper namespace="com.example.mapper.UserMapper">

    <!--
    ============================================================
        resultMap:结果映射(当字段名和属性名不一致时使用)
        因为我们开启了驼峰映射,所以这里其实可以不写
        但为了让你了解这个重要功能,我还是写出来作为演示
    ============================================================
    -->
    <resultMap id="userResultMap" type="User">
        <!-- id 标签映射主键字段 -->
        <id column="id" property="id"/>
        <!-- result 标签映射普通字段 -->
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="email" property="email"/>
        <result column="age" property="age"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <!--
    ============================================================
        SQL 片段:把重复的 SQL 提取出来,避免到处复制粘贴
    ============================================================
    -->
    <sql id="userColumns">
        id, username, password, email, age, create_time
    </sql>

    <!--
    ============================================================
        1. 查询:根据 ID 查询单个用户
           id       → 对应接口中的方法名
           parameterType → 参数类型(基本类型可以省略)
           resultType    → 返回值类型(这里用了别名 User,全名是 com.example.entity.User)
    ============================================================
    -->
    <select id="getUserById" parameterType="int" resultType="User">
        SELECT
            <include refid="userColumns"/>
        FROM user
        WHERE id = #{id}
    </select>

    <!--
        2. 查询:查询所有用户
           返回的是 List<User>,但 resultType 只写集合中元素的类型
    -->
    <select id="getAllUsers" resultType="User">
        SELECT
            <include refid="userColumns"/>
        FROM user
        ORDER BY id ASC
    </select>

    <!--
        3. 查询:根据用户名模糊查询
           模糊查询用 CONCAT 拼接 %,避免 SQL 注入
    -->
    <select id="getUsersByUsername" parameterType="string" resultType="User">
        SELECT
            <include refid="userColumns"/>
        FROM user
        WHERE username LIKE CONCAT('%', #{username}, '%')
    </select>

    <!--
        4. 新增用户
           useGeneratedKeys="true"  → 获取数据库自动生成的主键
           keyProperty="id"         → 把生成的主键值赋给 User 对象的 id 属性
    -->
    <insert id="insertUser" parameterType="User"
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (username, password, email, age)
        VALUES (#{username}, #{password}, #{email}, #{age})
    </insert>

    <!--
        5. 修改用户信息
           #{xxx} 中的 xxx 对应 User 对象的属性名
    -->
    <update id="updateUser" parameterType="User">
        UPDATE user
        SET username = #{username},
            password = #{password},
            email    = #{email},
            age      = #{age}
        WHERE id = #{id}
    </update>

    <!--
        6. 根据 ID 删除用户
    -->
    <delete id="deleteUserById" parameterType="int">
        DELETE FROM user
        WHERE id = #{id}
    </delete>

    <!--
        7. 多参数查询示例
           当方法有多个参数时,需要用 @Param 注解指定参数名
           XML 中用 #{参数名} 获取
    -->
    <select id="getUserByNameAndAge" resultType="User">
        SELECT
            <include refid="userColumns"/>
        FROM user
        WHERE username = #{username}
          AND age = #{age}
    </select>

</mapper>

到目前为止,你的项目目录结构应该是这样的:

mybatis-demo
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           ├── entity
    │   │           │   └── User.java            ← 实体类
    │   │           └── mapper
    │   │               └── UserMapper.java      ← Mapper 接口
    │   └── resources
    │       ├── mybatis-config.xml               ← MyBatis 核心配置
    │       ├── logback.xml                      ← 日志配置
    │       └── mapper
    │           └── UserMapper.xml               ← SQL 映射文件
    └── test
        └── java
            └── com
                └── example
                    └── MyBatisTest.java          ← 测试类(下一步创建)

步骤 8:编写工具类(获取 SqlSession)

💡 在写测试之前,我们先封装一个工具类,避免每次都写一堆重复的初始化代码。

src/main/java 下创建包com.example.util,新建类 MyBatisUtil.java

package com.example.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis 工具类
 * 作用:统一创建 SqlSession,避免重复代码
 */
public class MyBatisUtil {

    // SqlSessionFactory 全局只需要一个(重量级对象,创建开销大)
    private static SqlSessionFactory sqlSessionFactory;

    // 静态代码块:类加载时执行一次,初始化 SqlSessionFactory
    static {
        try {
            // 1. 读取 MyBatis 核心配置文件
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);

            // 2. 根据配置文件创建 SqlSessionFactory(工厂模式,生产 SqlSession)
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            // 3. 关闭流
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取 SqlSession 对象
     * SqlSession 是 MyBatis 操作数据库的核心对象,包含执行SQL、提交事务、回滚事务等方法
     * @param isAutoCommit 是否自动提交事务
     *                     true:自动提交,执行完SQL自动生效
     *                     false:手动提交,需要调用 sqlSession.commit() 才生效
     */
    public static SqlSession getSqlSession(boolean isAutoCommit) {
        return sqlSessionFactory.openSession(isAutoCommit);
    }

    /**
     * 重载方法:默认关闭自动提交(推荐,方便控制事务)
     */
    public static SqlSession getSqlSession() {
        return getSqlSession(false);
    }
}

步骤9:编写测试类,验证项目能否运行

激动人心的时刻到了! 我们来测试每一个 CRUD 操作。

在 src/test/java 下创建包 com.example,新建测试类 MyBatisTest.java

package com.example;

import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * MyBatis CRUD 测试类
 * 每个测试方法可以单独运行(点击方法左边的绿色三角按钮)
 */
public class MyBatisTest {

    // ==================== 查询测试 ====================

    /**
     * 测试1:根据 ID 查询用户
     */
    @Test
    public void testGetUserById() {
        // 1. 获取 SqlSession(相当于一次数据库会话)
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);

        try {
            // 2. 获取 Mapper 接口的代理对象(MyBatis 自动生成实现类)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 3. 调用方法,就像调用普通 Java 方法一样简单!
            User user = userMapper.getUserById(1);

            // 4. 打印结果
            System.out.println("========== 查询结果 ==========");
            System.out.println(user);
        } finally {
            // 5. 关闭 SqlSession(释放资源,很重要!)
            sqlSession.close();
        }
    }

    /**
     * 测试2:查询所有用户
     */
    @Test
    public void testGetAllUsers() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getAllUsers();

            System.out.println("========== 所有用户 ==========");
            for (User user : userList) {
                System.out.println(user);
            }
            System.out.println("共查询到 " + userList.size() + " 条数据");
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 测试3:根据用户名模糊查询
     */
    @Test
    public void testGetUsersByUsername() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 搜索用户名中包含"三"的用户
            List<User> users = userMapper.getUsersByUsername("三");

            System.out.println("========== 模糊查询结果 ==========");
            for (User user : users) {
                System.out.println(user);
            }
        } finally {
            sqlSession.close();
        }
    }

    // ==================== 新增测试 ====================

    /**
     * 测试4:新增用户
     */
    @Test
    public void testInsertUser() {
        // 注意:增删改操作建议不自动提交,手动控制事务
        SqlSession sqlSession = MyBatisUtil.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 创建新用户对象
            User newUser = new User();
            newUser.setUsername("孙七");
            newUser.setPassword("sun777");
            newUser.setEmail("sunqi@qq.com");
            newUser.setAge(26);

            // 执行插入
            int rows = userMapper.insertUser(newUser);

            // 手动提交事务(重要!不提交数据不会真正写入数据库)
            sqlSession.commit();

            System.out.println("========== 新增结果 ==========");
            System.out.println("影响行数:" + rows);
            System.out.println("新用户的自增ID:" + newUser.getId());  // 自动回填的ID
        } catch (Exception e) {
            // 出错时回滚事务
            sqlSession.rollback();
            System.err.println("新增失败:" + e.getMessage());
        } finally {
            sqlSession.close();
        }
    }

    // ==================== 修改测试 ====================

    /**
     * 测试5:修改用户信息
     */
    @Test
    public void testUpdateUser() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 先查出来,再修改
            User user = userMapper.getUserById(1);
            System.out.println("修改前:" + user);

            // 修改信息
            user.setEmail("zhangsan_new@qq.com");
            user.setAge(26);

            // 执行更新
            int rows = userMapper.updateUser(user);
            sqlSession.commit();

            // 再查一次,验证是否修改成功
            User updatedUser = userMapper.getUserById(1);
            System.out.println("修改后:" + updatedUser);
            System.out.println("影响行数:" + rows);
        } catch (Exception e) {
            sqlSession.rollback();
            System.err.println("修改失败:" + e.getMessage());
        } finally {
            sqlSession.close();
        }
    }

    // ==================== 删除测试 ====================

    /**
     * 测试6:删除用户
     */
    @Test
    public void testDeleteUser() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 删除 id=4 的用户(赵六)
            int rows = userMapper.deleteUserById(4);
            sqlSession.commit();

            System.out.println("========== 删除结果 ==========");
            System.out.println("影响行数:" + rows);

            // 验证:查询所有用户,看赵六是否还在
            List<User> allUsers = userMapper.getAllUsers();
            System.out.println("删除后剩余用户:");
            allUsers.forEach(System.out::println);
        } catch (Exception e) {
            sqlSession.rollback();
            System.err.println("删除失败:" + e.getMessage());
        } finally {
            sqlSession.close();
        }
    }

    // ==================== 多参数查询测试 ====================

    /**
     * 测试7:多参数查询
     */
    @Test
    public void testGetUserByNameAndAge() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession(true);

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            List<User> users = userMapper.getUserByNameAndAge("张三", 25);

            System.out.println("========== 多参数查询结果 ==========");
            for (User user : users) {
                System.out.println(user);
            }
        } finally {
            sqlSession.close();
        }
    }
}

运行测试

在 IDEA 中打开 MyBatisTest.java

  • 运行单个测试:点击某个测试方法左边的 绿色三角形 ▶️
  • 运行所有测试:点击类名左边的 绿色三角形 ▶️

成功运行后,你应该在控制台看到类似这样的输出(以 testGetAllUsers 为例):

✅ MyBatis 初始化成功!
10:30:15.123 [main] DEBUG com.example.mapper.UserMapper.getAllUsers - ==>  Preparing: SELECT id, username, password, email, age, create_time FROM user ORDER BY id ASC
10:30:15.156 [main] DEBUG com.example.mapper.UserMapper.getAllUsers - ==> Parameters: 
10:30:15.178 [main] DEBUG com.example.mapper.UserMapper.getAllUsers - <==      Total: 4
========== 所有用户 ==========
User(id=1, username=张三, password=123456, email=zhangsan@qq.com, age=25, createTime=...)
User(id=2, username=李四, password=654321, email=lisi@qq.com, age=30, createTime=...)
User(id=3, username=王五, password=abcdef, email=wangwu@163.com, age=28, createTime=...)
User(id=4, username=赵六, password=111111, email=zhaoliu@gmail.com, age=22, createTime=...)
共查询到 4 条数据

🎉 恭喜!你的第一个 MyBatis 项目成功运行了!


✅ 三、

 三、核心知识点讲解

代码跑起来了,现在让我们回头理解背后的原理,做到知其然更知其所以然。

3.1 什么是 ORM?

ORM(Object Relational Mapping)= 对象关系映射

别被这个名词吓到,其实很简单:

表格

数据库中的概念 Java 中的概念 映射关系
一张表(table) 一个类(class) user 表 ↔ User 类
一行数据(row) 一个对象(object) 一行用户数据 ↔ 一个 User 对象
一个字段(column) 一个属性(field) username 字段 ↔ username 属性

简单说:ORM 就是让 Java 对象和数据库表之间自动 "对号入座"

MyBatis 属于半自动 ORM 框架(SQL 还要自己写),Hibernate 属于全自动 ORM 框架(连 SQL 都帮你生成)。

3.2 MyBatis 核心执行流程

        ┌─────────────────────────────────────────────────────────┐
        │                   MyBatis 执行流程                       │
        └─────────────────────────────────────────────────────────┘

     ① 读取配置文件                    mybatis-config.xml
            │                              ↓
     ② 创建工厂对象              SqlSessionFactoryBuilder
            │                         .build(inputStream)
            │                              ↓
     ③ 得到工厂                    SqlSessionFactory
            │                              ↓
     ④ 打开会话                      SqlSession
            │                         (一次数据库对话)
            │                              ↓
     ⑤ 获取 Mapper 代理           sqlSession.getMapper(UserMapper.class)
            │                              ↓
     ⑥ 调用方法 → 执行 SQL        userMapper.getUserById(1)
            │                              ↓
     ⑦ 返回结果 → 映射为对象        User 对象
            │                              ↓
     ⑧ 关闭 SqlSession              sqlSession.close()

用大白话解释这个流程:

表格

步骤 做了什么 生活类比
① 读取配置文件 告诉 MyBatis 数据库在哪、账号密码多少 告诉快递员收件地址
② 创建工厂 根据配置创建一个 "生产线" 开了一家工厂
③ 打开会话 建立一次数据库连接 打了一通电话
④ 获取 Mapper 拿到操作数据库的工具 拿到了工具箱
⑤ 调用方法 执行具体的 SQL 操作 用工具干活
⑥ 关闭会话 释放连接资源 挂断电话

3.3 映射文件常用标签说明

表格

标签 用途 对应 SQL
<select> 查询 SELECT
<insert> 新增 INSERT
<update> 修改 UPDATE
<delete> 删除 DELETE
<resultMap> 自定义结果映射
<sql> 定义可复用的 SQL 片段
<include> 引用 SQL 片段
<if> 条件判断(动态 SQL)
<where> 智能 WHERE 条件(动态 SQL)
<foreach> 循环遍历(动态 SQL)

3.4 #{} 和 ${} 的区别(面试常考!)

这是 MyBatis 中最容易搞混的两个东西:

表格

对比 #{} ${}
本质 预编译参数(PreparedStatement 的?) 字符串直接拼接
安全性 ✅ 防止 SQL 注入 ❌ 有 SQL 注入风险
使用场景 绝大多数情况都用这个 动态表名、动态排序字段
示例 WHERE id = #{id} → WHERE id = ? ORDER BY ${columnName} → ORDER BY age

🔑 记住一句话:能用 #{} 就用 #{} ,只有在不能用的地方(表名、列名)才用 ${}


3.5 参数传递方式总结

表格

场景 接口写法 XML 中取值
单个基本类型参数 getUserById(Integer id) #{id} 或 #{随便写}
单个对象参数 insertUser(User user) #{属性名} 如 #{username}
多个参数 getUser(@Param("name") String name, @Param("age") Integer age) #{name}, #{age}
Map 参数 getUser(Map<String, Object> params) #{key 名}

💡 新手建议:多个参数时一定要加 @Param 注解,否则 MyBatis 不知道哪个参数对应哪个。


四、常见报错及解决办法

新手学 MyBatis 的过程中,踩坑是在所难免的。下面我把最常见的 8 个坑列出来,附上解决办法。

坑 1:找不到配置文件

报错信息

java.io.IOException: Could not find resource mybatis-config.xml

原因: 配置文件没放在 src/main/resources 根目录下。

解决办法

  • ✅ 确认 mybatis-config.xml 放在 src/main/resources/
  • ✅ 确认文件名拼写正确(注意大小写)
  • ✅ 在 IDEA 中右键 resources 文件夹,确认它被标记为 Resources Root(文件夹图标带黄色横线)

坑 2:Mapper XML 文件没注册

报错信息

org.apache.ibatis.binding.BindingException: 
Type interface com.example.mapper.UserMapper is not known to the MapperRegistry.

原因: 你写了 Mapper 接口和 XML 文件,但忘了在 mybatis-config.xml 中注册。

解决办法: 在 mybatis-config.xml<mappers> 中添加:

<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

坑 3:namespace 写错

报错信息

org.apache.ibatis.binding.BindingException: 
Invalid bound statement (not found): com.example.mapper.UserMapper.getUserById

原因: XML 文件中 namespace 和接口的全限定名不一致。

解决办法:检查 XML 中的 namespace:

<!-- ✅ 正确:必须是接口的完整包名+类名 -->
<mapper namespace="com.example.mapper.UserMapper">

<!-- ❌ 错误示例 -->
<mapper namespace="UserMapper">
<mapper namespace="com.example.dao.UserMapper">

坑 4:XML 中的 id 和接口方法名不一致

报错信息

Invalid bound statement (not found): com.example.mapper.UserMapper.findUserById

原因: XML 中 <select> 标签的 id 必须和接口中的方法名完全一致。

<!-- 接口中方法名是 getUserById -->
<!-- ✅ 正确 -->
<select id="getUserById" ...>

<!-- ❌ 错误 -->
<select id="findUserById" ...>
<select id="get_user_by_id" ...>

坑 5:增删改后数据没变化

现象: 代码没报错,但数据库里数据没有变化。原因没有提交事务! 这是新手最容易犯的错。

解决办法(二选一)

运行

// 方式1:手动提交
SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 默认不自动提交
// ...... 执行增删改 ......
sqlSession.commit(); // ⚠️ 别忘了这一行!

// 方式2:开启自动提交
SqlSession sqlSession = MyBatisUtil.getSqlSession(true); // 传入 true

坑 6:数据库连接失败

报错信息

com.mysql.cj.jdbc.exceptions.CommunicationsException: 
Communications link failure

排查清单

  • ✅ MySQL 服务是否启动了?(net start mysql 或在服务管理中查看)
  • ✅ 数据库地址、端口是否正确?(默认是 localhost:3306
  • ✅ 用户名和密码是否正确?
  • ✅ 对应数据库是否已创建?
  • ✅ MySQL 8.0 的驱动类名是 com.mysql.cj.jdbc.Driver(不是旧版 com.mysql.jdbc.Driver

坑 7:字段和属性映射不上,查询结果为 null

现象: 查询出来的对象,某些属性是 null,但数据库里明明有值。原因: 数据库字段名(如 create_time)和 Java 属性名(如 createTime)不一致。

解决办法(三选一)

<!-- 方法1:开启驼峰映射(推荐!最省事) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>

<!-- 方法2:在 SQL 中起别名 -->
SELECT create_time AS createTime FROM user

<!-- 方法3:使用 resultMap 手动映射 -->
<resultMap id="userMap" type="User">
    <result column="create_time" property="createTime"/>
</resultMap>

坑 8:XML 文件中特殊字符报错

报错信息

The content of elements must consist of well-formed character data or markup.

原因: SQL 中包含 <>& 等 XML 特殊字符。

解决办法

<!-- ❌ 错误写法:< 在 XML 中是特殊字符 -->
<select id="getUsersByAge" resultType="User">
    SELECT * FROM user WHERE age < 30
</select>

<!-- ✅ 方法1:使用转义字符 -->
<select id="getUsersByAge" resultType="User">
    SELECT * FROM user WHERE age &lt; 30
</select>

<!-- ✅ 方法2:使用 CDATA 区段(推荐) -->
<select id="getUsersByAge" resultType="User">
    SELECT * FROM user WHERE age <![CDATA[ < ]]> 30
</select>

常用转义字符对照表:

表格

特殊字符 转义写法
< <
> >
& &
" "
' '

🔥 五、进阶:动态 SQL 入门(附加彩蛋)

什么是动态 SQL?就是根据不同的条件,动态拼接不同的 SQL 语句,是 MyBatis 最强大的特性之一。

UserMapper.xml 追加如下代码:

<!--
    动态 SQL 示例:条件查询
    根据传入的参数动态拼接 WHERE 条件
    - 如果传了 username,就按 username 查
    - 如果传了 age,就按 age 查
    - 都传了,就两个条件一起查
    - 都没传,就查所有
-->
<select id="getUserByCondition" parameterType="User" resultType="User">
    SELECT
        <include refid="userColumns"/>
    FROM user

    <!-- <where> 标签自动处理第一个条件多余的 AND/OR -->
    <where>
        <!-- <if> 标签:条件判断,test 写 OGNL 表达式 -->
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>

        <if test="age != null">
            AND age = #{age}
        </if>

        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
    </where>

    ORDER BY id ASC
</select>

UserMapper.java 接口新增方法:

/**
 * 动态条件查询
 */
List<User> getUserByCondition(User user);

测试代码:

@Test
public void testDynamicQuery() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession(true);

    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // 场景1:只按用户名查
        User condition1 = new User();
        condition1.setUsername("张");
        System.out.println("=== 按用户名模糊查 ===");
        userMapper.getUserByCondition(condition1).forEach(System.out::println);

        // 场景2:只按年龄查
        User condition2 = new User();
        condition2.setAge(25);
        System.out.println("=== 按年龄查 ===");
        userMapper.getUserByCondition(condition2).forEach(System.out::println);

        // 场景3:用户名 + 年龄同时查
        User condition3 = new User();
        condition3.setUsername("张");
        condition3.setAge(25);
        System.out.println("=== 按用户名+年龄查 ===");
        userMapper.getUserByCondition(condition3).forEach(System.out::println);

        // 场景4:不传任何条件 → 查所有
        System.out.println("=== 查所有 ===");
        userMapper.getUserByCondition(new User()).forEach(System.out::println);

    } finally {
        sqlSession.close();
    }
}

💡 同一个方法,传入不同参数生成不同 SQL,这就是动态 SQL 的魅力!


📝 六、总结

学习重点回顾

表格

序号 知识点 掌握程度
1 MyBatis 是什么、解决什么问题 ⭐⭐⭐ 必须理解
2 核心配置文件 mybatis-config.xml 的编写 ⭐⭐⭐ 必须掌握
3 实体类、Mapper 接口、XML 映射文件的关系 ⭐⭐⭐ 必须掌握
4 基本 CRUD 操作 ⭐⭐⭐ 必须掌握
5 #{} 和 ${} 的区别 ⭐⭐⭐ 面试必考
6 SqlSession 的使用和事务提交 ⭐⭐⭐ 必须掌握
7 resultMap 结果映射 ⭐⭐ 重要
8 动态 SQL(if、where、foreach) ⭐⭐ 重要

整体架构图解

text

┌─────────────────────────────────────────────────────────────┐
│                        你的 Java 代码                        │
│          userMapper.getUserById(1)                           │
└─────────────────────┬───────────────────────────────────────┘
                      │ 调用
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                    Mapper 接口层                              │
│          UserMapper.java(只定义方法,不写实现)               │
└─────────────────────┬───────────────────────────────────────┘
                      │ MyBatis 自动绑定
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                    XML 映射文件                               │
│          UserMapper.xml(写具体的 SQL 语句)                   │
│          SELECT * FROM user WHERE id = #{id}                 │
└─────────────────────┬───────────────────────────────────────┘
                      │ 执行 SQL
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                      MySQL 数据库                            │
│                    user 表中的数据                            │
└─────────────────────────────────────────────────────────────┘

后续进阶学习路线

text

MyBatis 入门(✅ 你在这里)
    │
    ├── 📖 动态 SQL 深入(if、choose、foreach、trim)
    │
    ├── 📖 MyBatis 多表关联查询(一对一、一对多、多对多)
    │
    ├── 📖 MyBatis 缓存机制(一级缓存、二级缓存)
    │
    ├── 📖 MyBatis 注解开发(用注解替代 XML)
    │
    ├── 📖 MyBatis 分页插件(PageHelper)
    │
    ├── 📖 MyBatis 代码生成器(MyBatis Generator)
    │
    ├── 🚀 MyBatis + Spring 整合
    │
    └── 🚀 MyBatis + Spring Boot 整合(企业级开发必学)
          └── MyBatis-Plus(MyBatis 增强工具,更加简化开发)

📌 最后说两句:MyBatis 是 Java 后端开发的基本功,面试必问,工作天天用。这篇教程的代码都是经过验证的,建议你亲手敲一遍(不要复制粘贴),敲完之后再试着自己从零写一遍,印象会深刻很多。

如果这篇文章对你有帮助,点赞👍 + 收藏⭐ + 关注,后续我会继续更新 MyBatis 进阶系列教程!有问题欢迎在评论区留言,我看到都会回复!💬

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐