天天看点

Spring源码系列09——Mybatis源码解析Mybatis对比JDBC的优势:Mybatis源码解析过程——解析mybtatis-config.xml全局配置文件Mybatis源码解析过程——解析mapper.xml配置文件根据解析后的configuration对象生成sqlsessionfactory对象

Mybatis对比JDBC的优势:

(1)使用连接池管理数据库连接数据库连接,避免了JDBC频繁的连接和断开数据库资源;

(2)使用mapper.xml配置文件的形式,将sql语句与java代码分离;

(3)mybatis通过parameterType向sql中传递参数,更加灵活;

(4)mybatis可以将查询结果自动映射为java对象,避免了jdbc手工写代码的繁琐过程。

Mybatis源码解析过程——解析mybtatis-config.xml全局配置文件

解析mybatis-config.xml配置文件

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

解析properties节点

解析setting节点

解析我们的mybatis环境,也就是数据库连接配置;

解析到:org.apache.ibatis.session.Configuration#environment

* 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂

*

解析我们的类型处理器节点

解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap

解析解析我们的mapper

Mybatis源码解析过程——解析mapper.xml配置文件

(1)mybatis中mapper的四种配置方式

(1)package:批量解析mapper配置文件

(2)resource:按照配置解析mapper文件

(3)url:从网络资源或者本地磁盘解析mapper文件

(4)class:要求接口和xml在同一个包下面

/**
         * 判断我们mapper是不是通过批量注册的
         * <package name="com.tuling.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.tuling.mapper.DeptMapper"></mapper>
           *
           */
          String mapperClass = child.getStringAttribute("class");
           

(2)解析package配置的mapper.xml

org.apache.ibatis.binding.MapperRegistry#addMapper

// 根据mapper接口名获取 xml文件并解析,  解析<mapper></mapper>里面所有东西放到configuration
      loadXmlResource();
      // 添加已解析的标记
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
           

根据mapper接口的名称,转换处理为xml文件的物理路径,然后利用XMLMapperBuilder类进行解析。

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

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

private void configurationElement(XNode context) {
    try {
      /**
       * 解析我们的namespace属性
       * <mapper namespace="com.tuling.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.tuling.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);
    }
  }
           

(3)解析mapper.xml中配置的sql语句

将sql中的内容解析成MappedStatement对象,然后存入configuration对象中。

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" >
     <settings>
          <setting name="defaultScriptingLanguage" value="tulingLang"/>
     </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.tuling.mapper.Employee.saveEmployee!selectKey
     */
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    /**
     *<insert id="saveEmployee" parameterType="com.tuling.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);
  }
           

根据解析后的configuration对象生成sqlsessionfactory对象

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }