Mybatis源码解析(二) —— 加载 Configuration

Mybatis源码解析(二) —— 加载 Configuration

   正如上文所看到的 Configuration 对象保存了全部Mybatis的配置信息,也就是说mybatis-config.xml 以及 mapper.xml 中的全部信息均可以在 Configuration 对象中获取到。因此通常状况下,Configuration 对象只会存在一个。经过上篇文章咱们知道了mybatis-config.xml 和 mapper.xml分别是经过 XMLConfigBuilder 和 XMLMapperBuilder 进行解析存储到Configuration的。但有2个问题须要咱们去了解:java

  • 一、 XMLConfigBuilder 和 XMLMapperBuilder 是如何解析xml信息的?
  • 二、 Configuration 内部结构是怎样的?它是如何存储 xml信息的?

   那么,咱们就带着这2个问题去分析下源码吧!sql

1、 Configuration 属性

  Configuration包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构以下:数据库

  • configuration
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

  其中咱们最常配置的是 settings 。一个配置相对完整的 settings 元素的示例以下:apache

<settings>
  <!-- 全局地开启或关闭配置文件中的全部映射器已经配置的任何缓存 -->
  <setting name="cacheEnabled" value="true"/>
  <!-- 延迟加载的全局开关。当开启时,全部关联对象都会延迟加载。 特定关联关系中可经过设置 fetchType 属性来覆盖该项的开关状态。 -->
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- 是否容许单一语句返回多结果集(须要驱动支持) -->
  <setting name="multipleResultSetsEnabled" value="true"/>
  <!-- 使用列标签代替列名 -->
  <setting name="useColumnLabel" value="true"/>
  <!-- 容许 JDBC 支持自动生成主键,须要驱动支持。 若是设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工做(好比 Derby) -->
  <setting name="useGeneratedKeys" value="false"/>
  <!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(不管是否嵌套)-->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <!-- 指定发现自动映射目标未知列(或者未知属性类型)的行为。
       NONE: 不作任何反应
       WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
       FAILING: 映射失败 (抛出 SqlSessionException) -->
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <!-- 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新 -->
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <!-- 设置超时时间,它决定驱动等待数据库响应的秒数 -->
  <setting name="defaultStatementTimeout" value="25"/>
  <!-- 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只能够在查询设置中被覆盖 -->
  <setting name="defaultFetchSize" value="100"/>
  <!-- 容许在嵌套语句中使用分页(RowBounds)。若是容许使用则设置为 false-->
  <setting name="safeRowBoundsEnabled" value="false"/>
  <!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的相似映射 -->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种状况下会缓存一个会话中执行的全部查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不一样调用将不会共享数据-->
  <setting name="localCacheScope" value="SESSION"/>
  <!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动须要指定列的 JDBC 类型,多数状况直接用通常类型便可,好比 NULL、VARCHAR 或 OTHER-->
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <!-- 指定哪一个对象的方法触发一次延迟加载-->
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
  <!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
  <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
复制代码

   查看 Configuration 源码,咱们能够轻松的找到上面配置对应的字段属性:缓存

protected Environment environment;
 
   protected boolean safeRowBoundsEnabled = false;
   protected boolean safeResultHandlerEnabled = true;
   protected boolean mapUnderscoreToCamelCase = false;
   protected boolean aggressiveLazyLoading = true;
   protected boolean multipleResultSetsEnabled = true;
   protected boolean useGeneratedKeys = false;
   protected boolean useColumnLabel = true;
   protected boolean cacheEnabled = true;
   protected boolean callSettersOnNulls = false;
   protected String logPrefix;
   protected Class <? extends Log> logImpl;
   protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
   protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
   protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
   protected Integer defaultStatementTimeout;
   protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
   protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
 
   protected Properties variables = new Properties();
   protected ObjectFactory objectFactory = new DefaultObjectFactory();
   protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
   protected MapperRegistry mapperRegistry = new MapperRegistry(this);
 
   protected boolean lazyLoadingEnabled = false;
   protected ProxyFactory proxyFactory;
 
   protected String databaseId;
   
   protected Class<?> configurationFactory;
 
   protected final InterceptorChain interceptorChain = new InterceptorChain();
   protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
   protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
   protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
复制代码

   上面的字段属性对应的是mybatis-config.xml 配置文件,那么对应mapper.xml的字段属性以下:session

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
复制代码

  其中 咱们最须要关注的是 mappedStatementsresultMaps 以及 sqlFragmentsmybatis

  • resultMaps: 不难理解就是 保存了 mapper.xml 中的 resultMap 节点信息
  • mappedStatements: 保存了 Mapper 配置文件中得 select/update/insert/delete节点信息
  • sqlFragments: 保存了 Mapper 配置文件中得 sql 节点信息

  上面3种是咱们在平时项目开发中使用最多的,咱们能够发现其 都是 StrictMap 这个 内部类 的 value,那咱们来具体分析下 StrictMap 与普通Map有什么不同的地方:app

public V put(String key, V value) {
      if (containsKey(key))
        throw new IllegalArgumentException(name + " already contains value for " + key);
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          // 存简称 key
          super.put(shortKey, value);
        } else {
          // 重复的 key 时存的value 为  Ambiguity ,在 get 时会判断  value 是否为 Ambiguity,是则抛异常
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      // 存全称 key
      return super.put(key, value);
    }
    
    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      // 判断类型是否为 Ambiguity
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }
        
   
    // 截取最后一个"."符号后面的字符串作为shortName
    private String getShortName(String key) {
      final String[] keyparts = key.split("\\.");
      final String shortKey = keyparts[keyparts.length - 1];
      return shortKey;
    }    
    复制代码

   从源码中咱们能够看出,其重写了 put 和 get 2个方法 ,其中 put方法针对 key 作了3个方面的处理:ide

  • 一、 截取最后一个"."符号后面的字符串作为 key 存储一次value
  • 二、 key 重复时,存储的value是一个 Ambiguity 对象。get 获取时会判断value是否未 Ambiguity 类型,若是是则抛出异常
  • 三、 直接调用 super.put(key, value) 存储同一 value 一次(此时key未作任何操做处理)

   也就是说,针对 :源码分析

StrictMap.put("com.xxx.selectId","select * from user where id=?")复制代码

   这一次put请求, StrictMap 中有 2个不一样的key,但value相同的元素:

com.xxx.selectId = select * from user where id=?
          selectId = select * from user where id=?
          复制代码

  以上就是 Configuration 内部属性的大体分析,其中关键的属性分别是: mappedStatementsresultMapssqlFragments,接下来咱们会分析这3。

2、 XMLConfigBuilder.parse()

   XMLConfigBuilder.parse() 主要用于解析mybatis-config.xml 配置文件的信息,其自己没有多大的意义去分析,我这边仍是给出部分源码吧,有想进一步去了解的同窗能够自行深刻分析:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      // 解析 properties(参数配置) 节点
      propertiesElement(root.evalNode("properties"));
      // 解析 typeAliases(别名) 节点
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析 plugins(插件) 节点
      pluginElement(root.evalNode("plugins"));
      // 解析 objectFactory(数据库返回结果集使用) 节点
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析 settings 节点
      settingsElement(root.evalNode("settings"));
      // 解析 environments 节点
      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);
    }
  }
复制代码

3、 XmlMapperBuilder.parse()

   若是拿电脑作比喻的话,前面 XMLConfigBuilder.parse() 就比如主机,可是仅仅有主机是不行的,咱们还得须要鼠标、键盘、显示器等等组件才玩组装成一个完整的电脑。而 XmlMapperBuilder.parse() 所作的事儿就是组装各类组件(Mapper)。咱们来看下 XmlMapperBuilder.parse() 的源码:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析方法源头
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

  // 最核心解析方法
  private void configurationElement(XNode context) {
    try {
      // 咱们都知道 Mapper 的  namespace 是与 Mapper接口路径对应的,因此进来须要判断下 namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
          throw new BuilderException("Mapper's namespace cannot be empty");
      } 
      // 为 MapperBuilderAssistant 设置 namespace
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 resultMap 节点信息
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 sql 节点信息
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析 select|insert|update|delete 节点信息
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
复制代码

   咱们能够看到核心的解析方法内部分别针对 不一样的节点 进行加载,咱们先看下 resultMap 的解析加载:

加载 resultMap节点

   正如官方所描述的同样 : resultMap 是最复杂也是最强大的元素(用来描述如何从数据库结果集中来加载对象)。 因此其解析复杂度也是最复杂的,咱们先看一个简单的resultMap 节点配置:

<resultMap id="selectUserById" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <result property="phone" column="phone"/>
</resultMap>
复制代码

  结合中上面的配置, 咱们再看下面其解析源码会更加清晰明了:

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取 resultMap 节点中的 id 配置信息,也就是上面示列的  id="selectUserById"
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // 获取 resultMap 节点中的 type 配置信息,也就是上面示列的  type="User"
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    // 获取到全部字节的信息,即 id 节点、result节点等等
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 将获取到的 子节点信息封装到 resultMapping 对象中。
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 实际调用的是 MapperBuilderAssistant.addResultMap() 方法
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
复制代码

   从源码分析来看,整个解析流程分4步走:

  • 一、 获取 resultMap 节点中的 id 配置信息,也就是上面示列的 id="blogPostResult"
  • 二、 获取 resultMap 节点中的 type 配置信息,也就是上面示列的 type="User" ( Class 名)
  • 三、 将获取到的 子节点信息封装到 resultMapping 对象中。
  • 四、 实际调用的是 MapperBuilderAssistant.addResultMap() 方法 将上面3步获取到的数据生成 resultMap 保存到 Configuration 中。

   上面有2 个关键对象: ResultMappingMapperBuilderAssistant。其中 MapperBuilderAssistant 贯穿了整个Mapper的解析,因此先不分析,咱们先看下 ResultMapping ,其源码内部的字段属性有如下:

ResultMapping 类属性

private Configuration configuration;
  private String property;
  private String column;
  private Class<?> javaType;
  private JdbcType jdbcType;
  private TypeHandler<?> typeHandler;
  private String nestedResultMapId;
  private String nestedQueryId;
  private Set<String> notNullColumns;
  private String columnPrefix;
  private List<ResultFlag> flags;
  private List<ResultMapping> composites;
  private String resultSet;
  private String foreignColumn;
  private boolean lazy;
复制代码

ResultMap 类属性

private String id;
  private Class<?> type;
  private List<ResultMapping> resultMappings;
  private List<ResultMapping> idResultMappings;
  private List<ResultMapping> constructorResultMappings;
  private List<ResultMapping> propertyResultMappings;
  private Set<String> mappedColumns;
  private Discriminator discriminator;
  private boolean hasNestedResultMaps;
  private boolean hasNestedQueries;
  private Boolean autoMapping;
复制代码

   相信大多数同窗对其中的 property、column、javaType、jdbcType、typeHandler 这几个相对熟悉不少,正如看到的同样 ResultMapping 主要用于封装 resultMap 节点的全部子节点(子节点嵌套也是同样的) 的 信息。它与 ResultMap 的关系以下:

  • 一、 ResultMap 是由 id、type以及大量的 ResultMapping 对象 组合而成。
  • 二、 ResultMapping对象 是 结果集(数据库操做结果集)与 java Bean对象 属性的 对应关系。 即一个 ResultMapping对象 对应一个 Bean 对象中某个字段属性。
  • 三、 ResultMap 对象是 结果集 与 java Bean对象的 对应关系。即一个 ResultMap 对象 对应一个 Bean对象。

加载 select|insert|update|delete节点

   咱们先看一个简单的 select 节点元素的配置:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>
复制代码

   咱们再来分析下 其实如何被加载的, 咱们来分析下 加载这4个节点的方法 buildStatementFromContext() 源码:

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 建立 XMLStatementBuilder 
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 经过 XMLStatementBuilder的 parseStatementNode() 方法进行加载。
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
复制代码

   咱们能够看到其内部建立了一个 XMLStatementBuilder 对象,而后再调用其 parseStatementNode() 进行加载的。仔细发现,咱们能够看到建立 XMLStatementBuilder 对象须要 的3跟 关系构造参数:configuration、 builderAssistant(是否是很熟悉,没错就是 MapperBuilderAssistant ) 、context(节点信息)

XMLStatementBuilder.parseStatementNode()

   parseStatementNode() 方法是加载 select|insert|update|delete节点 信息的核心,那么咱们深刻分析其内部实现:

// 省略了一些相对不重要的代码
   public void parseStatementNode() {
    
    // 如下几个获取的配置信息,咱们都应该很熟悉吧
    String id = context.getStringAttribute("id");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);

    
    // 读取 <include> 信息 
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());


    // 将每一个<select/>, <update/>,<insert/>,<delete/> 加载(经过 createSqlSource()方法)为一个 DynamicSqlSource (SqlSource 最经常使用子类 ):内部存在 getBoundSql() 方法 会从XML文件读取的映射语句的内容 并封装成 BoundSql。
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    .....

    // 经过 MapperBuilderAssistant 的 addMappedStatement() 添加  MappedStatement 信息。
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
复制代码

   根据源码咱们大体能够把整个流程分红4个部分:

  • 一、 读取 select|insert|update|delete 节点的属性信息,好比: id、resultMap等等咱们经常使用到的信息
  • 二、 处理 节点信息 ,经过 XMLIncludeTransformer.applyIncludes() 方法将 Sql 中的 替换成真实的SQl
  • 三、 经过 LanguageDriver.createSqlSource()方法 将每一个 select|insert|update|delete 节点 内部信息(即SQL语句) 加载(经过 )为一个 SqlSource:内部存在 getBoundSql() 方法会将SQL 替换(主要替换 #{ } 和 ${ }) 并封装成 BoundSql
  • 四、 经过 MapperBuilderAssistant 的 addMappedStatement() 方法往 Configuration 添加 MappedStatement 信息。

   上面中涉及到了几个咱们首次见面的类,咱们先从 SqlSource 开始分析,咱们来看下用得最多的子类 DynamicSqlSource 的源码:

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
  复制代码

   其内部是经过 XMLScriptBuilder.parseScriptNode() 方法 来进行建立 SqlSource,继续查询该方法内部源码:

public SqlSource parseScriptNode() {
    // 将一个SQL内容加载成对个 SqlNode
    List<SqlNode> contents = parseDynamicTags(context);
    // 经过组合模式将多个 SqlNode 组合成一个 SqlNode
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      // 建立动态的 SqlSource
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 建立原始(静态)的 SqlSource
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
  复制代码

   上面源码中关于 SqlNode 以及它的大量子类之间的设计是很了不得,可是因为篇幅有限,这里大体解释下为何须要把 SQL 语句 拆分红多个 SqlNode,咱们先看下示列:


<select id="selectStudents" parameterType="int" resultType="hashmap">
        select 
            stud_id as studId, name, phone
        from students
        <where>
            name = #{name}
            <if test="phone != null">
                AND phone = #{phone}
            </if>
        </where>
        
</select>

  复制代码

   正如上面的sql同样,若是咱们一次性把SQL保存到一个 SqlNode 中,那是否是在获取生成 BoundSql 时 解析相对困难了呢?若是咱们把一些关键点给作个分割是否是相对好解析点呢?因此出现了 IfSqlNode、WhereSqlNode等等,不用看,你们应该也能明白 IfSqlNod 就是用于 存储 :

<if test="phone != null">
                AND phone = #{phone}
            </if>复制代码

   固然不是直接存储的,大体结构能够看下图:

IfSqlNode

   咱们获取 BoundSql 时 IfSqlNode 就会判断 是否知足条件。因此整个 SqlNode 体系是很庞大的,它们分别有不一样的职责。从上面咱们看到最后将 SqlNode 做为 建立 DynamicSqlSource 对象的参数。 咱们来查看下 DynamicSqlSource 源码。看看其是如何获取到 BoundSql的:

public class DynamicSqlSource implements SqlSource {

  private Configuration configuration;
  // 存放了SQL片断信息
  private SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 每一个SqlNode的 apply方法调用时,都将解析好的sql加到context中,最终经过context.getSql()获得完整的sql 
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}复制代码

   正如上面所示同样, 每一个SqlNode都会取调用 apply方法 自行解析并拼接到context。可能有人会问,getBoundSql() 何时会被调用呢? 我这里提早 解释下: 当咱们请求Mapper接口时(即一个SqlSession会话访问数据库时)会调用到。

   BoundSql 内部主要时封装好了请求sql,以及请求参数,这是其源码内部属性:

// 一个完整的sql,此时的sql 就是 JDBC 那种,即  select * from user where id = ?
  private String sql; 
  // 参数列表
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;
复制代码

   至此, 加载 select|insert|update|delete节点 的流程已经很是清晰了。但咱们还有一个 MapperBuilderAssistant 没解析。其实 MapperBuilderAssistant 类如其,它就是 MapperBuilderXml 的 助理。MapperBuilderXml 对象负责从XML读取配置,而 MapperBuilderAssistant 负责建立对象并加载到Configuration中。

4、我的总结

   整个 Configuration 的加载主要分2部分:

  • 一、 mybatis-config.xml 的加载
  • 二、 mapper.xml 的加载

  其中 mapper.xml 的加载 是 最为复杂的,本文也主要解析了它的加载。下面的序列图讲述其加载流程:

mapper.xml 的加载流程

         若是您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!

> 本文由博客一文多发平台 [OpenWrite](https://openwrite.cn?from=article_bottom) 发布!  复制代码
相关文章
相关标签/搜索