Mybatis源码分析

392 阅读15分钟

MyBatis介绍

MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。

Mybaits整体体系图

image.png

例子

public class App {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        Reader reader;
        try {
            //将XML配置文件构建为Configuration配置类
            reader = Resources.getResourceAsReader(resource);
            // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            // 数据源 执行器  DefaultSqlSession
            SqlSession session = sqlMapper.openSession();
            try {
                // 执行查询 底层执行jdbc
                //User user = (User)session.selectOne("com.xxx.mapper.selectById", 1);

                UserMapper mapper = session.getMapper(UserMapper.class);
                System.out.println(mapper.getClass());
                User user = mapper.selectById(1L);
                System.out.println(user.getUserName());
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

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

image.png
可以看到除了XMLConfigBuilder继承于BaseBuilder,还有一些其他的解析配置文件的类。

XMLConfigBuilder.parse方法

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

parseConfiguration方法

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="cacheEnabled" value="true"/>
          <setting name="lazyLoadingEnabled" value="true"/>
         <setting name="mapUnderscoreToCamelCase" value="false"/>
         <setting name="localCacheScope" value="SESSION"/>
         <setting name="jdbcTypeForNull" value="OTHER"/>
          ..............
         </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="Author" type="cn.xxx.pojo.Author"/>
      </typeAliases>
     <typeAliases>
        <package name="cn.xxx.pojo"/>
     </typeAliases>
     解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
     */
    typeAliasesElement(root.evalNode("typeAliases"));
    /**
     * 解析我们的插件(比如分页插件)
     * mybatis自带的
     * 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"));

    /**
     * todo
     */
    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类路径下的
     url:来指定我们磁盘下的或者网络资源的
     class:
     若注册Mapper不带xml文件的,这里可以直接注册
     若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
     -->
     <mappers>
        <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
        <mapper class="com.xxx.mapper.DeptMapper"></mapper>


          <package name="com.xxx.mapper"></package>
        -->
     </mappers>
     * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
               2.
     */
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

mapperElement方法

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    /**
     * 获取我们mappers节点下的一个一个的mapper节点
     */
    for (XNode child : parent.getChildren()) {
      /**
       * 判断我们mapper是不是通过批量注册的
       * <package name="com.xxx.mapper"></package>
       */
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        /**
         * 判断从classpath下读取我们的mapper
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        String resource = child.getStringAttribute("resource");
        /**
         * 判断是不是从我们的网络资源读取(或者本地磁盘得)
         * <mapper url="D:/mapper/EmployeeMapper.xml"/>
         */
        String url = child.getStringAttribute("url");
        /**
         * 解析这种类型(要求接口和xml在同一个包下)
         * <mapper class="com.xxx.mapper.DeptMapper"></mapper>
         *
         */
        String mapperClass = child.getStringAttribute("class");

        /**
         * 我们得mappers节点只配置了
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        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.");
        }
      }
    }
  }
}

addMapper方法

/**
 * 方法实现说明:把我们的Mapper class保存到我们的knownMappers map 中
 * @param type:我们的Mapper接口
 * @return:
 * @exception:
 * @date:2019/8/22 20:29
 */
public <T> void addMapper(Class<T> type) {
  /**
   * 判断我们传入进来的type类型是不是接口
   */
  if (type.isInterface()) {
    /**
     * 判断我们的缓存中有没有该类型
     */
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      /**
       * 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中
       */
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.    mapper注解构造器
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      /**
       * 进行解析
       */
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

MapperAnnotationBuilder.parser方法

public void parse() {
  String resource = type.toString();
  // 是否已经解析mapper接口对应的xml
  if (!configuration.isResourceLoaded(resource)) {
    // 根据mapper接口名获取 xml文件并解析,  解析<mapper></mapper>里面所有东西放到configuration
    loadXmlResource();
    // 添加已解析的标记
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache();
    parseCacheRef();
    // 获取所有方法 看是不是用了注解
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        // issue #237
        if (!method.isBridge()) {
          // 是不是用了注解  用了注解会将注解解析成MappedStatement
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

loadXmlResource方法

private void loadXmlResource() {
  // Spring may not know the real resource name so we check a flag
  // to prevent loading again a resource twice
  // this flag is set at XMLMapperBuilder#bindMapperForNamespace
  if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    String xmlResource = type.getName().replace('.', '/') + ".xml";
    // #1347
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      // Search XML mapper that is not in the module but in the classpath.
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e2) {
        // ignore, resource is not required
      }
    }
    if (inputStream != null) {
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      xmlParser.parse();
    }
  }
}

XMLMapperBuilder.parse方法

/**
 * 方法实现说明:真正的去解析我们的Mapper.xml(EmployeeMapper.xml)
 * 
 * @return:
 * @exception:
 * @date:2019/8/30 16:43
 */
public void parse() {
  /**
   * 判断当前的Mapper是否被加载过
   */
  if (!configuration.isResourceLoaded(resource)) {
    /**
     * 真正的解析我们的 <mapper namespace="com.xxx.mapper.EmployeeMapper">
     *
     */
    configurationElement(parser.evalNode("/mapper"));
    /**
     * 把资源保存到我们Configuration中
     */
    configuration.addLoadedResource(resource);

    bindMapperForNamespace();
  }

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

configurationElement方法

/**
 * 方法实现说明:解析我们的<mapper></mapper>节点
 * @author:xsls
 * @param context document节点
 * @return:
 * @exception:
 * @date:2019/8/31 13:34
 */
private void configurationElement(XNode context) {
  try {
    /**
     * 解析我们的namespace属性
     * <mapper namespace="com.xxx.mapper.EmployeeMapper">
     */
    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.xxx.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节点(该节点mybaits3.5貌似不推荐使用了)
     */
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    /**
     * 解析我们的resultMap节点
     * 解析到:org.apache.ibatis.session.Configuration#resultMaps
     *    异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
     *
     */
    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);
  }
}

cacheElement方法

/**
 * 方法实现说明:解析缓存属性
 *    <cache type=""
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>

 * 
 * @param context:cache节点
 * @return:
 * @exception:
 * @date:2019/8/31 14:13
 */
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;
}

CacheBuilder.build方法

public Cache build() {
  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 = setStandardDecorators(cache);
  } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
    cache = new LoggingCache(cache);
  }
  return cache;
}

setStandardDecorators方法

private Cache setStandardDecorators(Cache cache) {
  try {
    MetaObject metaCache = SystemMetaObject.forObject(cache);
    if (size != null && metaCache.hasSetter("size")) {
      metaCache.setValue("size", size);
    }
    if (clearInterval != null) {
      cache = new ScheduledCache(cache);//ScheduledCache:调度缓存,负责定时清空缓存
      ((ScheduledCache) cache).setClearInterval(clearInterval);
    }
    if (readWrite) {  // 将LRU 装饰到Serialized
      cache = new SerializedCache(cache); //SerializedCache:缓存序列化和反序列化存储
    }
    cache = new LoggingCache(cache);
    cache = new SynchronizedCache(cache);
    if (blocking) {
      cache = new BlockingCache(cache);
    }
    return cache;
  } catch (Exception e) {
    throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
  }
}

这边使用装饰者模式对cache进行增强。

buildStatementFromContext方法

/**
 * 方法实现说明:解析我们得得select|update|delte|insert节点然后
 * 创建我们得mapperStatment对象
 * 
 * @param list:所有的select|update|delte|insert节点
 * @param requiredDatabaseId:判断有没有数据库厂商Id
 * @return:
 * @exception:
 * @date:2019/9/5 21:35
 */
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  /**
   * 循环我们的select|delte|insert|update节点
   */
  for (XNode context : list) {
    /**
     * 创建一个xmlStatement的构建器对象
     */
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

XMLStatementBuilder.parseStatementNode方法

public void parseStatementNode() {
  /**
   * 我们的insert|delte|update|select 语句的sqlId
   */
  String id = context.getStringAttribute("id");
  /**
   * 判断我们的insert|delte|update|select  节点是否配置了
   * 数据库厂商标注
   */
  String databaseId = context.getStringAttribute("databaseId");

  /**
   * 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
   */
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  /**
   * 获得节点名称:select|insert|update|delete
   */
  String nodeName = context.getNode().getNodeName();
  /**
   * 根据nodeName 获得 SqlCommandType枚举
   */
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  /**
   * 判断是不是select语句节点
   */
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  /**
   *  获取flushCache属性
   *  默认值为isSelect的反值:查询:默认flushCache=false   增删改:默认flushCache=true
   */
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  /**
   * 获取useCache属性
   * 默认值为isSelect:查询:默认useCache=true   增删改:默认useCache=false
   */
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);

  /**
   * resultOrdered:  是否需要处理嵌套查询结果 group by (使用极少)
   * 可以将比如 30条数据的三组数据  组成一个嵌套的查询结果
   */
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  /**
   * 解析我们的sql公用片段
   *     <select id="qryEmployeeById" resultType="Employee" parameterType="int">
            <include refid="selectInfo"></include>
            employee where id=#{id}
        </select>
      将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中
   */
  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  /**
   * 解析我们sql节点的参数类型
   */
  String parameterType = context.getStringAttribute("parameterType");
  // 把参数类型字符串转化为class
  Class<?> parameterTypeClass = resolveClass(parameterType);

  /**
   * 查看sql是否支撑自定义语言
   * <delete id="delEmployeeById" parameterType="int" lang="xxxLang">
   <settings>
        <setting name="defaultScriptingLanguage" value="xxxLang"/>
   </settings>
   */
  String lang = context.getStringAttribute("lang");
  /**
   * 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
   */
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  /**
   * 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id
   */
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  /**
   * 我们insert语句 用于主键生成组件
   */
  KeyGenerator keyGenerator;
  /**
   * selectById!selectKey
   * id+!selectKey
   */
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  /**
   * 把我们的命名空间拼接到keyStatementId中
   * com.xxx.mapper.Employee.saveEmployee!selectKey
   */
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  /**
   *<insert id="saveEmployee" parameterType="com.xxx.entity.Employee" useGeneratedKeys="true" keyProperty="id">
   *判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
   */
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {

    /**
     * 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
     * 否者就看我们的mybatis-config.xml配置文件中是配置了
     * <setting name="useGeneratedKeys" value="true"></setting> 默认是false
     * 并且判断sql操作类型是否为insert
     * 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
     * 否则就是NoKeyGenerator.INSTANCE
     */
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  /**
   * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
   * sql脚本对象  .  解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
   */
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  /**
   * STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
   */
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  /**
   * 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
   */
  Integer fetchSize = context.getIntAttribute("fetchSize");
  /**
   * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
   */
  Integer timeout = context.getIntAttribute("timeout");
  /**
   * 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
   */
  String parameterMap = context.getStringAttribute("parameterMap");
  /**
   * 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
   * 可以使用 resultType 或 resultMap,但不能同时使用
   */
  String resultType = context.getStringAttribute("resultType");
  /**解析我们查询结果集返回的类型     */
  Class<?> resultTypeClass = resolveClass(resultType);
  /**
   * 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
   * 可以使用 resultMap 或 resultType,但不能同时使用。
   */
  String resultMap = context.getStringAttribute("resultMap");

  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }

  /**
   * 解析 keyProperty  keyColumn 仅适用于 insert 和 update
   */
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  /**
   * 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
   */
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

以上即是mybatis解析配置文件的全流程源码过程。那么接下来就是openSession打开数据库会话操作。然后数据库操作过程的源码分析。

openSession方法

image.png
有两种实现,进入DefaultSqlSessionFactory类。 image.png

openSessionFromDataSource方法

/**
 * 方法实现说明:从session中开启一个数据源
 * @param execType:执行器类型
 * @param level:隔离级别
 * @return:SqlSession
 * @exception:
 * @date:2019/9/9 13:38
 */
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
     * 详看下面newExecutor方法
     */
    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方法

/**
 * 方法实现说明:创建一个sql语句执行器对象
 * @param transaction:事务
 * @param executorType:执行器类型
 * @return:Executor执行器对象
 * @exception:
 * @date:2019/9/9 13:59
 */
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);
  }
  /**
   * TODO:调用所有的拦截器对象plugin方法
   */
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

selectOne方法

/**
 * 方法实现说明:查询我们当个对象
 * @param statement:我们的statementId(com.xxx.mapper.EmployeeMapper.findOne)
 * @param parameter:调用时候的参数
 * @return: T 返回结果
 * @exception:
 * @date:2019/9/9 20:26
 */
@Override
public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  /**
   * 这里selectOne调用也是调用selectList方法
   */
  List<T> list = this.selectList(statement, parameter);
  //若查询出来有且有一个一个对象,直接返回要给
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    /**
     * 查询的有多个,那么久抛出我们熟悉的异常
     * Expected one result (or null) to be returned by selectOne(), but found: " + list.size()
     */
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

selectList方法

/**
 * 方法实现说明
 * @param statement: statementId
 * @param parameter:参数对象
 * @param rowBounds :mybiats的逻辑分页对象
 * @return:
 * @exception:
 * @date:2019/9/9 20:33
 */
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    /**
     * 第一步:通过我们的statement去我们的全局配置类中获取MappedStatement
     */
    MappedStatement ms = configuration.getMappedStatement(statement);
    /**
     * 通过执行器去执行我们的sql对象
     * 第一步:包装我们的集合类参数
     * 第二步:一般情况下是executor为cacheExetory对象
     */
    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();
  }
}

wrapCollection方法

/**
 * 方法实现说明:包装我们集合类的参数
 * @param object:参数对象
 * @return:Object:包装后的对象
 * @exception:
 * @date:2019/9/9 20:36
 */
private Object wrapCollection(final Object object) {
  //若我们的参数类型是Collection
  if (object instanceof Collection) {
    StrictMap<Object> map = new StrictMap<>();
    //把他key为collection存放到map中
    map.put("collection", object);
    //若我们参数类型是list类型  把key为list作为集合存放到map中
    if (object instanceof List) {
      map.put("list", object);
    }
    return map;
  } else if (object != null && object.getClass().isArray()) {
    //若是数组,存放key为array的map中
    StrictMap<Object> map = new StrictMap<>();
    map.put("array", object);
    return map;
  }
  return object;
}

CachingExecutor.query方法

/**
 * 方法实现说明:通过我们的sql执行器对象执行sql
 * @param ms 用于封装我们一个个的insert|delete|update|select 对象
 * @param parameterObject:参数对象
 * @param rowBounds :mybaits的逻辑分页对象 TODO?????
 * @param resultHandler:结果处理器对象
 * @return:
 * @exception:
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  /**
   * 通过参数对象解析我们的sql详细信息1339025938:1570540512:com.xxx.mapper.selectById:0:2147483647:select id,user_name,create_time from t_user where id=?:1:development
   */
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@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方法

@Override
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.");
  }
  // <2> 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    // <4.1> 从一级缓存中,获取查询结果
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    // <4.2> 获取到,则进行处理
    if (list != null) {
      //处理存过的
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // 获得不到,则从数据库中查询 详细看下面queryFromDatabase方法
      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;
}

queryFromDatabase方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // 详细看下面doQuery方法
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

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();
    // 详细看下面newStatementHandler方法
    StatementHandler handler = configuration.newStatementHandler方法(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //详细看下面prepareStatement方法
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 详细看下面PreparedStatementHandler的query方法
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

newStatementHandler方法

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

prepareStatement方法

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

PreparedStatementHandler.query方法

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // 执行数据库操作
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}