MyBatis 底层架构概览

一、MyBatis 底层架构概览

MyBatis 的底层架构基于 JDBC 封装,通过配置驱动反射机制实现灵活的 SQL 执行与对象映射。其核心组件可分为以下几类:

组件作用
SqlSessionFactory工厂类,负责创建 SqlSession(数据库会话),是 MyBatis 的核心入口。
SqlSession数据库会话,提供 selectOne()insert() 等方法,直接操作数据库。
Executor执行器,负责管理 Statement 的创建、重用和执行(如 SimpleExecutorBatchExecutor)。
StatementHandler语句处理器,负责将 MyBatis 的 MappedStatement 转换为 JDBC 的 PreparedStatementStatement
ParameterHandler参数处理器,负责将 Java 对象参数转换为 JDBC 语句的参数(处理 #{} 占位符)。
ResultSetHandler结果集处理器,负责将 JDBC 结果集转换为 Java 对象(处理 resultMap 映射)。
TypeHandler类型处理器,负责 Java 类型与 JDBC 类型的相互转换(如 StringVARCHAR)。
Configuration配置类,存储 MyBatis 的全局配置(如数据源、映射文件、类型处理器等)。

二、核心组件详解

1. SqlSessionFactory:工厂与配置中心

SqlSessionFactory 是 MyBatis 的核心入口,通过 SqlSessionFactoryBuilder 读取配置文件(mybatis-config.xml)或 Java 配置类创建。其核心职责是:

  • 加载全局配置(数据源、事务管理器、插件等)。
  • 注册映射文件(Mapper.xml)或注解定义的 SQL 语句。
  • 创建 SqlSession 实例(每次数据库操作需通过 SqlSession 执行)。

示例:创建 SqlSessionFactory

// 读取配置文件并构建 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2. SqlSession:数据库会话

SqlSession 是 MyBatis 的“数据库连接代理”,封装了 JDBC 的 Connection,提供以下功能:

  • 执行 SQL:通过 selectOne()update() 等方法执行查询或更新。
  • 管理事务:通过 commit()rollback() 提交或回滚事务。
  • 获取 Mapper 接口代理:通过 getMapper(Class<T> type) 获取 Mapper 接口的动态代理对象(如 UserMapper)。

关键机制
SqlSession 内部通过 Executor 管理 SQL 执行,每次调用 select()update() 时,会从 Executor 获取一个 Statement 并执行。


3. Executor:SQL 执行器

Executor 是 MyBatis 的“执行引擎”,负责管理 Statement 的生命周期(创建、重用、关闭),并根据配置选择不同的执行策略。MyBatis 提供了三种默认实现:

执行器类型特点适用场景
SimpleExecutor每次执行 SQL 时创建新的 Statement,执行后关闭。默认执行器,适合大多数场景。
ReuseExecutor缓存 Statement(按 SQL 和参数缓存),重复使用以减少 JDBC 开销。高频执行相同 SQL 的场景。
BatchExecutor批量执行 SQL(通过 addBatch()executeBatch()),减少网络 IO。批量插入/更新场景(如百万级数据)。

执行流程

  1. SqlSession 调用 Executorupdate()query() 方法。
  2. Executor 根据 MappedStatement(SQL 配置)创建或获取 Statement
  3. StatementHandler 处理 Statement 的参数设置和执行。

4. StatementHandler:语句处理器

StatementHandler 是 SQL 执行的核心处理器,负责将 MyBatis 的 MappedStatement 转换为 JDBC 可执行的 Statement,并处理以下关键逻辑:

  • 参数替换:将 #{} 占位符替换为实际参数值(通过 ParameterHandler)。
  • SQL 预编译:调用 Connection.prepareStatement() 生成预编译语句(防止 SQL 注入)。
  • 结果集处理:调用 ResultSetHandler 处理查询结果。

核心方法

  • prepare():创建 PreparedStatementStatement
  • parameterize():设置 SQL 参数(调用 ParameterHandler)。
  • batch():批量执行 SQL(仅 BatchExecutor 使用)。

5. ParameterHandler:参数处理器

ParameterHandler 负责将 Java 对象参数转换为 JDBC 语句的参数,核心逻辑是解析 #{} 占位符并设置参数值。例如:

  • 若参数是 User 对象,且 #{name} 对应 name 字段,则通过反射获取 user.getName() 并设置到 PreparedStatement
  • 支持 #{} 的高级特性(如 #{id, jdbcType=INT} 指定 JDBC 类型)。

示例
假设 SQL 为 INSERT INTO user (name, age) VALUES (#{name}, #{age})ParameterHandler 会将 nameage 参数值设置到 PreparedStatement 的第 1、2 个位置。


6. ResultSetHandler:结果集处理器

ResultSetHandler 负责将 JDBC 结果集(ResultSet)转换为 Java 对象,核心依赖 ResultMap(映射配置)。其处理逻辑包括:

  • 简单映射:单表查询时,直接将 ResultSet 的列值映射到 Java 对象的字段(通过 TypeHandler 转换类型)。
  • 嵌套映射:多表关联查询时,通过 association(一对一)、collection(一对多)等标签递归映射子对象。

示例
若查询结果包含 user_iduser_nameorder_idorder_amountResultMap 可配置将 user_iduser_name 映射到 User 对象,order_idorder_amount 映射到 Order 对象,并通过 collection 关联到 Userorders 属性。


7. TypeHandler:类型处理器

TypeHandler 是 MyBatis 的“类型桥梁”,负责 Java 类型与 JDBC 类型的相互转换。MyBatis 内置了多种 TypeHandler(如 StringTypeHandlerIntegerTypeHandler),也支持自定义。

核心方法

  • setParameter():将 Java 类型转换为 JDBC 类型(设置到 PreparedStatement)。
  • getResult():将 JDBC 类型转换为 Java 类型(从 ResultSetCallableStatement 获取)。

自定义 TypeHandler 示例

// 自定义类型处理器(将 Java 的 LocalDate 转换为 JDBC 的 DATE)
@MappedTypes(LocalDate.class)
@MappedJdbcTypes(JdbcType.DATE)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType) throws SQLException {
        ps.setDate(i, Date.valueOf(parameter)); // LocalDate → java.sql.Date
    }

    @Override
    public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Date date = rs.getDate(columnName);
        return date != null ? date.toLocalDate() : null; // java.sql.Date → LocalDate
    }
}

三、SQL 执行全流程

通过以上组件的协作,MyBatis 的 SQL 执行流程可分为以下步骤:

1. 读取配置与初始化

SqlSessionFactory 加载 mybatis-config.xml 和映射文件(Mapper.xml),解析 MappedStatement(存储 SQL 语句、参数类型、结果类型等信息)并注册到 Configuration 中。

2. 获取 SqlSession

通过 SqlSessionFactory.openSession() 获取 SqlSession,内部创建 Executor(默认 SimpleExecutor)。

3. 获取 Mapper 代理

通过 sqlSession.getMapper(UserMapper.class) 获取 UserMapper 接口的动态代理对象。代理对象的 invoke() 方法会拦截所有方法调用,转发到 SqlSession 执行。

4. 执行 SQL

  • 参数处理Executor 调用 StatementHandler.prepare() 创建 PreparedStatement,并通过 ParameterHandler.setParameter() 设置参数值(解析 #{} 占位符)。
  • SQL 执行StatementHandler.parameterize() 执行 PreparedStatement.execute()(查询返回 ResultSet,更新返回影响行数)。
  • 结果映射:若为查询,ResultSetHandler.handleResultSets() 根据 ResultMapResultSet 转换为 Java 对象(递归处理嵌套映射)。

5. 事务管理

  • 本地事务:通过 SqlSession.commit()rollback() 控制 JDBC 事务(基于 Connection.setAutoCommit(false))。
  • 分布式事务:需结合外部框架(如 Seata),MyBatis 本身不直接支持。

四、ORM 映射的核心机制

MyBatis 的 ORM 映射通过 MappedStatementResultMap 实现,核心是将 SQL 语句与 Java 对象绑定。

1. MappedStatement:SQL 配置的元数据

MappedStatement 存储了 SQL 语句的所有元数据,包括:

  • SQL 语句内容(statementTypeSTATEMENT/PREPARED/CALLABLE)。
  • 参数类型(parameterType:Java 类或别名)。
  • 结果类型(resultType:Java 类或别名)。
  • 结果映射(resultMap:复杂映射的 ID)。
  • 缓存配置(是否启用一级/二级缓存)。

示例(XML 映射文件)

<mapper namespace="com.example.UserMapper">
    <select id="getUserById" parameterType="Long" resultType="User">
        SELECT id, name, age FROM user WHERE id = #{id}
    </select>
</mapper>

此配置会生成一个 MappedStatement,关联 getUserById 方法、User 结果类型和 #{id} 参数。


2. ResultMap:复杂映射的解决方案

当 SQL 查询涉及多表关联或字段名与对象属性名不一致时,需通过 ResultMap 显式定义映射规则。例如:

场景:数据库表 user 有字段 user_name,而 Java 对象 User 的属性是 userName(驼峰命名)。

ResultMap 配置

<resultMap id="userMap" type="User">
    <result column="user_name" property="userName"/> <!-- 字段名与属性名不一致时映射 -->
</resultMap>

使用 ResultMap

<select id="getUserById" parameterType="Long" resultMap="userMap">
    SELECT user_name FROM user WHERE id = #{id}
</select>

五、缓存机制:提升性能的关键

MyBatis 提供了一级缓存二级缓存,用于减少数据库查询次数,提升性能。

1. 一级缓存(本地缓存)

  • 作用域SqlSession 级别(同一个 SqlSession 中多次查询同一数据,仅执行一次 SQL)。
  • 触发条件
    • 相同 SqlSession
    • 相同 statementId(即相同的 Mapper 方法)。
    • 相同参数。
  • 失效场景
    • SqlSession 关闭或提交/回滚事务。
    • 执行了 updatedeleteinsert 等修改操作(缓存会被清空)。

示例

SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查询(执行 SQL)
User user1 = mapper.getUserById(1L); 

// 第二次查询(直接从一级缓存获取)
User user2 = mapper.getUserById(1L); 

session.close(); // 缓存失效

2. 二级缓存(全局缓存)

  • 作用域Mapper 级别(所有 SqlSession 共享同一缓存)。
  • 启用条件
    • mybatis-config.xml 中开启全局二级缓存:<setting name="cacheEnabled" value="true"/>
    • Mapper.xml 中声明 <cache/>(或通过注解 @CacheNamespace)。
  • 失效场景
    • 缓存时间过期(通过 evictionflushInterval 配置)。
    • 执行了修改操作(默认清空缓存,可通过 flushCache="false" 配置不自动清空)。

示例

<!-- mybatis-config.xml -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

<!-- UserMapper.xml -->
<mapper namespace="com.example.UserMapper">
    <cache eviction="LRU" flushInterval="60000" size="1024"/> <!-- LRU 策略,60秒刷新,最大1024条 -->
    
    <select id="getUserById" parameterType="Long" resultType="User" useCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

六、动态 SQL:灵活处理复杂查询

MyBatis 支持通过 XML 标签或注解编写动态 SQL,根据不同条件拼接 SQL 语句(如 ifforeachchoose 等)。其底层通过 DynamicSqlSource 解析动态标签,生成最终的 SQL 语句。

示例(XML 动态 SQL)

<select id="getUserList" parameterType="UserQuery" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
    <if test="orderBy != null">
        ORDER BY ${orderBy}
    </if>
</select>
  • <where> 标签自动处理 WHERE 关键字和多余的 AND
  • <if> 标签根据参数是否存在动态拼接条件。
  • ${orderBy} 直接拼接字符串(需注意 SQL 注入风险,建议配合白名单校验)。

七、插件机制:扩展 MyBatis 功能

MyBatis 支持通过**插件(Interceptor)**拦截核心组件的方法(如 Executor.update()StatementHandler.parameterize()),实现自定义逻辑(如分页、日志、性能监控)。

插件实现步骤

  1. 定义拦截器类,实现 Interceptor 接口,重写 intercept(Invocation invocation) 方法。
  2. 通过 @Intercepts@Signature 注解指定拦截的目标类和方法。
  3. 将拦截器注册到 SqlSessionFactoryConfiguration 中。

示例:分页插件(简化版)

@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取分页参数(如 PageHelper 设置的 offset/limit)
        Object parameter = invocation.getArgs()[1];
        if (parameter instanceof Page) {
            Page page = (Page) parameter;
            // 修改 SQL 为带 LIMIT/OFFSET 的分页语句
            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            BoundSql boundSql = ms.getBoundSql(parameter);
            String originalSql = boundSql.getSql();
            String pageSql = originalSql + " LIMIT " + page.getOffset() + ", " + page.getLimit();
            // 替换 BoundSql 的 SQL
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            // 通过反射替换 Executor 的参数
            MappedStatement newMs = copyFromNewBoundSql(ms, newBoundSql);
            return invocation.proceed(); // 执行修改后的 SQL
        }
        return invocation.proceed();
    }
}

八、总结

MyBatis 的底层原理围绕JDBC 封装ORM 映射展开,通过核心组件(SqlSessionFactorySqlSessionExecutor 等)的协作,实现了 SQL 执行、参数处理、结果映射的高效流程。其优势在于:

  • 简化 JDBC:通过配置和映射减少重复代码。
  • 灵活扩展:支持动态 SQL、插件机制、自定义类型处理器。
  • 性能优化:通过一级/二级缓存、批量执行器提升效率。

理解 MyBatis 的底层原理,能帮助开发者更高效地使用其特性(如优化 SQL 执行、解决缓存问题、扩展插件功能),并在遇到性能瓶颈或 bug 时快速定位问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值