MyBatis零基础快速入门:十分钟掌握企业级数据操作(附实战代码)

面向 Java 新手,带你跳过繁琐配置,直达数据增删改查核心!

在 Java 企业级应用开发中,数据持久化是绕不开的环节。你可能听过 JDBC、JPA (Hibernate) 等技术,它们各有优劣。传统的 JDBC 代码量大、重复多,写起来很“累”;像 JPA 这样的全自动 ORM (Object-Relational Mapping) 框架,虽然自动化程度高,但在处理复杂、定制化的 SQL 查询时,有时会显得不够灵活,甚至让你感觉“失去了对数据库的控制”。

这时候,MyBatis 就像一位“懂你”的伙伴出现了!它是一个优秀的持久层框架,相比 JDBC,它极大地简化了数据库操作的代码;相比 JPA,它让你能够完全掌控 SQL 语句。这意味着你既能享受到框架带来的便利,又能充分发挥数据库的性能,写出高性能的定制化 SQL。

本指南就是为你——零基础的Java开发者——量身打造的MyBatis快速入门手册。我们将一起:

  • 🚀 快速搭建一个MyBatis开发环境。
  • ⚙️ 理解并编写MyBatis的核心配置文件
  • 📝 学会如何定义**数据操作接口(Mapper)**和对应的 SQL 映射文件
  • 🎯 通过完整的代码示例,亲手实现最常用的 CRUD(创建、读取、更新、删除)操作。
  • ⚠️ 梳理初学者常见的高频问题避坑指南,让你少走弯路。

准备好了吗?让我们马上开始MyBatis的探索之旅,十分钟内掌握企业级开发中的数据操作核心!


1. 为什么选择 MyBatis?

MyBatis 为何受到众多企业和开发者的青睐?它的魅力在于以下几点:

  • SQL 掌控力:这是MyBatis最突出的优点。你可以直接在 XML 文件或注解中编写原生的 SQL 语句。MyBatis 负责帮你把 Java 方法和 SQL 绑定起来,自动处理输入参数和查询结果到 Java 对象的映射。
  • 告别繁琐 JDBC:MyBatis 封装了大量 JDBC 的模板代码,比如打开/关闭连接、创建 Statement、处理 ResultSet 等,让你专注于业务逻辑和 SQL 本身。
  • 灵活映射:无论是简单的属性映射,还是复杂的联合查询结果,MyBatis 提供了强大的 <resultMap> 等配置,轻松应对各种数据结构的映射需求。
  • 易集成:与 Spring、Spring Boot 等主流 Java 框架无缝集成,在企业级项目中应用起来非常方便。
  • 半自动化 ORM:它不像 JPA 那样完全隐藏 SQL,而是介于 JDBC 和全自动 ORM 之间,为你提供了一个平衡点。

总结一下:如果你需要编写高性能、定制化的 SQL,同时又希望摆脱 JDBC 的繁琐,那么MyBatis绝对是你的不二之选!


2. 环境准备:项目搭建与依赖引入

“千里之行,始于足下”。要使用 MyBatis,我们得先搭建一个基础的 Java 项目环境。

2.1 开发环境基础

  • 确保你已经安装了 Java Development Kit (JDK),推荐使用 JDK 8 或更高版本
  • 推荐使用 Maven 或 Gradle 作为项目管理工具,它们能帮助我们轻松管理项目依赖。本指南以 Maven 为例。
  • 准备一个数据库,例如 MySQL。安装并启动 MySQL 服务。

2.2 创建 Maven 项目

打开你的集成开发环境 (IDE),如 IntelliJ IDEA 或 Eclipse,创建一个新的 Maven 项目。

2.3 引入 MyBatis 及数据库驱动依赖

在创建好的 Maven 项目的 pom.xml 文件中,添加 MyBatis 核心库和你的数据库驱动(这里以 MySQL 为例)的依赖。

<?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-quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <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>
        <!-- MyBatis 核心依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version> <!-- 使用最新稳定版本,建议查看 Maven Central -->
        </dependency>

        <!-- MySQL 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version> <!-- 根据你的 MySQL 版本选择 -->
        </dependency>

        <!-- 日志依赖(可选,便于查看 MyBatis 生成的 SQL) -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version> <!-- 推荐使用 SLF4J -->
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.36</version>
            <scope>test</scope> <!-- 日志通常在测试或开发环境使用 -->
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>test</scope>
        </dependency>

        <!-- 单元测试依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

小提示:将上面的代码复制到你的 pom.xml 文件中,然后刷新 Maven 项目(IDE 会提示)。依赖就会自动下载到你的本地仓库。

2.4 项目结构

一个标准的 MyBatis standalone 项目结构如下,请参照创建对应的包和文件夹:

myBatis-quickstart/
├── src/
│   ├── main/
│   │   ├── java/         # 存放你的 Java 源代码
│   │   │   └── com/example/mybatis/
│   │   │       ├── pojo/     # 存放实体类 (POJO)
│   │   │       │   └── User.java
│   │   │       └── mapper/   # 存放 Mapper 接口
│   │   │           └── UserMapper.java
│   │   └── resources/    # 存放配置文件和 Mapper XML
│   │       ├── mybatis-config.xml # MyBatis 全局配置文件
│   │       ├── mapper/       # 存放 Mapper XML 文件
│   │       │   └── UserMapper.xml
│   │       └── log4j.properties # 日志配置文件 (可选)
│   └── test/             # 存放单元测试代码 (推荐)
│       └── java/
│           └── com/example/mybatis/
│               └── MyBatisTest.java
└── pom.xml             # Maven 配置文件

请注意resources 文件夹下的内容在打包时会被复制到 classpath 的根目录。


3. MyBatis 核心配置 (mybatis-config.xml)

mybatis-config.xml 是 MyBatis 的全局配置文件,它告诉 MyBatis 如何连接数据库、如何加载 Mapper 等。这个文件通常放在 src/main/resources 目录下。

3.1 配置文件骨架

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- environments:配置不同的数据库环境,default 指定当前使用哪个环境 -->
    <environments default="development">
        <!-- environment:定义一个具体的环境 -->
        <environment id="development">
            <!-- transactionManager:事务管理器,JDBC 表示使用 JDBC 事务 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource:数据源,POOLED 表示使用 MyBatis 内置的连接池 -->
            <dataSource type="POOLED">
                <!-- property:配置数据库连接信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- TODO: 将 your_database, your_username, your_password 替换为你自己的数据库信息 -->
                <property name="url" value="jdbc:mysql://localhost:3306/your_database?useSSL=false&amp;serverTimezone=UTC"/>
                <property name="username" value="your_username"/>
                <property name="password" value="your_password"/>
            </dataSource>
        </environment>
        <!-- 实际项目中可能还有 production 等环境 -->
        <!--
        <environment id="production">
            ...
        </environment>
        -->
    </environments>

    <!-- mappers:注册你的 Mapper 文件或接口 -->
    <mappers>
        <!-- resource:通过 classpath 路径加载 Mapper XML 文件 -->
        <mapper resource="mapper/UserMapper.xml"/>
        <!-- 或者:package:扫描指定包下的所有 Mapper 接口(接口名需与 XML 文件名对应,XML 在相同包结构下) -->
        <!-- <package name="com.example.mybatis.mapper"/> -->
    </mappers>

    <!-- 其他常用配置(可选) -->
    <settings>
        <!-- 配置 MyBatis 日志实现,方便调试 -->
        <setting name="logImpl" value="LOG4J"/>
        <!-- 开启驼峰命名自动映射:数据库列名 user_name 自动映射到 Java 属性 userName -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- typeAliases:为 POJO 类配置别名,方便在 XML 中引用 -->
    <typeAliases>
         <!-- package:扫描指定包下的所有类,类名即为别名(首字母大小写不敏感) -->
         <package name="com.example.mybatis.pojo"/>
         <!-- 或者:type:为单个类指定别名 -->
         <!-- <typeAlias type="com.example.mybatis.pojo.User" alias="User"/> -->
    </typeAliases>

</configuration>

重要!:请将文件中的 your_database, your_username, your_password 替换为你实际的数据库连接信息。

3.2 核心配置项解释

  • <environments>:可以配置多个数据库连接环境,default 属性指定当前激活的环境 ID。
  • <environment>:定义一个具体的环境配置,id 是唯一的标识。
    • <transactionManager>:配置事务管理方式,JDBC 表示使用原生的 JDBC 事务管理(需要手动提交/回滚),MANAGED 表示由外部容器管理(如 Spring)。
    • <dataSource>:配置数据源,通常使用连接池 (POOLED) 以提高性能。内部通过 <property> 配置连接数据库所需的参数。
  • <mappers>:**非常重要!**在这里注册你的 Mapper 文件。MyBatis 需要知道去哪里找到你的 SQL 映射。resource 方式适合少量 Mapper,package 方式适合大量 Mapper,MyBatis 会自动扫描指定包。
  • <settings>:配置 MyBatis 的全局行为,比如日志输出、缓存设置、驼峰命名自动映射等。开启 mapUnderscoreToCamelCase 对提高开发效率非常有帮助。
  • <typeAliases>:给你的 Java 类(尤其是 POJO)起个“昵称”,在 Mapper XML 中就可以直接用昵称代替完整的类路径了,让 XML 更简洁。使用 package 扫描更方便。

4. 创建实体类 (POJO) 和 Mapper 接口/XML

MyBatis 的核心是 Mapper,它连接你的 Java 代码和 SQL 语句。通常,一个 Mapper 对应数据库的一张表。

4.1 实体类 (POJO)

首先,创建一个 Java Bean 来映射数据库表的行数据。假设我们有一个 users 表,包含 id, username, age 字段。

数据库表结构:

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    age INT
);

User.java:

package com.example.mybatis.pojo;

import java.io.Serializable;

// User 实体类,映射数据库 users 表
public class User implements Serializable {
    private Long id; // 对应数据库的 id 字段
    private String username; // 对应数据库的 username 字段
    private Integer age; // 对应数据库的 age 字段

    // TODO: 根据实际需求添加更多属性

    // 必须提供无参构造函数(MyBatis 需要通过它创建对象)
    public User() {
    }

    // 常用构造函数
    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    // Getters 和 Setters 方法(MyBatis 需要通过它们访问属性)
    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 Integer getAge() {
        return age;
    }

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

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

4.2 Mapper 接口

创建一个 Java 接口,声明你想要执行的数据库操作方法。

UserMapper.java:

package com.example.mybatis.mapper;

import com.example.mybatis.pojo.User;
import org.apache.ibatis.annotations.Param; // 当方法有多个参数时,建议使用 @Param 注解

import java.util.List;

// UserMapper 接口:定义了针对 User 对象的数据库操作方法
public interface UserMapper {

    /**
     * 根据用户 ID 查询用户信息
     * @param id 用户 ID
     * @return 对应的 User 对象,如果不存在则返回 null
     */
    User selectUserById(Long id);

    /**
     * 查询所有用户信息
     * @return 所有 User 对象的列表
     */
    List<User> selectAllUsers();

    /**
     * 插入一个新用户
     * @param user 待插入的 User 对象
     * @return 插入的记录数
     */
    int insertUser(User user);

    /**
     * 更新用户信息
     * @param user 待更新的 User 对象 (通常包含 id 和需要修改的属性)
     * @return 更新的记录数
     */
    int updateUser(User user);

    /**
     * 根据用户 ID 删除用户
     * @param id 待删除用户的 ID
     * @return 删除的记录数
     */
    int deleteUserById(Long id);

    /**
     * 根据用户名模糊查询用户列表
     * @param usernameKeyword 用户名关键字
     * @return 符合条件的 User 对象列表
     */
    List<User> selectUsersByUsername(@Param("usernameKeyword") String usernameKeyword);

    // TODO: 你可以在这里继续添加其他需要的数据库操作方法
}

4.3 Mapper XML 文件

创建与 Mapper 接口同名的 XML 文件 (UserMapper.xml),通常放在 src/main/resources/mapper/ 目录下,用来编写具体的 SQL 语句。

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">
<!-- namespace 必须与你的 Mapper 接口全限定名(包名+类名)完全一致 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">

    <!-- 定义一个结果集映射,将数据库列名映射到 POJO 属性名 -->
    <!-- id: 映射的唯一标识,type: 映射到的 POJO 类型 -->
    <resultMap id="userResultMap" type="com.example.mybatis.pojo.User">
        <!-- id: 主键列映射 -->
        <!-- column: 数据库列名, property: POJO 属性名 -->
        <id column="id" property="id"/>
        <!-- result: 普通列映射 -->
        <result column="username" property="username"/>
        <result column="age" property="age"/>
        <!-- TODO: 如果 User 类有更多属性需要映射,在这里添加对应的 <result> 标签 -->
    </resultMap>

    <!-- 根据ID查询用户 -->
    <!-- id: 必须与 Mapper 接口中的方法名 selectUserById 完全一致 -->
    <!-- resultType: 指定查询结果映射到的单行 POJO 类型 -->
    <!-- #{} 是参数占位符,防止 SQL 注入 -->
    <select id="selectUserById" resultType="com.example.mybatis.pojo.User">
        SELECT id, username, age
        FROM users
        WHERE id = #{id}
    </select>

    <!-- 查询所有用户 -->
    <select id="selectAllUsers" resultType="com.example.mybatis.pojo.User">
        SELECT id, username, age
        FROM users
    </select>

    <!-- 插入一个新用户 -->
    <!-- parameterType: 指定输入参数的类型 -->
    <!-- useGeneratedKeys="true" keyProperty="id": 用于获取数据库自增主键的值,并设置到传入的 User 对象的 id 属性上 -->
    <insert id="insertUser" parameterType="com.example.mybatis.pojo.User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (username, age)
        VALUES (#{username}, #{age})
    </insert>

    <!-- 更新用户信息 -->
    <update id="updateUser" parameterType="com.example.mybatis.pojo.User">
        UPDATE users
        SET username = #{username}, age = #{age}
        WHERE id = #{id}
    </update>

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

    <!-- 根据用户名模糊查询 -->
    <!-- resultMap: 引用上面定义好的结果集映射 -->
    <!-- #{usernameKeyword}: 这里的参数名需要和 Mapper 接口中 @Param("usernameKeyword") 的值一致 -->
    <select id="selectUsersByUsername" resultMap="userResultMap">
        SELECT id, username, age
        FROM users
        WHERE username LIKE CONCAT('%', #{usernameKeyword}, '%')
    </select>

    <!-- TODO: 根据你的 Mapper 接口继续添加对应的 SQL 映射 -->

</mapper>

4.4 接口与 XML 的“联姻”

MyBatis 如何知道 UserMapper 接口的 selectUserById(Long id) 方法对应 UserMapper.xml 中的哪个 <select> 标签呢?

关键在于两点:

  1. XML 文件中的 <mapper namespace="..."> 的值必须与 Mapper 接口的**全限定名(包名+类名)**完全一致。
  2. XML 文件中每个 SQL 标签(<select>, <insert>, <update>, <delete>)的 id 属性值必须与 Mapper 接口中对应的方法名完全一致。

MyBatis 通过 namespace + id 来找到并执行对应的 SQL 语句。

重要概念解释:

  • #{...}:参数占位符。MyBatis 会将其替换为对应的参数值,并在底层使用 PreparedStatement 设置参数。这是推荐的方式,可以有效防止 SQL 注入。
  • ${...}:字符串替换。MyBatis 会直接将值拼接到 SQL 字符串中。存在 SQL 注入风险,通常只用于替换表名、列名等无法使用参数占位符的场景。小白阶段请慎用
  • resultType:指定查询结果单行数据映射到的 Java 类型。
  • resultMap:用于定义复杂的列到属性映射规则,或者处理关联查询、继承等场景。当数据库列名和 POJO 属性名不一致,且未开启 mapUnderscoreToCamelCase 时,也需要用到它。

5. 执行 CRUD 操作:让代码跑起来!

现在配置和 Mapper 都准备好了,是时候让 MyBatis 连接数据库并执行操作了!

5.1 核心 API 对象

  • SqlSessionFactory:顾名思义,它是创建 SqlSession 的工厂。一个应用通常只需要一个 SqlSessionFactory。它是线程安全的。
  • SqlSession:MyBatis 工作单元,包含了所有执行 SQL 的方法。它不是线程安全的,所以每个线程都应该有自己的 SqlSession 实例。用完后必须关闭。

5.2 构建 SqlSessionFactory 工具类

我们创建一个简单的工具类来获取 SqlSessionFactory

MyBatisUtil.java:

package com.example.mybatis;

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

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

// MyBatis 工具类,用于获取 SqlSessionFactory
public class MyBatisUtil {

    // SqlSessionFactory 是线程安全的,应用程序生命周期内只需要一个
    private static SqlSessionFactory sqlSessionFactory;

    static {
        // 类加载时执行,初始化 SqlSessionFactory
        try {
            // 指定 mybatis-config.xml 配置文件的路径 (相对 classpath)
            String resource = "mybatis-config.xml";
            // 读取配置文件为 InputStream
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // TODO: 可以根据需要加载不同的 environment (比如 "production")
            // sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "production");
        } catch (IOException e) {
            e.printStackTrace();
            // 如果初始化失败,可以抛出运行时异常或记录日志
            throw new ExceptionInInitializerError("初始化 SqlSessionFactory 失败: " + e.getMessage());
        }
    }

    /**
     * 获取 SqlSessionFactory 实例
     * @return SqlSessionFactory
     */
    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    // TODO: 可以根据需要提供获取 SqlSession 的便捷方法
}

5.3 编写测试代码执行 CRUD

现在,我们编写一个简单的 Main 方法或者 JUnit 测试来执行我们定义的 CRUD 操作。JUnit 测试更规范,这里使用 JUnit 示例。

src/test/java/com/example/mybatis/MyBatisTest.java:

package com.example.mybatis;

import com.example.mybatis.mapper.UserMapper;
import com.example.mybatis.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*; // JUnit 断言库

// MyBatis 测试类
public class MyBatisTest {

    private SqlSession sqlSession; // 工作单元
    private UserMapper userMapper; // Mapper 接口代理对象

    @Before // 在每个测试方法执行前运行
    public void setUp() throws Exception {
        // 1. 获取 SqlSession (默认不自动提交事务)
        sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
        // 如果希望自动提交,可以使用 openSession(true);

        // 2. 获取 Mapper 接口的代理对象
        userMapper = sqlSession.getMapper(UserMapper.class);

        System.out.println("\n--- 开始测试 ---");
    }

    @After // 在每个测试方法执行后运行
    public void tearDown() throws Exception {
        // 3. 关闭 SqlSession
        if (sqlSession != null) {
            // 测试完成后,通常回滚事务,避免测试数据污染数据库
            // 实际业务代码中需要根据情况 commit 或 rollback
             sqlSession.rollback(); // 测试结束回滚
             // sqlSession.commit(); // 如果希望测试修改数据库,则提交
            sqlSession.close();
            sqlSession = null; // 避免资源泄露
        }
        System.out.println("--- 测试结束 ---\n");
    }

    @Test // 标记这是一个测试方法
    public void testInsertUser() {
        System.out.println("--- 测试 Insert ---");
        User newUser = new User("test_insert", 30);
        int count = userMapper.insertUser(newUser);
        System.out.println("插入用户: " + newUser);
        System.out.println("插入记录数: " + count);
        // 断言插入成功
        assertEquals(1, count);
        // 断言自增主键已设置到对象上
        assertNotNull(newUser.getId());

        System.out.println("插入成功,新用户 ID: " + newUser.getId());

        // 提交事务,使插入生效 (在 @After 中回滚,这里只是演示提交)
        // sqlSession.commit();
    }

    @Test
    public void testSelectUserById() {
        System.out.println("--- 测试 Select By ID ---");
        // TODO: 确保数据库中存在 ID 为 1 的用户,或者先插入一个用户
        // 为了测试稳定,我们可以先插入一个用户,再查询
        User tempUser = new User("temp_select", 28);
        userMapper.insertUser(tempUser); // 插入临时用户
        sqlSession.commit(); // 提交插入事务,否则查不到

        Long userIdToQuery = tempUser.getId(); // 获取插入的临时用户 ID
        System.out.println("准备查询用户 ID: " + userIdToQuery);

        User user = userMapper.selectUserById(userIdToQuery);
        System.out.println("查询结果: " + user);

        // 断言查询结果不为 null
        assertNotNull(user);
        // 断言查询到的用户 ID 正确
        assertEquals(userIdToQuery, user.getId());

        // 清理临时数据 (可选,因为 @After 会回滚)
        // userMapper.deleteUserById(userIdToQuery);
        // sqlSession.commit();
    }

    @Test
    public void testSelectAllUsers() {
        System.out.println("--- 测试 Select All ---");
        List<User> userList = userMapper.selectAllUsers();
        System.out.println("查询所有用户结果:");
        userList.forEach(System.out::println);

        // 断言查询结果不为 null
        assertNotNull(userList);
        // 断言列表大小,取决于你的数据库有多少用户
        // assertTrue(userList.size() > 0);
    }

    @Test
    public void testUpdateUser() {
        System.out.println("--- 测试 Update ---");
        // TODO: 确保数据库中存在一个用户用于更新,或者先插入一个
        User tempUser = new User("temp_update", 22);
        userMapper.insertUser(tempUser); // 插入临时用户
        sqlSession.commit(); // 提交插入

        Long userIdToUpdate = tempUser.getId();
        System.out.println("准备更新用户 ID: " + userIdToUpdate);

        // 查询出用户,然后修改属性进行更新
        User userToUpdate = userMapper.selectUserById(userIdToUpdate);
        assertNotNull(userToUpdate); // 确保用户存在

        System.out.println("更新前用户信息: " + userToUpdate);
        userToUpdate.setAge(userToUpdate.getAge() + 1); // 年龄加一
        userToUpdate.setUsername(userToUpdate.getUsername() + "_updated"); // 用户名加后缀

        int count = userMapper.updateUser(userToUpdate);
        System.out.println("更新记录数: " + count);
        assertEquals(1, count); // 断言更新成功

        // 提交更新事务 (或者依赖 @After 的回滚)
        // sqlSession.commit();

        // 重新查询,验证更新是否生效
        User updatedUser = userMapper.selectUserById(userIdToUpdate);
        System.out.println("更新后用户信息: " + updatedUser);
        assertEquals(userToUpdate.getAge(), updatedUser.getAge());
        assertEquals(userToUpdate.getUsername(), updatedUser.getUsername());
    }

    @Test
    public void testDeleteUserById() {
        System.out.println("--- 测试 Delete ---");
        // TODO: 确保数据库中存在一个用户用于删除,或者先插入一个
        User tempUser = new User("temp_delete", 99);
        userMapper.insertUser(tempUser); // 插入临时用户
        sqlSession.commit(); // 提交插入

        Long userIdToDelete = tempUser.getId();
        System.out.println("准备删除用户 ID: " + userIdToDelete);

        // 先确认用户存在
        User userToDelete = userMapper.selectUserById(userIdToDelete);
        assertNotNull(userToDelete);

        int count = userMapper.deleteUserById(userIdToDelete);
        System.out.println("删除记录数: " + count);
        assertEquals(1, count); // 断言删除成功

        // 提交删除事务 (或者依赖 @After 的回滚)
        // sqlSession.commit();

        // 尝试再次查询,验证用户是否已被删除
        User deletedUser = userMapper.selectUserById(userIdToDelete);
        System.out.println("删除后查询结果: " + deletedUser);
        assertNull(deletedUser); // 断言用户不存在
    }

    @Test
    public void testSelectUsersByUsernameKeyword() {
        System.out.println("--- 测试 Select By Username Keyword ---");
         // TODO: 确保数据库中有包含 "test" 关键字的用户
         // 插入一些测试数据
         userMapper.insertUser(new User("test_keyword1", 10));
         userMapper.insertUser(new User("another_test", 20));
         userMapper.insertUser(new User("no_match", 30));
         sqlSession.commit(); // 提交插入

        List<User> users = userMapper.selectUsersByUsername("test");
        System.out.println("根据关键字 'test' 查询结果:");
        users.forEach(System.out::println);

        // 断言查询结果不为 null
        assertNotNull(users);
        // 断言至少找到 2 个用户
        assertTrue(users.size() >= 2);

        // 再次查询,测试找不到的情况
        List<User> noMatchUsers = userMapper.selectUsersByUsername("nonexistent");
        System.out.println("根据关键字 'nonexistent' 查询结果:");
        noMatchUsers.forEach(System.out::println);
        assertNotNull(noMatchUsers);
        assertTrue(noMatchUsers.isEmpty()); // 断言列表为空
    }

    // TODO: 可以添加更多测试方法来验证你的 Mapper 方法
}

动手试试! 运行 MyBatisTest 类中的测试方法 (testInsertUser, testSelectUserById 等)。如果一切配置正确,你应该能在控制台看到 MyBais 执行的 SQL 和结果。这会给你极大的成就感!


6. 动态 SQL 基础:构建灵活的查询

实际开发中,查询条件往往是动态变化的。MyBatis 提供了强大的动态 SQL 功能,让你在 XML 中像写代码一样构建灵活的 SQL 语句,避免在 Java 代码中进行丑陋的字符串拼接。

常用的动态 SQL 标签有:<if>, <where>, <set>, <foreach>, <choose>, <when>, <otherwise>, <trim> 等。

6.1 <if><where> 示例

假设我们要实现一个根据用户名(模糊查询)和/或年龄来查询用户的接口。

Mapper 接口方法不变:

List<User> selectUsersByCriteria(
    @Param("username") String username,
    @Param("age") Integer age);

Mapper XML 中对应的 SQL:

<select id="selectUsersByCriteria" resultType="com.example.mybatis.pojo.User">
    SELECT id, username, age
    FROM users
    <!-- <where> 标签智能处理 WHERE 子句 -->
    <!-- 它会在内部有条件成立时自动加上 WHERE 关键字 -->
    <!-- 并且会智能移除第一个条件前的 AND 或 OR -->
    <where>
        <!-- <if test="..."> 条件判断,test 中的内容是 OGNL 表达式 -->
        <if test="username != null and username != ''">
            username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <!-- 如果既有 username 又有 age,最终生成的 SQL 类似:
             SELECT ... FROM users WHERE username LIKE ... AND age = ...
             <where> 标签自动移除了 "username LIKE ..." 前的 WHERE,以及 "AND age = #{age}" 前的 "AND" (如果 age 是第一个条件的话)。
             但因为上面 username 先判断,所以 age 前面的 AND 会保留下来,<where> 只会处理第一个条件前面的多余 AND/OR。
             更常见的用法是:
             <where>
                 <if test="..."> username LIKE ... </if>
                 <if test="..."> AND age = ... </if>
             </where>
             这样 <where> 会确保只有条件存在时才加 WHERE,并处理第一个条件前的多余 AND。
        -->
    </where>
</select>

OGNL 表达式:在 <if test="..."> 等标签中,你可以使用 OGNL (Object-Graph Navigation Language) 表达式来访问方法参数。如果参数是 POJO,可以直接写属性名,如 test="user.age != null";如果是多个参数且使用了 @Param,则使用 @Param 指定的名称,如 test="username != null";如果是单个基本类型参数,可以直接使用参数名或者 _parameter。判断字符串是否非空,通常用 != null and != ''

6.2 <foreach> 示例

查询指定 ID 列表的用户是一个常见需求。<foreach> 标签非常适合处理集合参数(List, Array, Set)或 Map。

Mapper 接口方法:

List<User> selectUsersByIds(@Param("ids") List<Long> ids);

Mapper XML 中对应的 SQL:

<select id="selectUsersByIds" resultType="com.example.mybatis.pojo.User">
    SELECT id, username, age
    FROM users
    WHERE id IN
    <!-- 遍历集合参数 ids -->
    <!-- collection: 指定要遍历的集合参数名 (对应 @Param("ids")) -->
    <!-- item: 遍历过程中当前元素的变量名 -->
    <!-- open: 遍历开始前插入的字符串 -->
    <!-- separator: 每次遍历元素之间插入的字符串 -->
    <!-- close: 遍历结束后插入的字符串 -->
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id} <!-- 使用 #{} 获取当前元素的值 -->
    </foreach>
</select>

如果传入 ids[1L, 5L, 10L],MyBatis 最终生成的 SQL 类似:

SELECT id, username, age FROM users WHERE id IN ( ? , ? , ? )

MyBatis 会将 1, 5, 10 作为预编译参数设置进去。这比手动拼接 (1,5,10) 要安全得多。

更多动态 SQL 标签: 还有 <set> (用于 UPDATE 语句,自动移除末尾逗号), <choose>/<when>/<otherwise> (多分支判断) 等,都非常实用。建议查阅官方文档深入学习。


7. 集成 Spring/Spring Boot (进阶了解)

在真实的企业级开发中,MyBatis 通常与 Spring 或 Spring Boot 框架一起使用。Spring 提供了强大的依赖注入和事务管理功能,可以极大地简化 MyBatis 的配置和使用。

如果你正在学习 Spring Boot,你会发现集成 MyBatis 变得异常简单。只需要引入 mybatis-spring-boot-starter 依赖,在 application.properties (或 application.yml) 中配置数据源和 MyBatis 相关的路径,再在 Spring Boot 启动类上添加 @MapperScan 注解扫描你的 Mapper 接口包,MyBatis 就会自动配置好 SqlSessionFactorySqlSession,并将你的 Mapper 接口注册为 Spring Bean,可以直接通过 @Autowired 注入使用了。

# application.properties 示例
spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.mapper-locations=classpath*:mapper/*Mapper.xml # Mapper XML 位置
mybatis.type-aliases-package=com.example.mybatis.pojo # POJO 包
mybatis.configuration.map-underscore-to-camel-case=true # 开启驼峰映射
// Spring Boot 启动类示例
package com.example.myapp;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mybatis.mapper") // 扫描你的 Mapper 接口所在的包
public class MyAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }
}

在 Service 类中直接注入 Mapper 接口:

// Service 层示例
package com.example.myapp.service.impl;

import com.example.mybatis.mapper.UserMapper;
import com.example.mybatis.pojo.User;
import com.example.myapp.service.UserService; // 假设你有 UserService 接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // Spring 事务注解

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    // 直接注入 Mapper 接口的代理对象
    @Autowired
    private UserMapper userMapper;

    @Override
    @Transactional // 开启 Spring 事务管理
    public int createUser(User user) {
        return userMapper.insertUser(user);
    }

    @Override
    public User getUserById(Long id) {
        return userMapper.selectUserById(id);
    }

    // ... 其他方法类似

}

通过 Spring Boot 集成,你可以不再关心 SqlSessionFactorySqlSession 的创建和管理,事务也由 @Transactional 注解搞定,开发效率大幅提升!这部分内容更深入,适合你在掌握 MyBatis 基础后进一步学习。

8. 初学者避坑指南 (高频问题解答)

作为新手,你很可能会遇到一些常见问题,别担心,这里帮你整理好了,遇到问题先来看看这里!

  • 问题1:resources 下的配置文件或 Mapper XML 找不到?
    • 原因:路径不对,或者 Maven/Gradle 没有将 resources 目录下的文件复制到 target/classes (即 classpath)。
    • 解决方案
      • 检查 mybatis-config.xmlsrc/main/resources 下。
      • 检查 mapper/UserMapper.xmlsrc/main/resources/mapper 下。
      • 检查 mybatis-config.xml<mapper resource="mapper/UserMapper.xml"/> 的路径是否正确。
      • 如果是 Maven 项目,确认 pom.xml 中没有阻止资源文件打包的配置。通常 src/main/resources 会自动打包。
      • 清理并重新构建项目 (mvn clean package)。
  • 问题2:namespaceid 匹配错误,报 Bound statement not found 异常?
    • 原因:MyBatis 通过 namespace + id 找到对应的 SQL 映射,任何一个不匹配都会导致找不到。
    • 解决方案
      • 检查 Mapper XML 文件中 <mapper namespace="..."> 的值是否完全等于 Mapper 接口的全限定名
      • 检查 Mapper XML 中 SQL 标签的 id 属性值是否完全等于 Mapper 接口中对应的方法名。
  • 问题3:参数传不进去,或者参数映射错误?
    • 原因:参数类型与 XML 中声明的 parameterType 不匹配,或者多参数方法没有使用 @Param 注解,导致 MyBatis 不知道如何取值。
    • 解决方案
      • 如果只有一个参数,XML 中可以使用 parameterType 指定类型,在 SQL 中直接用 #{参数名}#{_parameter} 访问。
      • 如果参数是 POJO,XML 中 parameterType 指向 POJO 类,SQL 中使用 #{属性名}
      • 强烈建议多参数方法都使用 @Param("参数别名") 注解,然后在 XML 中使用 #{参数别名} 访问。例如 selectUserById(@Param("userId") Long id) 对应 XML 中的 WHERE id = #{userId}
      • 检查 #{}${} 是否用错。大多数情况下应该使用 #{}
  • 问题4:数据库连接失败?
    • 原因mybatis-config.xml 中的数据库连接信息(url, username, password, driver)错误,或者数据库服务未启动,防火墙阻止连接等。
    • 解决方案
      • 仔细检查 mybatis-config.xml 中的数据库连接配置。
      • 确认数据库服务正在运行,并且监听了正确的端口。
      • 确保 JDBC 驱动 (mysql-connector-java) 版本与你的数据库兼容。
      • 如果是远程数据库,检查网络连接和防火墙设置。
      • serverTimezone=UTCuseSSL=false 参数对于 MySQL 8+ 常见,通常需要加上。
  • 问题5:插入后自增主键回填不到对象上?
    • 原因:插入标签 <insert> 没有正确配置 useGeneratedKeys="true"keyProperty="你的主键属性名"
    • 解决方案:确保插入标签中添加了 useGeneratedKeys="true" keyProperty="id" (假设你的主键属性名是 id)。
  • 问题6:查询结果映射错误,部分属性为 null?
    • 原因:数据库列名与 POJO 属性名不一致,且没有使用 <resultMap> 或开启 mapUnderscoreToCamelCase
    • 解决方案
      • 推荐开启 <setting name="mapUnderscoreToCamelCase" value="true"/> 来自动映射 user_nameuserName
      • 如果列名和属性名差异较大,或者需要处理复杂映射,定义并使用 <resultMap> 进行显式映射。

温馨提示:遇到问题时,仔细查看控制台输出的错误日志。MyBatis 的日志通常会给出非常详细的错误信息,结合这些信息去排查问题会事半功倍!如果开启了日志 (<setting name="logImpl" value="LOG4J"/> 并配置了 log4j.properties),还能看到 MyBatis 实际执行的 SQL 语句,这对于调试非常有帮助。


你的 MyBatis 之旅刚刚开始!

恭喜你!通过本指南的学习和实践,你已经掌握了 MyBatis 的基础知识和核心用法,能够独立搭建环境、配置、编写 Mapper 并执行基本的数据库 CRUD 操作。这为你进一步深入学习和在企业级项目中应用 MyBatis 打下了坚实的基础。

核心知识点再回顾:

  • 定位:MyBatis 是一个灵活的持久层框架,让你掌控 SQL。
  • 配置mybatis-config.xml 是全局指挥中心。
  • 映射:Mapper 接口定义操作,Mapper XML 编写 SQL 并负责映射 (#{}, ${})。
  • APISqlSessionFactory (工厂) 和 SqlSession (工作单元)。
  • CRUD:掌握 <select>, <insert>, <update>, <delete> 标签的基本用法。
  • 动态 SQL<if>, <where>, <foreach> 等标签让你写出更灵活的 SQL。
  • 集成:与 Spring/Spring Boot 集成是企业级开发的常见模式。
  • 避坑:注意 namespace/id 匹配、参数传递、#{} vs ${}、事务管理等。

接下来,你可以:

  1. 巩固基础:多动手实践,尝试实现更复杂的查询,比如带分页、排序的查询。
  2. 深入官方文档MyBatis 官方文档 是最权威的资料,了解更多高级特性,如缓存、插件、类型处理器等。
  3. 学习进阶技术:研究 MyBatis 的二级缓存如何配置和使用,学习如何使用 PageHelper 等流行分页插件。
  4. 结合 Spring Boot 实践:搭建一个 Spring Boot + MyBatis 项目,体验更便捷的开发模式。
  5. 探索 MyBatis Generator:了解代码生成器,提高开发效率。

掌握 MyBatis 这样的持久层框架是 Java 后端开发者的必备技能。不断学习和实践,你一定能在数据操作领域游刃有余!


如果你觉得这篇指南对你有帮助,请毫不犹豫地:

  • 点赞👍:让更多的人看到这篇指南!
  • 收藏⭐:方便以后随时查阅!
  • 分享↗️:帮助更多正在学习 MyBatis 的朋友!

也欢迎在评论区留下你的问题或学习心得,我们一起交流进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值