[特殊字符]【MyBatis 从入门到进阶】保姆级教程 | CRUD、动态SQL、原理剖析与避坑指南,看完这篇全搞定!

文章标题:🚀【MyBatis 从入门到进阶】保姆级教程 | CRUD、动态SQL、原理剖析与避坑指南,看完这篇全搞定!

标签: #Java #MyBatis #ORM框架 #数据库 #持久层 #动态SQL #源码分析 #避坑指南 #优快云


哈喽,各位在 优快云 奋斗的小伙伴们!👋 是不是在 Java 开发中,与数据库打交道是家常便饭?还在手写繁琐的 JDBC 代码,手动管理连接、处理结果集吗?🤯 那你可就 OUT 啦!今天,我们要隆重介绍一款优秀的持久层框架——MyBatis!✨

MyBatis 是一款半自动化的 ORM (Object Relational Mapping) 框架,它能让你从繁杂的 JDBC 代码中解脱出来,更专注于 SQL 语句本身,同时又提供了强大的灵活性。无论你是刚接触 MyBatis 的萌新 🌱,还是希望深入理解其原理和实践的进阶选手,这篇保姆级教程都值得你拥有!收藏⭐+关注哦!

🤔 什么是 MyBatis?为什么选择它?

MyBatis 的前身是 iBATIS,它是一个支持定制化 SQL、存储过程以及高级映射的优秀持久层框架。与 Hibernate 这样的全自动化 ORM 框架不同,MyBatis 的特点在于:

  • SQL 与代码分离:SQL 语句写在 XML 文件或注解中,与 Java 代码解耦,方便维护和优化。
  • 灵活性高:开发者可以完全掌控 SQL 语句,对于复杂的查询和数据库特性支持良好。你可以写出最适合业务的 SQL。
  • 学习成本相对较低:相比 Hibernate,MyBatis 的概念更少,更容易上手。
  • 性能优秀:MyBatis 本身开销较小,且允许开发者直接优化 SQL。
  • 动态 SQL:强大的动态 SQL 功能,可以根据条件动态拼接 SQL 语句,非常实用。
  • 与 Spring/Spring Boot 完美集成:是 Java EE 和微服务开发中的主流选择。

一句话总结(敲黑板!):MyBatis 帮你把 Java 对象和数据库表记录“对应”起来,让你用操作对象的方式来操作数据,同时还能让你愉快地写 SQL!🎯

🛠️ MyBatis 快速入门:一个简单的 CRUD 示例

光说不练假把式!我们来看一个简单的用户表 (users) 的增删改查示例。

1. 准备工作:

  • 数据库表结构 (MySQL 为例):

    CREATE TABLE users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(50) NOT NULL,
        password VARCHAR(50) NOT NULL,
        email VARCHAR(100)
    );
    
  • Maven 依赖 (pom.xml):

    <dependencies>
        <!-- MyBatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version> <!-- 请使用最新稳定版 -->
        </dependency>
        <!-- MySQL驱动 (根据你的数据库选择) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version> <!-- 请使用与你MySQL版本匹配的驱动 -->
        </dependency>
        <!-- (可选) 日志实现,如SLF4J + Logback -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.32</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.10</version>
        </dependency>
        <!-- (可选) 测试框架,如 JUnit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

2. 创建实体类 User.java:

package com.example.mybatis.entity;

/**
 * 用户实体类,对应数据库中的 users 表
 */
public class User {
    private Integer id;       // 用户ID,对应表中的 id 字段
    private String username;  // 用户名,对应表中的 username 字段
    private String password;  // 密码,对应表中的 password 字段
    private String email;     // 邮箱,对应表中的 email 字段

    // 构造函数 (可以有多个,按需创建)
    public User() {
    }

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    // Getter 和 Setter 方法 (必须提供,MyBatis需要通过它们来设置和获取属性值)
    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;
    }

    // (可选) toString 方法,方便调试时打印对象信息
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

3. 创建 Mapper 接口 UserMapper.java:

package com.example.mybatis.mapper;

import com.example.mybatis.entity.User;
import org.apache.ibatis.annotations.Param; // 引入 Param 注解,用于多参数传递

import java.util.List;

/**
 * 用户 Mapper 接口
 * 定义了对 users 表的数据库操作方法
 * MyBatis 会为这个接口生成动态代理实现类
 */
public interface UserMapper {

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

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

    /**
     * 插入一个新用户
     * @param user 包含新用户信息的 User 对象 (id 通常由数据库自增生成)
     * @return 影响的行数 (通常是 1 表示成功)
     */
    int insertUser(User user);

    /**
     * 更新用户信息
     * @param user 包含要更新的用户信息的 User 对象 (通常需要 id 来定位用户)
     * @return 影响的行数
     */
    int updateUser(User user);

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

    /**
     * (示例) 使用 @Param 注解传递多个参数
     * 根据用户名和邮箱查询用户
     * @param username 用户名
     * @param email 邮箱
     * @return 匹配的用户对象列表
     */
    List<User> getUsersByUsernameAndEmail(@Param("username") String username, @Param("email") String email);
}

4. 创建 Mapper XML 配置文件 UserMapper.xml (通常放在 resources/com/example/mybatis/mapper/ 目录下):

<?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 接口的完整类名 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">

    <!--
        id: 对应 Mapper 接口中的方法名
        parameterType: 输入参数的类型 (可选,MyBatis 通常能自动推断)
        resultType: 返回结果的类型 (对于单条记录查询) 或集合中元素的类型 (对于多条记录查询)
                    这里是实体类的完整类名
    -->

    <!-- 根据 ID 查询用户 -->
    <select id="getUserById" parameterType="int" resultType="com.example.mybatis.entity.User">
        SELECT id, username, password, email FROM users WHERE id = #{id}
        <!-- #{id} 是占位符,MyBatis 会用 PreparedStatement 来防止 SQL 注入 -->
    </select>

    <!-- 查询所有用户 -->
    <select id="getAllUsers" resultType="com.example.mybatis.entity.User">
        SELECT id, username, password, email FROM users
    </select>

    <!--
        插入新用户
        useGeneratedKeys="true": 表示使用数据库的自增主键
        keyProperty="id": 将生成的自增主键值设置回传入的 User 对象的 id 属性
    -->
    <insert id="insertUser" parameterType="com.example.mybatis.entity.User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (username, password, email)
        VALUES (#{username}, #{password}, #{email})
    </insert>

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

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

    <!-- (示例) 根据用户名和邮箱查询用户 -->
    <select id="getUsersByUsernameAndEmail" resultType="com.example.mybatis.entity.User">
        SELECT id, username, password, email FROM users
        WHERE username = #{username} AND email = #{email}
        <!-- 注意:如果 Mapper 接口方法中使用了 @Param 注解,
             这里的占位符 #{username} 和 #{email} 会直接对应注解中的名称 -->
    </select>

</mapper>

5. 创建 MyBatis 全局配置文件 mybatis-config.xml (通常放在 resources 目录下):

<?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">
<configuration>

    <!-- (可选) 引入外部属性文件,如数据库连接信息 -->
    <!-- <properties resource="db.properties"/> -->

    <!-- (可选) 设置 -->
    <settings>
        <!-- 开启驼峰命名转换:table_column -> javaProperty (例如 user_name -> userName) -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- (可选) 配置日志实现,如果项目中没有特定日志框架,MyBatis会尝试使用自带的日志 -->
        <!-- <setting name="logImpl" value="STDOUT_LOGGING" /> --> <!-- 输出到控制台 -->
    </settings>

    <!-- (可选) 类型别名,可以为 Java 类型设置一个简短的名字,在 XML 中使用别名 -->
    <typeAliases>
        <typeAlias type="com.example.mybatis.entity.User" alias="User"/>
        <!-- 也可以扫描包下的所有类作为别名,别名默认为类名(首字母小写) -->
        <!-- <package name="com.example.mybatis.entity"/> -->
    </typeAliases>

    <!-- 环境配置 (可以配置多个环境,如开发、测试、生产) -->
    <environments default="development"> <!-- default 指定默认使用的环境 ID -->
        <environment id="development">
            <!-- 事务管理器类型:JDBC 表示使用 JDBC 的事务管理方式 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源类型:POOLED 表示使用 MyBatis 自带的连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <!-- 数据库驱动 -->
                <property name="url" value="jdbc:mysql://localhost:3306/your_database_name?useSSL=false&amp;serverTimezone=UTC"/> <!-- 数据库连接URL -->
                <property name="username" value="your_username"/> <!-- 数据库用户名 -->
                <property name="password" value="your_password"/> <!-- 数据库密码 -->
            </dataSource>
        </environment>
    </environments>

    <!-- Mapper 注册 -->
    <mappers>
        <!-- 方式一:注册单个 Mapper XML 文件 -->
        <mapper resource="com/example/mybatis/mapper/UserMapper.xml"/>
        <!-- 方式二:注册指定包下的所有 Mapper 接口 (推荐,前提是 Mapper 接口和 XML 文件在同一目录下且同名) -->
        <!-- <package name="com.example.mybatis.mapper"/> -->
    </mappers>

</configuration>

请务必将 your_database_name, your_username, your_password 替换成你自己的数据库信息!

6. 编写测试代码 MyBatisTest.java:

package com.example.mybatis.test;

import com.example.mybatis.entity.User;
import com.example.mybatis.mapper.UserMapper;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    private UserMapper userMapper;

    /**
     * JUnit 的 @Before 注解,在每个测试方法执行前运行
     * 用于初始化 SqlSessionFactory 和 SqlSession
     */
    @Before
    public void setUp() throws IOException {
        // 1. 读取 MyBatis 全局配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 2. 创建 SqlSessionFactory 对象
        // SqlSessionFactoryBuilder 用于构建 SqlSessionFactory
        // 一旦创建,SqlSessionFactoryBuilder 实例就不再需要了
        this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close(); // 关闭输入流

        // 3. 从 SqlSessionFactory 获取 SqlSession 对象
        // SqlSession 包含了执行 SQL 命令所需的所有方法,是 MyBatis工作的核心接口
        // 默认情况下,SqlSession 不是线程安全的,每个线程都应该有它自己的 SqlSession 实例
        // 参数 true 表示开启自动提交事务,如果为 false 或不传,则需要手动 session.commit()
        this.sqlSession = sqlSessionFactory.openSession(true); // 开启自动提交

        // 4. 获取 Mapper 接口的代理对象
        // MyBatis 会为 UserMapper 接口创建一个动态代理对象
        this.userMapper = sqlSession.getMapper(UserMapper.class);
    }

    /**
     * JUnit 的 @After 注解,在每个测试方法执行后运行
     * 用于关闭 SqlSession
     */
    @After
    public void tearDown() {
        if (sqlSession != null) {
            sqlSession.close(); // 关闭 SqlSession,释放资源
        }
    }

    @Test
    public void testInsertUser() {
        User newUser = new User("Alice", "alice123", "alice@example.com");
        int result = userMapper.insertUser(newUser);
        System.out.println("插入结果 (影响行数): " + result);
        System.out.println("插入后 User 的 ID: " + newUser.getId()); // 因为配置了 useGeneratedKeys 和 keyProperty

        // 断言 (根据实际情况添加更严格的断言)
        // Assert.assertEquals(1, result);
        // Assert.assertNotNull(newUser.getId());
    }

    @Test
    public void testGetUserById() {
        // 假设 ID 为 1 的用户存在
        User user = userMapper.getUserById(1);
        if (user != null) {
            System.out.println("查询到的用户: " + user);
        } else {
            System.out.println("未找到 ID 为 1 的用户。");
        }
    }

    @Test
    public void testGetAllUsers() {
        List<User> users = userMapper.getAllUsers();
        System.out.println("所有用户列表:");
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testUpdateUser() {
        // 假设要更新 ID 为 1 的用户
        User userToUpdate = userMapper.getUserById(1);
        if (userToUpdate != null) {
            userToUpdate.setEmail("updated_alice@example.com");
            userToUpdate.setPassword("new_password_for_alice");
            int result = userMapper.updateUser(userToUpdate);
            System.out.println("更新结果 (影响行数): " + result);

            User updatedUser = userMapper.getUserById(1);
            System.out.println("更新后的用户: " + updatedUser);
        } else {
            System.out.println("未找到要更新的用户。");
        }
    }

    @Test
    public void testDeleteUserById() {
        // 假设要删除 ID 为 2 的用户 (请确保这个 ID 存在或用于测试)
        int userIdToDelete = 2;
        User userBeforeDelete = userMapper.getUserById(userIdToDelete);
        if (userBeforeDelete == null) {
            System.out.println("ID 为 " + userIdToDelete + " 的用户不存在,无法执行删除测试。请先插入该用户。");
            return;
        }

        int result = userMapper.deleteUserById(userIdToDelete);
        System.out.println("删除结果 (影响行数): " + result);

        User userAfterDelete = userMapper.getUserById(userIdToDelete);
        if (userAfterDelete == null) {
            System.out.println("ID 为 " + userIdToDelete + " 的用户已成功删除。");
        } else {
            System.out.println("删除失败,用户依然存在: " + userAfterDelete);
        }
    }

    @Test
    public void testGetUsersByUsernameAndEmail() {
        // 假设存在用户名为 "Alice",邮箱为 "updated_alice@example.com" 的用户
        List<User> users = userMapper.getUsersByUsernameAndEmail("Alice", "updated_alice@example.com");
        System.out.println("根据用户名和邮箱查询到的用户:");
        for (User user : users) {
            System.out.println(user);
        }
    }
}

运行测试代码,你就能看到 MyBatis 如何帮你轻松完成数据库操作啦! 🎉

💡 MyBatis 深度与广度:不止于 CRUD

MyBatis 的强大远不止于此,我们来探索一下它的更多特性和原理:

1. 动态 SQL:让 SQL “活” 起来!

动态 SQL 是 MyBatis 的一大亮点,它允许你根据不同的条件动态地构建 SQL 语句。常用的动态 SQL 标签有:

  • <if>:条件判断
  • <choose>, <when>, <otherwise>:类似 Java 的 switch 语句
  • <trim>, <where>, <set>:用于处理 SQL 前缀、后缀或去除多余的 AND, OR, ,
  • <foreach>:用于遍历集合,常用于 IN 子句或批量插入/更新

示例:使用 <if><where> 进行动态条件查询

修改 UserMapper.xml,增加一个动态查询方法:

<!-- 动态条件查询用户 -->
<select id="findUsersByCriteria" parameterType="com.example.mybatis.entity.User" resultType="com.example.mybatis.entity.User">
    SELECT id, username, password, email FROM users
    <where>
        <!-- 如果 username 不为空且不为空字符串,则添加 username 条件 -->
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <!-- 如果 email 不为空且不为空字符串,则添加 email 条件 -->
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <!-- 注意:<where> 标签会自动处理第一个 AND 或 OR,
             如果内部条件都不满足,则不生成 WHERE 子句。 -->
    </where>
</select>

UserMapper.java 中添加对应方法:

List<User> findUsersByCriteria(User criteria);

测试时,你可以传入一个 User 对象,只设置你想要查询的条件字段,其他字段为 null 或空字符串。

2. 结果映射 (<resultMap>): 复杂关系的桥梁

当数据库表字段名与 Java 实体类属性名不一致(即使开启了驼峰转换也无法满足),或者需要处理一对一、一对多、多对多等复杂关联关系时,<resultMap> 就派上用场了。

示例:字段名不一致的映射

假设 users 表有一个字段 user_nickname,而 User 实体类属性是 nickname

<resultMap id="userResultMap" type="com.example.mybatis.entity.User">
    <id property="id" column="id"/> <!-- 主键映射 -->
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="nickname" column="user_nickname"/> <!-- 将 user_nickname 映射到 nickname -->
</resultMap>

<select id="getUserWithNickname" parameterType="int" resultMap="userResultMap">
    SELECT id, username, password, email, user_nickname FROM users WHERE id = #{id}
</select>

关联查询 (<association><collection>) 更是处理复杂对象图的利器,这里不展开细讲,但它是 MyBatis 强大映射能力的体现。

3. 缓存机制:提升查询效率

MyBatis 内置了两级缓存:

  • 一级缓存 (Local Cache):默认开启,作用域是 SqlSession 级别。在同一个 SqlSession 内,对同一个 Mapper 的同一个方法的相同参数的查询,第一次会从数据库查,然后将结果放入缓存;后续相同的查询会直接从缓存中获取,不再访问数据库。当执行 CUD 操作(增删改)或者手动调用 sqlSession.clearCache() 时,一级缓存会清空。
  • 二级缓存 (Global Cache):作用域是 Mapper Namespace 级别(即同一个 XxxMapper.xml)。需要手动在 mybatis-config.xml 中开启 <setting name="cacheEnabled" value="true"/>,并在 XxxMapper.xml 中使用 <cache/> 标签来配置。多个 SqlSession 可以共享同一个 Mapper 的二级缓存。当一个 SqlSession 执行了 CUD 操作并提交后,会清空该 Namespace 下的二级缓存。

注意: 缓存虽好,但也可能带来数据不一致的问题(脏读),特别是在分布式环境或涉及多个应用操作同一数据库时,需要谨慎使用和配置二级缓存。通常建议优先考虑数据库层面的缓存或专门的缓存中间件(如 Redis)。

4. MyBatis 实现原理浅析 (面试常问!)

理解 MyBatis 的工作流程和核心组件,有助于我们更好地使用它并排查问题。

核心组件:

  • SqlSessionFactoryBuilder: 根据 MyBatis 配置文件 (XML 或 Java 代码) 构建 SqlSessionFactory。它的生命周期很短。
  • SqlSessionFactory: 工厂模式的体现,用于创建 SqlSession。一旦创建,应在应用运行期间一直存在,是线程安全的。
  • SqlSession: MyBatis 工作的核心接口,包含了执行 SQL 命令、获取 Mapper 代理、管理事务等方法。每个线程都应该有它自己的 SqlSession 实例,非线程安全。
  • Executor: SqlSession 底层依赖 Executor 来执行 SQL。Executor 负责 SQL 的动态拼装、参数处理、结果集映射、缓存处理等。有多种实现,如 SimpleExecutor (默认)、ReuseExecutor (重用 PreparedStatement)、BatchExecutor (批量执行)。
  • MappedStatement: MyBatis 将 Mapper XML 文件中的每一个 <select>, <insert>, <update>, <delete> 标签解析为一个 MappedStatement 对象,包含了 SQL 语句、输入参数类型、输出结果类型、缓存配置等所有信息。
  • Configuration: MyBatis 的所有配置信息都保存在 Configuration 对象中,包括环境配置、类型别名、Mapper 注册、MappedStatement 等。

工作流程(简化版):

  1. 加载配置:读取 mybatis-config.xml 和 Mapper XML 文件,解析配置信息,创建 Configuration 对象。
  2. 创建 SqlSessionFactorySqlSessionFactoryBuilder 根据 Configuration 创建 SqlSessionFactory
  3. 创建 SqlSession:从 SqlSessionFactory 获取 SqlSession
  4. 获取 Mapper 代理:调用 sqlSession.getMapper(XxxMapper.class) 时,MyBatis 使用 JDK 动态代理或 CGLIB 为 XxxMapper 接口生成一个代理对象。
  5. 执行 Mapper 方法:当调用 Mapper 代理对象的方法时,实际上会执行代理逻辑(如 MapperProxyinvoke 方法)。
  6. 定位 MappedStatement:代理逻辑会根据调用的方法名(接口全限定名 + 方法名)从 Configuration 中找到对应的 MappedStatement
  7. 执行 SQLSqlSession 将请求传递给 ExecutorExecutor 根据 MappedStatement 中的信息,进行参数处理、动态 SQL 解析、执行 SQL 语句(通过 JDBC),并将结果集进行映射。
  8. 结果映射与返回Executor 将数据库返回的结果集,根据 <resultMap>resultType 的配置,映射成 Java 对象或集合,并返回给调用者。
  9. 关闭 SqlSession:释放资源。

MyBatis 广度应用:

  • 与 Spring/Spring Boot 集成:通过 mybatis-springmybatis-spring-boot-starter 依赖,可以非常方便地将 MyBatis 集成到 Spring 生态中,由 Spring 管理 SqlSessionFactorySqlSession(通过 SqlSessionTemplate)和事务。
  • 插件 (Interceptor):MyBatis 提供了强大的插件机制,允许你在 ExecutorParameterHandlerResultSetHandlerStatementHandler 这四个核心组件的方法执行前后进行拦截和扩展,常用于实现分页、数据权限、日志记录等功能。
  • 代码生成器 (MyBatis Generator):可以根据数据库表结构自动生成实体类、Mapper 接口和 Mapper XML 文件,提高开发效率。
  • 注解方式:除了 XML,MyBatis 也支持使用注解(如 @Select, @Insert 等)来编写 SQL,对于简单的 SQL 操作比较方便,但复杂 SQL 还是推荐 XML。

⚠️ MyBatis 避坑指南:这些点要注意!

  1. #{} ${} 的区别 (SQL 注入风险!)

    • #{}:预编译占位符,MyBatis 会使用 PreparedStatement,将参数安全地设置进去,能有效防止 SQL 注入。强烈推荐使用!
    • ${}:字符串替换,直接将参数拼接到 SQL 语句中,有 SQL 注入的风险。仅在特殊场景下使用,如动态指定表名、列名或 ORDER BY 子句(且必须确保参数来源可靠或进行严格校验)。
  2. parameterType 通常可以省略,但有时会出问题

    • MyBatis 大部分情况下能推断出参数类型。但如果传递的是 MapMap 中有多个参数,或者有复杂嵌套对象时,显式指定 parameterType 有时能避免一些奇怪的问题。
  3. resultType vs <resultMap>

    • 简单的结果映射(字段名与属性名一致或符合驼峰规则)用 resultType 即可。
    • 复杂映射(字段名不一致、关联查询、集合嵌套等)必须使用 <resultMap>
  4. N+1 查询问题

    • 在进行一对多或多对多关联查询时,如果配置不当(比如使用嵌套查询 select 属性且没有合理使用延迟加载或一次性加载),很容易产生 N+1 查询问题,即先查询主对象 (1次),然后根据每个主对象再去查询关联的子对象 (N次),导致大量低效的 SQL 执行。
    • 解决方法
      • 使用 JOIN 查询,在 <resultMap> 中通过 <association><collection>javaTypeofType 配合 columnPrefix 等属性进行映射。
      • 合理使用延迟加载 (lazyLoadingEnabled<association/collection>fetchType="lazy"),按需加载关联数据。
  5. Mapper 接口方法重载的限制

    • 由于 Mapper XML 中的 id 是与 Mapper 接口方法名对应的,MyBatis 不支持 Mapper 接口中方法名相同但参数不同的重载(除非使用不同的 @Param 注解来区分,但 XML 中的 id 仍然需要唯一)。通常建议为每个不同的操作定义一个唯一的方法名。
  6. XML 文件路径或 namespace 写错

    • 这是新手常犯的错误,会导致找不到 Mapper 或方法。请仔细检查 mybatis-config.xml 中 Mapper 的注册路径以及 UserMapper.xml 中的 namespace 是否与接口全限定名完全一致。
  7. 事务管理

    • 单独使用 MyBatis 时,需要手动控制事务(sqlSession.commit()sqlSession.rollback()),或者在 openSession() 时传入 true 开启自动提交。
    • 与 Spring 集成后,通常由 Spring 的声明式事务管理。
  8. 动态 SQL 拼接时空格和逗号问题

    • 使用 <if> 等标签时,要注意 SQL 语句中可能多余的空格或逗号。<trim>, <where>, <set> 标签能很好地解决这些问题。
  9. 缓存导致的数据不一致

    • 如前所述,二级缓存需要谨慎使用,确保了解其作用域和刷新机制,避免脏读。

总结一下:

MyBatis 是一款功能强大且灵活的持久层框架,它通过将 SQL 与代码分离,提供了高度的 SQL 控制权,同时通过动态 SQL、结果映射、缓存等机制,极大地简化了数据库操作。

  • 入门:掌握基本的 CRUD、配置文件和 Mapper 编写。
  • 进阶:熟练运用动态 SQL、<resultMap> 进行复杂映射、理解缓存机制、了解其核心原理。
  • 实践:注意避免常见的坑点,结合 Spring/Spring Boot 使用,发挥其最大效能。

学习 MyBatis 不仅仅是学会用一个工具,更是理解持久化思想、SQL 优化和框架设计的一个好机会!💪

好啦,关于 MyBatis 的超详细讲解就到这里! 希望这篇从入门到进阶,覆盖原理与避坑的教程,能让你对 MyBatis 有一个全面而深入的理解!

觉得这篇教程对你有巨大帮助?请务必 点赞👍 + 收藏⭐ + 关注我👀 ! 你的支持是我持续输出高质量技术博文的源泉!💖

在使用 MyBatis 过程中,你遇到过哪些印象深刻的问题?或者有什么独到的使用技巧?欢迎在评论区分享你的经验,我们一起交流,共同进步!💬

Happy Coding!🚀


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PGFA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值