MyBatis框架讲解,工作原理、核心内容、如何实现【从浅入深】让你看完这篇文档对于MyBatis的理解更加深入

MyBatis 是一款 Java 持久层框架(ORM 框架的轻量级实现),核心目标是「简化数据库操作、实现 SQL 与 Java 代码分离」------ 它替代了 JDBC 繁琐的手动编码(加载驱动、创建连接、处理结果集等),同时保留了 SQL 的灵活性(无需像 Hibernate 那样完全屏蔽 SQL),是 Java 后端开发中最常用的持久层解决方案之一(尤其适合需要精细控制 SQL 的场景)。

下面从「核心定位、核心组件、工作原理、核心用法、高级特性、常见问题」六个维度,全面讲解 MyBatis,结合之前的搭建示例,帮你建立完整认知:

一、MyBatis 的核心定位:解决什么问题?

在 MyBatis 出现前,开发者使用 JDBC 操作数据库存在诸多痛点:

  1. 代码冗余:每次操作数据库都要重复写「加载驱动、创建 Connection、PreparedStatement、关闭资源」等代码;
  2. SQL 与代码耦合:SQL 语句硬编码在 Java 代码中,修改 SQL 需重新编译代码;
  3. 参数传递繁琐 :手动设置 SQL 参数(ps.setInt(1, id)),参数顺序易出错;
  4. 结果集映射复杂 :手动将 ResultSet 结果集封装为 Java 实体类(user.setId(rs.getInt("id"))),字段多时代码量大;
  5. 资源管理麻烦:需手动关闭 Connection、Statement 等资源,容易遗漏导致连接泄漏。

MyBatis 的核心解决方案:

  • XML / 注解 存储 SQL 语句,实现 SQL 与代码分离;
  • 自动完成「参数注入」(将 Java 方法参数映射到 SQL 参数);
  • 自动完成「结果集映射」(将数据库查询结果映射到 Java 实体类);
  • 管理数据库连接(内置连接池),自动释放资源;
  • 支持动态 SQL、缓存、分页等高级特性,满足复杂业务需求。

二、MyBatis 核心组件:各司其职的 "工具链"

MyBatis 的核心能力依赖以下组件,它们协同工作完成数据库操作,组件间的关系是理解 MyBatis 的关键:

组件 作用
mybatis-config.xml 全局配置文件:存储数据库连接信息、缓存策略、Mapper 注册等全局配置
SqlSessionFactory 会话工厂:重量级对象(全局仅需一个),负责创建 SqlSession,基于配置文件构建
SqlSession 数据库会话:类似 JDBC 的 Connection,代表一次数据库连接,线程不安全,用完即关
Mapper 接口 DAO 层接口:定义数据库操作方法(如 selectById),无实现类(MyBatis 动态代理生成)
Mapper 映射文件 与 Mapper 接口关联:存储 SQL 语句、参数映射、结果映射规则(如 UserMapper.xml
ResultMap 结果映射规则:解决「数据库字段名与实体类属性名不一致」的问题
ParameterMap 参数映射规则:定义方法参数与 SQL 参数的映射关系(较少直接使用,默认自动映射)
核心组件的关系:
  1. 加载 mybatis-config.xml → 构建 SqlSessionFactory
  2. SqlSessionFactory 创建 SqlSession(代表一次数据库会话);
  3. SqlSession 通过「动态代理」生成 Mapper 接口 的实现类;
  4. 调用 Mapper 接口方法 → MyBatis 解析对应的 Mapper 映射文件 中的 SQL;
  5. 执行 SQL 并自动完成「参数注入」和「结果集映射」→ 返回结果。

三、MyBatis 工作原理:一次 SQL 执行的完整流程

以「查询用户(selectById)」为例,拆解 MyBatis 的执行流程(结合之前的搭建示例):

java

运行

复制代码
// 1. 加载全局配置文件,创建 SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

// 2. 创建 SqlSession(手动提交事务)
SqlSession sqlSession = factory.openSession();

// 3. 获取 Mapper 接口实例(动态代理)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 4. 调用接口方法,执行 SQL
User user = userMapper.selectById(1);
底层执行流程(隐藏的关键步骤):
  1. 配置加载阶段

    • MyBatis 解析 mybatis-config.xml,读取数据库连接信息(数据源)、Mapper 注册(<mappers> 标签);
    • 解析 UserMapper.xml,将 SQL 语句、参数映射、结果映射规则封装为 MappedStatement(存储在 Configuration 中,全局唯一)。
  2. SqlSession 创建阶段

    • SqlSessionFactory 通过数据源创建 SqlSession,同时关联 Executor(执行器,MyBatis 核心执行组件)。
  3. 动态代理阶段

    • sqlSession.getMapper(UserMapper.class) 时,MyBatis 通过「JDK 动态代理」生成 UserMapper 的代理类;
    • 代理类的核心逻辑:将接口方法(如 selectById)关联到对应的 MappedStatement(即 UserMapper.xml 中的 <select id="selectById">)。
  4. SQL 执行阶段

    • 调用 userMapper.selectById(1) → 代理类触发 Executor 执行 SQL;
    • ExecutorConfiguration 中获取对应的 MappedStatement,解析 SQL 语句;
    • 通过 ParameterHandler(参数处理器)将 Java 方法参数(1)注入 SQL 的 #{id} 占位符;
    • 执行 SQL(通过 StatementHandler 操作数据库),获取 ResultSet 结果集;
    • 通过 ResultSetHandler(结果集处理器)将 ResultSet 自动映射为 User 实体类(基于 resultType 配置);
    • 返回 User 对象给调用者。
  5. 资源释放阶段

    • 调用 sqlSession.close() → 关闭 Connection,释放资源。

四、MyBatis 核心用法:从基础到实战

1. SQL 映射的两种方式(核心)

MyBatis 支持「XML 映射」和「注解映射」两种方式,按需选择:

(1)XML 映射(推荐,适合复杂 SQL)

即之前搭建中用的方式:Mapper 接口 + XML 映射文件,示例:

java

运行

复制代码
// Mapper 接口
public interface UserMapper {
    User selectById(Integer id); // 方法名 = XML 中 <select> 的 id
}

// XML 映射文件(UserMapper.xml)
<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectById" resultType="com.example.pojo.User">
        SELECT id, username, password, age FROM user WHERE id = #{id}
    </select>
</mapper>
(2)注解映射(适合简单 SQL)

无需 XML 文件,直接在 Mapper 接口方法上用注解写 SQL:

java

运行

复制代码
public interface UserMapper {
    // 注解映射:直接写 SQL,无需 XML
    @Select("SELECT id, username, password, age FROM user WHERE id = #{id}")
    User selectById(Integer id);

    @Insert("INSERT INTO user (username, password, age) VALUES (#{username}, #{password}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id") // 自增主键
    int insert(User user);
}

👉 对比:

  • XML 映射:适合复杂 SQL(如多表关联、动态 SQL),维护方便;
  • 注解映射:适合简单 SQL(单表 CRUD),代码简洁,无需额外 XML 文件。
2. 参数传递:#{} vs ${}(关键区别)

MyBatis 支持两种参数占位符,核心区别是「是否防止 SQL 注入」:

占位符 原理 优势 适用场景
#{} 预编译(PreparedStatement),参数替换为 ? 自动防止 SQL 注入 绝大多数场景(参数查询、新增)
${} 字符串拼接(Statement),直接替换参数 支持动态表名、排序字段 动态表名(如 SELECT * FROM ${tableName}

⚠️ 注意:${} 存在 SQL 注入风险,如:

java

运行

复制代码
// 危险:若 id 传入 "1 OR 1=1",会拼接为 SELECT * FROM user WHERE id = 1 OR 1=1(查询所有数据)
@Select("SELECT * FROM user WHERE id = ${id}")
User selectById(String id);

👉 优先使用 #{},仅在需要动态表名、动态排序字段时使用 ${},且需手动过滤参数。

3. 结果映射:resultType vs resultMap

当数据库字段名与实体类属性名一致 时,用 resultType 直接映射;若不一致 (如数据库字段 user_name,实体类属性 username),用 resultMap 手动配置映射关系:

示例:字段名与属性名不一致

sql

复制代码
-- 数据库表字段:user_name(而非 username)
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(50) NOT NULL, -- 字段名:user_name
  `password` varchar(50) NOT NULL,
  `age` int(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

// 实体类属性:username(与字段名不一致)
@Data
public class User {
    private Integer id;
    private String username; // 属性名:username
    private String password;
    private Integer age;
}
用 resultMap 配置映射:

xml

复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 定义 resultMap:映射规则 -->
    <resultMap id="UserResultMap" type="com.example.pojo.User">
        <id column="id" property="id"/> <!-- 主键映射 -->
        <result column="user_name" property="username"/> <!-- 字段名 → 属性名 -->
        <result column="password" property="password"/>
        <result column="age" property="age"/>
    </resultMap>

    <!-- 查询时引用 resultMap,而非 resultType -->
    <select id="selectById" resultMap="UserResultMap">
        SELECT id, user_name, password, age FROM user WHERE id = #{id}
    </select>
</mapper>

五、MyBatis 高级特性:提升开发效率

1. 动态 SQL(核心高级特性)

MyBatis 提供 <if> <where> <foreach> 等标签,支持根据参数动态拼接 SQL(无需手动拼接字符串,避免语法错误):

示例:多条件查询(动态拼接 WHERE 条件)

xml

复制代码
<select id="selectByCondition" resultType="com.example.pojo.User">
    SELECT id, username, password, age FROM user
    <where> <!-- 自动省略多余的 AND/OR,避免 WHERE AND 语法错误 -->
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>
示例:批量新增(foreach 遍历集合)

xml

复制代码
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (username, password, age) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.username}, #{user.password}, #{user.age})
    </foreach>
</insert>

// Mapper 接口方法
int batchInsert(List<User> userList); // collection="list" 对应方法参数的 List
2. 缓存机制(提升查询性能)

MyBatis 提供两级缓存,核心是「缓存查询结果,避免重复查询数据库」:

(1)一级缓存(本地缓存,默认开启)
  • 作用域:SqlSession 级别(一次数据库会话);
  • 原理:同一 SqlSession 中,多次执行相同的 SQL 查询(参数相同),第一次查询后缓存结果,后续直接从缓存获取;
  • 失效场景:执行 insert/update/delete 操作、关闭 SqlSession、手动调用 sqlSession.clearCache()
(2)二级缓存(全局缓存,需手动开启)
  • 作用域:SqlSessionFactory 级别(全局共享);

  • 开启方式:

    1. mybatis-config.xml 中开启全局缓存(默认开启,可省略):

      xml

      复制代码
      <settings>
          <setting name="cacheEnabled" value="true"/>
      </settings>
    2. 在 Mapper 映射文件中添加 <cache> 标签:

      xml

      复制代码
      <mapper namespace="com.example.mapper.UserMapper">
          <cache/> <!-- 开启该 Mapper 的二级缓存 -->
          <!-- ... SQL 语句 ... -->
      </mapper>
  • 注意:二级缓存的实体类需实现 Serializable 接口(缓存序列化存储)。

3. 分页插件(PageHelper)

MyBatis 本身不支持分页,需通过第三方插件实现(如 PageHelper),步骤:

  1. 引入依赖(pom.xml): xml

    复制代码
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.3.3</version>
    </dependency>
  2. mybatis-config.xml 中配置插件:

    xml

    复制代码
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 配置数据库方言(如 MySQL) -->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
  3. 使用分页: java

    运行

    复制代码
    // 分页查询:第 1 页,每页 10 条数据
    PageHelper.startPage(1, 10);
    List<User> userList = userMapper.selectAll(); // 执行查询,自动拼接 LIMIT 语句
    PageInfo<User> pageInfo = new PageInfo<>(userList); // 分页结果封装(总条数、总页数等)

六、常见问题与注意事项

1. 事务提交问题(新增 / 修改 / 删除无效果)

MyBatis 默认 手动提交事务sqlSession = factory.openSession()),执行 insert/update/delete 后需手动调用 sqlSession.commit(),否则数据不会写入数据库;若需自动提交,可使用 openSession(true)

2. Mapper 映射文件未注册(BindingException)

报错:Type interface com.example.mapper.UserMapper is not known to the MapperRegistry解决:在 mybatis-config.xml<mappers> 标签中注册 Mapper(XML 或包扫描):

xml

复制代码
<!-- 方式1:注册单个 XML 文件 -->
<mapper resource="mappers/UserMapper.xml"/>
<!-- 方式2:扫描包下所有 Mapper 接口(XML 需与接口同路径) -->
<package name="com.example.mapper"/>
3. 字段名与属性名不一致(查询结果为 null)

若未配置 resultMap,且数据库字段名与实体类属性名不一致(如 user_name vs username),查询结果会为 null;解决:要么用 resultMap 配置映射,要么在 SQL 中用别名(SELECT user_name AS username FROM user)。

4. SQL 注入风险(${} 滥用)

避免在查询条件中使用 ${},优先用 #{};若必须使用 ${}(如动态表名),需手动过滤参数(如限制表名只能是指定值)。

5. 整合 Spring 注意事项

实际开发中 MyBatis 常与 Spring 整合(Spring + MyBatis),核心是:

  • 由 Spring 管理 SqlSessionFactoryDataSource
  • 通过 @Mapper 注解或包扫描自动注册 Mapper 接口;
  • 事务由 Spring 管理(@Transactional 注解),无需手动调用 sqlSession.commit()

总结

MyBatis 的核心价值是「灵活与简化的平衡」:

  • 简化:自动处理参数注入、结果映射、资源管理,减少重复代码;
  • 灵活:SQL 完全由开发者控制,支持复杂查询、动态 SQL,适配各类业务场景。
相关推荐
庄宿正1 小时前
【Vue2+SpringBoot+SM2】Vue2 + Spring Boot 实现 SM2 双向非对称加密完整实战
java·spring boot·后端
Predestination王瀞潞1 小时前
Java EE开发技术(第七章:JSTL标签库)
java·java-ee
信仰_2739932431 小时前
Java面试题
java·开发语言
小满、2 小时前
MySQL :锁机制、InnoDB 架构与 MVCC 解析
数据库·mysql·innodb·mvcc·锁机制
AI2中文网2 小时前
AppInventor2 使用 SQLite(三)带条件过滤查询表数据
数据库·sql·sqlite·select·app inventor 2·appinventor·tableview
qinyia2 小时前
WisdomSSH如何高效检查服务器状态并生成运维报告
linux·运维·服务器·数据库·人工智能·后端·ssh
q***2512 小时前
Spring容器的开启与关闭
java·后端·spring
q***44812 小时前
java进阶--多线程学习
java·开发语言·学习
0***m8222 小时前
Maven Spring框架依赖包
java·spring·maven