Mybatis源码

文章详细介绍了MyBatis的配置文件解析过程,包括SqlSessionFactory的创建、XMLConfigBuilder的parse方法,以及配置文件中的各项设置。同时,展示了Mapper文件的解析,解释了MappedStatement的生成,涉及动态SQL和SqlSource的处理。
摘要由CSDN通过智能技术生成

一.配置文件解析

1.测试用例

单元测试

 @Test
    public void test21() {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CommonMapper mapper = sqlSession.getMapper(CommonMapper.class);
       ConsultConfigArea  area = new ConsultConfigArea();
       area.setAreaCode("HN1");
       area.setAreaName("hn2");
       area.setState("2");
        int i = mapper.updateArea(area);
        System.out.println(i);
    }

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 resource="db.properties"/>
    <settings>
        <!--全局全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true-->
        <setting name="cacheEnabled" value="false"/>
        <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
            fetchType属性来覆盖该项的开关状态。默认值为false -->
        <setting name="lazyLoadingEnabled" value="false"/>
        <!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,默认值false-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--是否允许单一语句返回多结果集,默认值为true -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!--使用列标签代替列名,默认值为true -->
        <setting name="useColumnLabel" value="true"/>
        <!--允许 JDBC 支持自动生成主键,需要驱动兼容,默认值为false -->
        <setting name="useGeneratedKeys" value="false"/>
        <!--指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL
            只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集
            (无论是否嵌套),默认值为PARTIAL-->
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <!--指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应;
            ARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'
            的日志等级必须设置为 WARN);FAILING: 映射失败 (抛出 SqlSessionException),默认值为NONE -->
        <setting name="autoMappingUnknownColumnBehavior" value="NONE"/>
        <!--配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);
            BATCH 执行器将重用语句并执行批量更新。默认值为SIMPLE -->
<!--        <setting name="defaultExecutorType" value="SIMPLE"/>-->
        <!--设置超时时间,它决定驱动等待数据库响应的秒数。参数为任意正整数,未设置默认值-->
        <setting name="defaultStatementTimeout" value="25"/>
        <!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。参数为任意正整数,
            未设置默认值 -->
<!--        <setting name="defaultFetchSize" value="100"/>-->
        <!--允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false,默认值未false -->
        <setting name="safeRowBoundsEnabled" value="false"/>
        <!--允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。默认值为true -->
        <setting name="safeResultHandlerEnabled" value="true"/>
        <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java
            属性名 aColumn 的类似映射,默认值为false -->
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <!--MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
         默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语
         句执行上,对相同 SqlSession 的不同调用将不会共享数据。-->
        <setting name="localCacheScope" value="SESSION"/>
        <!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情
        况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。默认值为OTHER -->
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <!--指定哪个对象的方法触发一次延迟加载。 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
        <!--指定动态 SQL 生成的默认语言。-->
        <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
        <!--指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或
        null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。-->
        <setting name="callSettersOnNulls" value="false"/>
        <!--当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的
            结果集 (i.e. collectioin and association)。(从3.4.2开始-->
        <setting name="returnInstanceForEmptyRow" value="false"/>
        <!--指定 MyBatis 增加到日志名称的前缀
        <setting name="logPrefix" value="log"/>-->
        <!--指定 MyBatis 所用日志的具体实现,未指定时将自动查找
            SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING。-->
        <setting name="logImpl" value="LOG4J"/>
        <!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB | JAVASSIST-->
        <setting name="proxyFactory" value="CGLIB"/>
        <!--指定VFS的实现
        <setting name="vfsImpl" value="vfs"/>-->
        <!--允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项。
           (从3.4.1开始)-->
        <setting name="useActualParamName" value="true"/>
        <!--指定一个提供Configuration实例的类。 这个被返回的Configuration实例用来加载被反序列化对象的懒加载属性值。
            这个类必须包含一个签名方法static Configuration getConfiguration(). (从 3.2.3 版本开始)
        <setting name="configurationFactory" value="configClass"/>-->
    </settings>
    <typeAliases>
        <!--        <typeAlias type="cn.enjoy.pojo.ConsultConfigArea" alias="Area"/>-->
        <package name="cn.enjoy"/>
    </typeAliases>
    <plugins>
        <plugin interceptor="cn.enjoy.interceptor.PageInterceptor">
            <property name="name" value="Jack"/>
        </plugin>
    </plugins>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${mybatis.druid.driver-class-name}"/>
                <property name="url" value="${mybatis.druid.url}"/>
                <property name="username" value="${mybatis.druid.username}"/>
                <property name="password" value="${mybatis.druid.password}"/>
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${mybatis.druid.driver-class-name}"/>
                <property name="url" value="${mybatis.druid.url}"/>
                <property name="username" value="${mybatis.druid.username}"/>
                <property name="password" value="${mybatis.druid.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    <typeHandlers>
            &lt;!&ndash;            当配置package的时候,mybatis会去配置的package扫描TypeHandler&ndash;&gt;
            <package name="com.dy.demo"/>

            &lt;!&ndash; handler属性直接配置我们要指定的TypeHandler &ndash;&gt;
    &lt;!&ndash;        <typeHandler handler=""/>

            &lt;!&ndash; javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 &ndash;&gt;
            <typeHandler javaType="" handler=""/>

            &lt;!&ndash; jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型  &ndash;&gt;
            <typeHandler jdbcType="" handler=""/>

            &lt;!&ndash; 也可两者都配置 &ndash;&gt;
            <typeHandler javaType="" jdbcType="" handler=""/>&ndash;&gt;

        </typeHandlers>-->
<!--    <objectFactory type=""></objectFactory>-->
    <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>
    <mappers>
        <mapper resource="xml/CommomMapper.xml"/>
        <mapper resource="xml/TUser1Mapper.xml"/>
<!--        <mapper resource="xml/ZgGoodsMapper.xml"/>-->
        <!--        <package name="cn.enjoy.dao"/>-->
    </mappers>
</configuration>
<?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="cn.enjoy.dao.CommonMapper">
    <!-- 开启二级缓存-->
    <cache type="cn.enjoy.cache.CaffeineCache"
               size="1024"
               eviction="LRU"
               flushInterval="120000"
               readOnly="true"/>
               
    <resultMap id="RECORDResultMap" type="cn.enjoy.pojo.ConsultRecord" >
        <id column="ID" property="id" />
        <result column="PSPTID" property="psptId"/>
        <result column="NAME" property="name" jdbcType="VARCHAR"/>
        <result column="ACTIVETIME" property="activeTime" jdbcType="VARCHAR" javaType="string"/>
        <result column="AUTOGRAPH" property="autograph" jdbcType="VARCHAR"/>
        <result column="ISPASS" property="ispass" jdbcType="VARCHAR"/>
        <result column="DOCAUTOGRAPH" property="docautograph" jdbcType="VARCHAR"/>
        <result column="FINGERPRINT" property="fingerprint" jdbcType="VARCHAR"/>
        <result column="PRINT_FLAG" property="printFlag" jdbcType="VARCHAR"/>
        <result column="REMARK" property="remark" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="queryCardIdInfo" resultType="ConsultIdCardInfo">
		select * from consult_idcardinfo
        <trim prefix="WHERE" prefixOverrides="AND|OR">
            <if test="cc.psptId != null and cc.psptId != ''">
                and psptId = #{cc.psptId,jdbcType=VARCHAR}
            </if>
        </trim>
	</select>
</mapper>

2.配置文件解析

1.SqlSessionFactory
SqlSessionFactory 是SqlSession工厂方法,创建SqlSession.
SqlSession 保存执行器Executor,Executor存放事务Transaction,
Transaction存放Datasource ,用于连接数据库

	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	SqlSession sqlSession = sqlSessionFactory.openSession();

2.XMLConfigBuilder.parse 创建Configure
Configure 存放mybatis 所有配置信息

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

3.XMLConfigBuilder.parser() 方法

/**
   * 解析配置文件的入口
   * @return Configuration 对象
   */
  public Configuration parse() {
    // 不允许重复解析
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 从根节点开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

4.parseConfiguration方法

/**
   * 从根节点configuration开始解析下层节点
   * @param root 根节点configuration节点
   */
  private void parseConfiguration(XNode root) {
    try {
      // 解析信息放入Configuration
      // 首先解析properties,以保证在解析其他节点时便可以生效
      //issue #117 read properties first
      //  <properties resource="db.properties"/>
      propertiesElement(root.evalNode("properties"));
      //  <settings>
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //加载自定义类扫描器
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      //别名扫描注册
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      //解析Pojo对象工厂类
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //解析settings标签
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析环境标签
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析类型转换器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

5.mapperElement 解析mappe文件(select udpate insert delete)

/**
   * 解析mappers节点,例如:
   * <mappers>
   *    <mapper resource="com/github/yeecode/mybatisDemo/UserDao.xml"/>
   *    <package name="com.github.yeecode.mybatisDemo" />
   * </mappers>
   * @param parent mappers节点
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 处理mappers的子节点,即mapper节点或者package节点
        if ("package".equals(child.getName())) { // package节点
          // 取出包路径
          String mapperPackage = child.getStringAttribute("name");
          // 全部加入Mappers中
          configuration.addMappers(mapperPackage);
        } else {
          // resource、url、class这三个属性只有一个生效
          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);
            // 使用XMLMapperBuilder解析映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 从网络获得输入流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 使用XMLMapperBuilder解析映射文件
            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.");
          }
        }
      }
    }
  }

6.XMLMapperBuilder.parser

/**
   * 解析映射文件
   */
  public void parse() {
    // 该节点是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
      // 处理mapper节点
      configurationElement(parser.evalNode("/mapper"));
      // 加入已解析的列表,防止重复解析
      configuration.addLoadedResource(resource);
      // 将Mapper注册给configuration
      bindMapperForNamespace();
    }

    // 下面分别用来处理失败的<resultMap>、<cache-ref>、SQL语句
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

7.configurationElement

  /**
   * 解析映射文件的下层节点
   * 解析的所有的数据都保存在configuration对象中
   * @param context 映射文件根节点
   */
  private void configurationElement(XNode context) {
    try {
      // 读取当前映射文件namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 映射文件中其他配置节点的解析
      // cache-ref–从其他命名空间引用缓存配置。
      //如果你不想定义自己的cache,可以使用cache-ref引用别的cache。因为每个cache都以namespace为id,
      //所以cache-ref只需要配置一个namespace属性就可以了。需要注意的是,如果cache-ref和cache都配置了,以cache为准。
      cacheRefElement(context.evalNode("cache-ref"));
     // <!-- 开启二级缓存-->
    // <cache type="cn.enjoy.cache.CaffeineCache" size="1024"  eviction="LRU" flushInterval="120000" readOnly="true"/>-
      cacheElement(context.evalNode("cache"));
      // 解析parameterMap  不常用
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析resultmap  数据库返回字段映射
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析sql  <sql id="queryAreaByAreaCodesql" databaseId="mysql">areaCode,areaName,state</sql>
      sqlElement(context.evalNodes("/mapper/sql"));
      // 处理各个数据库操作语句  (增删改查 重点)
      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);
    }
  }

8 buildStatementFromContext解析增删改查语句(MappedStatement)

 private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
      //  开始解析select update insert delete标签
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

sql语句解析,最后生成 MappedStatement对象

/**
   * 解析select、insert、update、delete这四类节点
   */
  public void parseStatementNode() {
    // 读取当前节点的id与databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 读取节点名称
    String nodeName = context.getNode().getNodeName();
    // 读取和判断语句类型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 处理语句中的Include节点
    //  select <include refid="queryAreaByAreaCodesql"></include> from consult_configarea  
    //引入sql
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 语句类型
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 此时,<selectKey> 和 <include> 节点均已被解析完毕并被删除,开始进行SQL解析
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 判断是否已经有解析好的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 全局或者本语句只要启用自动key生成,则使用key生成
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 读取各个配置属性
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // fetchSize 设置查询每次拉取数据的数量
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

     // sql语句解析,封装到MappedStatement对象
    // 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
    // MapperBuilderAssistant.addMappedStatement  构建者模式
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

sql 内容的解析
langDriver.createSqlSource(configuration, context, parameterTypeClass)

/**
   * SqlSource对象主要由XMLScriptBuild的parseScriptNode方法生成
   * @param configuration 配置信息
   * @param script 映射文件中的数据库操作节点
   * @param parameterType 参数类型
   * @return
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

/**
   * 解析节点生成SqlSource对象
   * 
   * @return SqlSource对象
   */
  public SqlSource parseScriptNode() {
    // 解析XML节点,得到节点树MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 根据节点树是否为动态,创建对应的SqlSource对象
    // 1 只要包含 if trim foreach  $ 都是动态
    // 2 除了动态 都是静态
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
 /**
   * 将XNode对象解析为节点树
   * parseDynamicTags 会逐级分析 XML 文件中的 节点并使用对应的NodeHandler 实现来处理该节点,
   * 最终将所有的节点整合到一个MixedSqlNode 对象中。MixedSqlNode对象就是SQL节点树
   * 例如:
   *	select * from consult_idcardinfo
   *    <trim prefix="WHERE" prefixOverrides="AND|OR">
   *        <if test="cc.psptId != null and cc.psptId != ''">
   *            and psptId = #{cc.psptId,jdbcType=VARCHAR}
   *        </if>
   *    </trim>
   * 如上所示,sql语句解析成SqlNode类似于一棵树,下面会挂很多子节点。
   *  1:
   * 	(select * from consult_idcardinfo)封装成sqlNoe
   * 2 :
   * 	<trim prefix="WHERE" prefixOverrides="AND|OR"> 封装成一个sqlNode
   *   1和2的sqlNode平级放于List集合contexts
   * 3 :
   *      <if test="cc.psptId != null and cc.psptId != ''">会解析成2的sqlNode的子节点。
   * 4:
   * 	and psptId = #{cc.psptId,jdbcType=VARCHAR} 会解析成3的sqlNode的子节点
   *5:
   *	 handler.handleNode(child, contents);会递归调用parseDynamicTags方法
   *6:
   *  MixedSqlNode最后返回MixedSqlNode对象。
   * @param node XNode对象,即数据库操作节点
   * @return 解析后得到的节点树
   */
  protected MixedSqlNode parseDynamicTags(XNode node) {
    // XNode拆分出的SqlNode列表
    List<SqlNode> contents = new ArrayList<>();
    // 输入XNode的子XNode
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      // 循环遍历每一个子XNode
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // CDATA类型或者text类型的XNode节点
        // 获取XNode内的信息
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 只要有一个TextSqlNode对象是动态的,则整个MixedSqlNode就是动态的
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 // 子XNode类型任然是Node类型
        String nodeName = child.getNode().getNodeName();
        // 找到对应的处理器
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        // 用处理器处理节点
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    // 返回一个混合节点,其实就是一个SQL节点树
    return new MixedSqlNode(contents);
  }

将解析的sql放于SqlSource ( DynamicSqlSourceSource 或者 RawSqlSource)对象,最后保存在configuration。
Configuration是mybatis重要的变量,存放所有的配置信息,以及sql信息

/**
   * 采用构建者模式,将MappedStatment存放到configuration
   */
public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    // 构建这模式构建MappedStatiment,存放到configuration. 
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

至此,mybaits重要的解析都已经完成。通过方法调用sql见下一章。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值