深入理解 MyBatis

本文深入探讨 MyBatis 框架,从 MyBatis 的整体结构和核心组件,如 SqlSessionFactory、SqlSession、Mapper 的获取与执行,到 MyBatis 的插件机制。同时,详细分析了 MyBatis 的缓存系统,包括一级缓存和二级缓存的工作原理、失效情况和实现细节,以及在 Spring 中如何管理 MyBatis 的 Mapper。通过这些内容,帮助读者全面理解 MyBatis 的内部运作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是 MyBatis ?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

官方解释:mybatis.org/mybatis-3/z…

Mybaits 整体结构

整体上可以把 Mybatis 分为三层

API 接口层:对开发提供的业务层,使用本地 API 调用来操作业务数据库

数据处理层:负责具体的 SQL 查找,语句解析(参数映射),SQL 执行,以及结果映射,主要用来完成一次数据库请求操作。

基础支撑层:负责基础的功能组件支撑,包括有连接管理,事务管理,配置加载以及缓存处理等。将这些公用的组件提取出来,负责为上游业务提供基础支撑。

2、入门案例

也可参考官方:mybatis.org/mybatis-3/z…

代码示列:

使用上面的例子,就可以完成 Mybatis 操纵数据库得到业务数据。

总结下分为四个步骤:

  1. 获取配置文件(也可以是配置建造器),从配置中得到 SqlSessionFactory
  2. 从 SqlSessionFactory 获取 SqlSession
  3. 通过 SqlSession.getMapper 进行 CRUD 和事务操作
  4. 关闭 session

思考:Mybatis 在上面四步操作中做了什么?MyBatissql 语句的执行解析过程?

3、执行过程分析

3.1 构建 SqlSessionFactory

我们一步一步来,根据代码进行查看

String resource = "mybatis-config.xml";
//将 XML 配置文件构建为 Configuration 配置类
Reader reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个 SqlSessionFactory  DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
复制代码

首先上面两行代码是从配置文件中进行构建 SqlSessionFactory,进入到 build... 中看看是如何构建出的

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
复制代码

这里看出 SqlSessionFactoryBuilder 会加载配置并解析 xml 配置文件,先看下 Mybatis 的一个完整的配置:

<?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)-->
    <properties resource="db.properties"></properties>
    <properties resource="org/mybatis/example/config.properties">
        <property name="username" value="dev_user"/>
        <property name="password" value="******"/>
    </properties>
    <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </dataSource>

    <!--全局设置(settings)-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

    <!--类型别名(typeAliases)-->
    <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
    </typeAliases>
    <typeAliases>
        <package name="domain.blog"/>
    </typeAliases>

    <!--类型处理器(typeHandlers)-->
    <typeHandlers>
        <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>

    <!--对象工厂(objectFactory)-->
    <objectFactory type="org.mybatis.example.ExampleObjectFactory">
        <property name="someProperty" value="100"/>
    </objectFactory>

    <!--插件(plugins)-->
    <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>

    <!--环境配置(environments)-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="..." value="..."/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--数据库厂商标识(databaseIdProvider)-->
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <!--映射器(mappers)-->
    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
        <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
        <mapper url="file:///var/mappers/BlogMapper.xml"/>
        <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
        <mapper class="org.mybatis.builder.BlogMapper"/>
        <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
</configuration>
复制代码

接下来可以看解析配置的核心方法

org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

public Configuration parse() {
    /**
     * 若已经解析过了 就抛出异常
     */
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    /**
     * 设置解析标志位
     */
    parsed = true;
    /**
     * 解析 mybatis-config.xml 的<configuration>节点
     * 
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

复制代码

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

private void parseConfiguration(XNode root) {
    try {
      /**
       * 解析 properties 节点
       *     <properties resource="mybatis/db.properties" />
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 解析我们的 mybatis-config.xml 中的 settings 节点       
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 基本没有用过该属性,VFS 含义是虚拟文件系统
       */
      loadCustomVfs(settings);
      /**
       * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
       */
      loadCustomLogImpl(settings);
      /**
       * 解析我们的别名
       * <typeAliases> <typeAlias alias="Author" type="cn.zcy.pojo.Author"/>
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 解析我们的插件(比如分页插件)
       */
      pluginElement(root.evalNode("plugins"));

      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 设置 settings 和默认值
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      /**
       * 解析我们的 mybatis 环境 <environments>
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 解析数据库厂商<databaseIdProvider>
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 解析我们的类型处理器节点<typeHandlers>*/
      typeHandlerElement(root.evalNode("typeHandlers"));
      /**
       * 最重要的就是解析我们的 mapper
       * resource/url/class/package;
       */
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
复制代码

以上过程执行完成后会得到一个包含 Configuration 对象,其中 Configuration 包含所有的配置信息。

总结

获取 SqlSessionFactory 的流程:

  1. 通过 SqlSessionFactoryBuilder 解析 Mybatis 所有的属性配置,生成一个包含所有的配置信息的 Configuration 对象。
  2. 然后用 Configuration 生成一个 DefaultSqlSessionFactory,整个过程的时序图如下

3.2 获取 SqlSession

拿到 DefaultSqlSessionFactory 后就可以开启 session,获取 SqlSession 后可以执行 CURD 操作,接下来看看 SqlSession session = sqlMapper.openSession() 的过程;

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //获取环境变量
      final Environment environment = configuration.getEnvironment();
      //获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      /**
       * 创建一个 sql 执行器对象
       * 一般情况下 若我们的 mybaits 的全局配置文件的 cacheEnabled 默认为 ture 就返回
       * 一个 cacheExecutor,若关闭的话返回的就是一个 SimpleExecutor
       */
      final Executor executor = configuration.newExecutor(tx, execType);
      // 建返回一个 DeaultSqlSessoin 对象返回
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
复制代码

这里会根据 SQL 执行器类型 execType,获取一个 sql 执行器,用来进行 SQL 语句操作

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    /**
     * 判断执行器的类型
     * 批量的执行器
     */
    if (ExecutorType.BATCH == executorType) {
      e
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值