MyBatis核心源码剖析


MyBatis是一款ORM框架,它解决的问题是针对JDBC操作数据库和封装数据集繁琐的问题。下面我们看一下传统JDBC来创建连接和获取数据集的方式。

JDBC

maven依赖

<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.22</version>
</dependency>
public void testjdbc() throws Exception {
    // 连接
    Connection connection = null;
    // 执行者
    PreparedStatement preparedStatement = null;

    try {
        // 加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 创建连接
        connection = DriverManager.getConnection("jdbc:mysql://xx.xx.xx.xx:3306/test", "root", "xxxxxx");

        String sql = "select id, name, age from user where id=?";

        // 创建执行者 -- 执行SQL,携带参数
        preparedStatement = connection.prepareStatement(sql);
        // 参数索引 表示SQL第几个?号参数,值为1
        preparedStatement.setInt(1,1);
        // 执行sql
        preparedStatement.execute();
        // 获取结果集
        ResultSet resultSet = preparedStatement.getResultSet();
        while (resultSet.next()){
            // 封装对象
            User user = User.builder()
                .id(resultSet.getInt("id"))
                .name(resultSet.getString("name"))
                .age(resultSet.getInt("age"))
                .build();
            System.out.println(user.toString());
        }
    }finally {
        // 释放资源
        if(connection!=null){
            connection.close();
        }
        if(preparedStatement!=null){
            preparedStatement.close();
        }
    }
}

从这可以看出整个JDBC连接数据库和数据集获取都是有一定的弊端,比如说有以下几点弊端

  1. 数据库连接每连接一个就要走连接-释放过程,这是非常耗性能的。这里可以利用池化思想,建立一个数据库连接池,创建的连接可以放进数据库连接池中,释放的时候放回到连接池,避免重新创建
  2. 硬编码太多,比如SQL语句,数据库驱动,数据库连接这些都是硬编码,其实这种都可以放进配置文件
  3. 创建执行者PreparedStatement时,如果我的SQL语句发生了变化,下面的参数设值也会发生变化,修改起来比较麻烦
  4. 从结果集中取数据这里如果我表字段类型变了,这里也是需要做调整,不易维护

上面的问题MyBatis都可以解决

  1. MyBatis可以配置数据连接池,比如Druid,Hikari,使用连接池管理数据库连接
  2. 将Sql语句配置在XXXXmapper.xml文件中与java代码分离
  3. MyBatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型
  4. MyBatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型

MyBatis Demo

db属性配置文件db.properties

mysql.driverClass=com.mysql.jdbc.Driver
mysql.jdbcUrl=jdbc:mysql://xx.xx.xx.xx:3306/test?characterEncoding=utf8
mysql.user= root
mysql.password= xxxxxx

MyBatis配置文件mybatis-config.xml

<?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 扫描属性文件db.properties  -->
    <properties resource="db.properties"></properties>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    <environments default="development">
        <environment id="development">
           <transactionManager type="JDBC"/>
            <!--mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,
其中POOLED对应的实现org.apache.ibatis.datasource.pooled.PooledDataSource-->
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driverClass}"/>
                <property name="url" value="${mysql.jdbcUrl}"/>
                <property name="username" value="${mysql.user}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.dm.mapper"/>
    </mappers>
</configuration>

实体类

@Data
@Builder
public class User {

    private Integer id;

    private String name;

    private Integer age;

}

Mapper接口

public interface UserMapper {

    User selectById(@Param(value = "id") Integer id);

    List<User> selectAllUser();
}

Mapper xml

<?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">
<mapper namespace="com.dm.mapper.UserMapper">
    <!-- 二级缓存作用域-->
    <cache></cache>
  
    <resultMap id="result" type="com.dm.entity.User">
        <id column="id" jdbcType="INTEGER" property="id"/>
        <result column="user_name" jdbcType="VARCHAR" property="name"/>
        <result column="age" jdbcType="INTEGER" property="age"/>
    </resultMap>


    <select id="selectById" resultMap="result">
        select * from user where id=${id}
        <where>
            <if test="id > 0">
                and id=#{id}
            </if>
        </where>
    </select>

    <select id="selectAllUser" resultType="com.dm.entity.User">
        select * from user
    </select>
</mapper>

测试代码

public static void main(String[] args) throws Exception {
    String resource = "mybatis-config.xml";
    Reader reader = null;
    SqlSession session = null;
    SqlSession session1 = null;
    try {
        //读取xml文件
        reader = Resources.getResourceAsReader(resource);
        // 通过加载配置文件流构建一个SqlSessionFactory--DefaultSqlSessionFactory
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        // DefaultSqlSession
        // 1查询
        session = sqlMapper.openSession();
        session1 = sqlMapper.openSession();

        UserMapper mapper = session.getMapper(UserMapper.class);

        // 2查询
        UserMapper mapper1 = session1.getMapper(UserMapper.class);

        // 输出Cache Hit Ratio判断是否走二级缓存,
        // session.commit();注释掉走了数据库,没注释没走数据库
        System.out.println(mapper.selectById(1));
        session.commit();
        System.out.println(mapper1.selectById(1));
    } finally {
        if (reader != null) {
            reader.close();
        }

        if (session != null) {
            session.close();
        }

        if (session1 != null) {
            session1.close();
        }

    }
}

源码分析

初始化Configuration

new SqlSessionFactoryBuilder().build(read)

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.
    }
  }
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  /**
   * 调用父类的BaseBuilder的构造方法
   * configuration赋值
   * typeAliasRegistry别名注册器赋值
   * typeAliases Map中
   */
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  /**
   * 把props绑定到configuration的props属性上
   */
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

解析mybatis-config.xml文件

parser.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;
}

parseConfiguration

解析我们的mybatis-config.xml的configuration节点

private void parseConfiguration(XNode root) {
    try {
      /**
       * 解析 properties节点
       * <properties resource="mybatis/db.properties" />
       * 解析到org.apache.ibatis.parsing.XPathParser#variables
       *      org.apache.ibatis.session.Configuration#variables
       */
      propertiesElement(root.evalNode("properties"));

      /**
       * 解析我们的mybatis-config.xml中的settings节点
       * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
       * <settings>
           <setting name="mapUnderscoreToCamelCase" value="true"/>
           <setting name="cacheEnabled" value="true"/>
            ..............
         </settings>
       *
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));

      /**
       * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
       * Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
       * 解析到:org.apache.ibatis.session.Configuration#vfsImpl
       */
      loadCustomVfs(settings);

      /**
       * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找
       * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
       * 解析到org.apache.ibatis.session.Configuration#logImpl
       */
      loadCustomLogImpl(settings);

      /**
       * 解析我们的别名
       * <typeAliases>
           <typeAlias alias="User" type="com.dm.entity.User"/>
        </typeAliases>
       <typeAliases>
          <package name="com.dm"/>
       </typeAliases>
       解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
       */
      typeAliasesElement(root.evalNode("typeAliases"));

      /**
       * 解析我们的插件(比如分页插件)
       * mybatis自带的4大插件 一般都使用Executor
       * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
         ParameterHandler (getParameterObject, setParameters)
         ResultSetHandler (handleResultSets, handleOutputParameters)
         StatementHandler (prepare, parameterize, batch, update, query)
        解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
       */
      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 default="dev">
           <environment id="dev">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
             <property name="driver" value="${jdbc.driver}"/>
             <property name="url" value="${jdbc.url}"/>
             <property name="username" value="root"/>
             <property name="password" value="Zw726515"/>
             </dataSource>
           </environment>

         <environment id="test">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
           <property name="driver" value="${jdbc.driver}"/>
           <property name="url" value="${jdbc.url}"/>
           <property name="username" value="root"/>
           <property name="password" value="123456"/>
           </dataSource>
         </environment>
       </environments>
       *  解析到:org.apache.ibatis.session.Configuration#environment
       *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
       */
      environmentsElement(root.evalNode("environments"));

      /**
       * 解析数据库厂商
       *     <databaseIdProvider type="DB_VENDOR">
                <property name="SQL Server" value="sqlserver"/>
                <property name="DB2" value="db2"/>
                <property name="Oracle" value="oracle" />
                <property name="MySql" value="mysql" />
             </databaseIdProvider>
       *  解析到:org.apache.ibatis.session.Configuration#databaseId
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));

      /**
       * 解析我们的类型处理器节点
       * <typeHandlers>
            <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
          </typeHandlers>
          解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
       */
      typeHandlerElement(root.evalNode("typeHandlers"));

      // ===========================重点===========================
      /**
       * 解析我们的mapper
       *
       resource:来注册我们的class类路径下的
       class:使用接口
       url:来指定我们磁盘下的或者网络资源的
       package:将包内的映射器接口实现全部注册为映射器
       class:
       若注册Mapper不带xml文件的,这里可以直接注册
       若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
       <mappers>
          <mapper resource="com/dm/mapper/UserMapper.xml"/>
          <mapper class="com.dm.mapper.UserMapper"></mapper>\
          <mapper url="file:///var/mappers/UserMapper.xml"></mapper>
          <package name="com.dm.mapper"></package>
       </mappers>
       */
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

构建二级缓存

cacheElement

解析二级缓存

private void cacheElement(XNode context) {
  if (context != null) {
    //解析cache节点的type属性
    String type = context.getStringAttribute("type", "PERPETUAL");
    //根据type的String获取class类型
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    //获取缓存过期策略:默认是LRU
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    //flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
    Long flushInterval = context.getLongAttribute("flushInterval");
    //size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
    Integer size = context.getIntAttribute("size");
    //只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    Properties props = context.getChildrenAsProperties();
    //把缓存节点加入到Configuration中
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

useNewCache

构建二级缓存

public Cache useNewCache(Class<? extends Cache> typeClass,
    Class<? extends Cache> evictionClass,
    Long flushInterval,
    Integer size,
    boolean readWrite,
    boolean blocking,
    Properties props) {
  Cache cache = new CacheBuilder(currentNamespace)
      .implementation(valueOrDefault(typeClass, PerpetualCache.class))
      .addDecorator(valueOrDefault(evictionClass, LruCache.class))
      .clearInterval(flushInterval)
      .size(size)
      .readWrite(readWrite)
      .blocking(blocking)
      .properties(props)
      .build();
  configuration.addCache(cache);
  currentCache = cache;
  return cache;
}

public Cache build() {
    // implementation -- PerpetualCache
    // decorators -- LruCache
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // cache = new SerializedCache(cache)
      // cache = new LoggingCache(cache);
      // cache = new SynchronizedCache(cache);
      // 通过责任链构建装饰器 PerpetualCache -> LruCache -> SerializedCache -> LoggingCache -> SynchronizedCache
      // 调用 SynchronizedCache -> LoggingCache -> SerializedCache -> PerpetualCache -> LruCache
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

解析**Mapper.xml文件

如果是批量扫描也就是mapper使用package模式会调用addMapper方法,往里面跟到MapperAnnotationBuilder.parse()下的loadXmlResource也是用下面的方法进行的xml解析

如果是用得注解形式SQL在parseStatement解析成MappedStatement

mapperElement

解析我们的mybatis-config.xml的mapper节点

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 获取我们mappers节点下的一个一个的mapper节点
    for (XNode child : parent.getChildren()) {
  
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          // 真正的解析我们的mapper.xml配置文件,解析SQL
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

这段代码核心点其实就是构建一个XMLMapperBuilder调用parse方法解析SQL

XMLMapperBuilder.parse

public void parse() {
  // 判断当前的Mapper是否被加载过
  if (!configuration.isResourceLoaded(resource)) {
    // 真正的解析我们的 <mapper namespace="com.dm.mapper.UserMapper">
    // ================ 这个方法重要 ================
    configurationElement(parser.evalNode("/mapper"));
    // 把资源保存到我们Configuration中
    configuration.addLoadedResource(resource);

    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

configurationElement

解析**Mapper.xml文件mapper节点

private void configurationElement(XNode context) {
  try {
      
    /**
     * 解析我们的namespace属性
     * <mapper namespace="com.dm.mapper.UserMapper">
     */
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
      
    /**
     * 保存我们当前的namespace  并且判断接口完全类名==namespace
     */
    builderAssistant.setCurrentNamespace(namespace);
      
    /**
     * 解析我们的缓存引用
     * 说明我当前的缓存引用和DeptMapper的缓存引用一致
     * <cache-ref namespace="com.dm.mapper.DeptMapper"></cache-ref>
          解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
          异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
     */
    cacheRefElement(context.evalNode("cache-ref"));
      
    /**
     * 解析我们的cache节点
     * <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
        解析到:org.apache.ibatis.session.Configuration#caches
               org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
     */
    cacheElement(context.evalNode("cache"));
      
    /**
     * 解析paramterMap节点
     */
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      
    /**
     * 解析我们的resultMap节点
     * 解析到:org.apache.ibatis.session.Configuration#resultMaps
     *    异常 org.apache.ibatis.session.Configuration#incompleteResultMap
     */
    resultMapElements(context.evalNodes("/mapper/resultMap"));
      
    /**
     * 解析我们通过sql节点
     *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
     *   其实等于 org.apache.ibatis.session.Configuration#sqlFragments
     *   因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
     */
    sqlElement(context.evalNodes("/mapper/sql"));
      
    /**
     * 解析我们的select | insert |update |delete节点
     * 解析到org.apache.ibatis.session.Configuration#mappedStatements
     */
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

创建SqlSession

通过SqlSessionFactory.openSession()创建SqlSession对象

openSessionFromDataSource

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();
  }
}

newExecutor

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  //批量的执行器
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    //可重复使用的执行器
    executor = new ReuseExecutor(this, transaction);
  } else {
    //简单的sql执行器对象
    executor = new SimpleExecutor(this, transaction);
  }
  //判断mybatis的全局配置文件是否开启缓存
  if (cacheEnabled) {
    //把当前的简单的执行器包装成一个CachingExecutor
    executor = new CachingExecutor(executor);
  }
  //调用所有的拦截器对象plugin方法
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

从上诉代码可以发现Executor共有4种BatchExecutor,ReuseExecutor,SimpleExecutor,CachingExecutor

如果开启了二级缓存Executor就是CachingExecutor包装了一个其他三种中的Executor,没有开启就是其他三种中的一种

Mapper执行流程

Mapper在SqlSession.getMapper中获取到了Mapper代理对象MapperProxy,MapperProxy中保存了sqlSession, mapperInterface, methodCache参数,包含sqlSession,接口,和方法,到时候直接反射通过接口和方法就可以调到指定的SQL语句

直接来到MapperProxy.invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    // 是否接口的默认方法
    } else if (method.isDefault()) {   
    
      // 调用我们的接口中的默认方法
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // 调用
  return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  //判断我们执行sql命令的类型
  switch (command.getType()) {
    //insert操作
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    //update操作
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    //delete操作
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    //select操作
    case SELECT:
      //返回值为空
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        //返回值是一个List
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        //返回值是一个map
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        //返回游标
        result = executeForCursor(sqlSession, args);
      } else {
        //查询返回单个

        //解析我们的参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行sql
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

具体查询流程

这里我们查询的是selectById所以我们这里会执行sqlSession.selectOne(),在底层selectOne还是会调selectList方法只是取了第一个

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    /**
     * 通过我们的statement去我们的全局配置类中获取MappedStatement
     */
    MappedStatement ms = configuration.getMappedStatement(statement);
    /**
     * 如果前面用插件代理了executor这里会首先执行Plugin代理类,执行拦截器调用Plugin.invoke
     * 
     * 通过执行器去执行我们的sql对象包装我们的集合类参数
     * 一般情况下是executor为CachingExecutor对象
     */
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

上面的 executor.query这里具体要看是哪个executor,在newExecutor方法中有体现。开启了二级缓存是CachingExecutor没有开启是BaseExecutor,这里我们走的是CachingExecutor

如果使用的是CachingExecutor就会直接在二级缓存拿,二级缓存没拿到就调用delegate拿到封装前的类,封装前的Executor有3种,但他们都没有实现query方法就会调基类的query,这里就会调用BaseExecutor.query

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //通过参数对象解析我们的sql详细信息
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

// CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 判断我们我们的mapper中是否开启了二级缓存<cache></cache>
    Cache cache = ms.getCache();
    // 判断是否配置了cache
    if (cache != null) {
      // 判断是否需要刷新缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 先去二级缓存中获取
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // 二级缓存中没有获取到
        if (list == null) {
          //通过查询数据库去查询
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //加入到二级缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //没有整合二级缓存,直接去查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }



// BaseExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    //已经关闭,则抛出 ExecutorException 异常
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清空本地缓存,如果queryStack为零,并且要求清空本地缓存。
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      // 从一级缓存中,获取查询结果
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 获取到,则进行处理
      if (list != null) {
        //处理存过的
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 获得不到,则从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

在BaseExecutor中queryFromDatabase走了doQuery,这个方法由创建的具体Executor类实现有BatchExecutor,ReuseExecutor,SimpleExecutor三种,这里我们进入SimpleExecutor

SimpleExecutor#doQuery

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    // 内部封装了ParameterHandler和ResultSetHandler
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

从这一部分代码可以看出插件代理调用顺序 Executer->StatementHandler->ParameterHandler->ResultSetHandler


文章作者: dm
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dm !
评论
 上一篇
Spring整合MyBatis源码剖析 Spring整合MyBatis源码剖析
MyBatis整合MyBatis源码设计很多Spring IOC的内容,要想明白MyBatis如何整合进Spring需要对Spring IOC的加载流程和扩展点很熟悉,详情可以看SpringIOC容器加载流程和源码剖析 Demo首先引入sp
2022-08-23
下一篇 
MySQL事务系统解析 MySQL事务系统解析
前言最近啃MySQL相关的知识,了解到事务以及事务隔离机制,想要深入了解下事务是如何实现的下面是此次主要探讨的几点方向。这里先推荐几篇文章 数据库内核月报 - 2017 / 12 数据库内核月报 - 2017 / 10 数据库内核月报 -
2022-07-10
  目录