Mybatis源码前戏之配置文件是如何解析的?

Mybatis源码之配置文件详解

之前介绍了Mybatis的整个核心处理架构,接着我们来聊一聊Mybatis是如何处理和解析配置文件的。

首先通过SqlSessionFactoryBuilder工厂构建sqlSessionFactory过程中,此时新创建了一个XMLConfigBuilder 对象,我们可以debug进去看看到底做了什么事

java 复制代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 前戏:准备工作:将配置文件加载到内存中并生成一个document对象 ,同时初始化Configuration对象
      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.
      }
    }
  }

这里紧接着看见了一个对象 XMLMapperEntityResolver(暂时不知道这对象有什么作用)

java 复制代码
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

此时看到下图

我们查看mybatis源码定义的dtd后缀文件,可以看到这里面有很多xml中定义的标签以及对应的属性名称

所以此时我们在xml文件中随意定义一个标签,它是不能够进行解析和验证操作的

因为在这里引入了对应的dtd文件

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">

那么它就会去验证当前文件的属性标签是否符合当前文件的类型定义信息,符合的话才可以写,不然是识别不了的。

此时还有一个问题就是识别dtd文件是需要联网的,但是有的项目为了保密或者公司要求,是不能联网的,那么此时mybatis提供了相应的方法,把dtd文件存储到本地,此时一样可以识别到的

java 复制代码
/**
   * Converts a public DTD into a local one.
   * 核心就是覆盖这个方法,达到转public DTD到本地DTD的目的
   *
   * @param publicId
   *          The public id that is what comes after "PUBLIC"
   * @param systemId
   *          The system id that is what comes after the public id.
   * @return The InputSource for the DTD
   *
   * @throws org.xml.sax.SAXException
   *           If anything goes wrong
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        // 查找SystemId指定的DTD文档,并调用getInputSource方法读取DTD文档
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }

此时我们继续向下看,XPathPareser做了什么工作

java 复制代码
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
java 复制代码
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    //共通构造函数,除了把参数都设置到实例变量里面去以外,还初始化了XPath
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

这里给类的成员变量进行了赋值操作,但是有一点注意,这里的xpath对象可以看出是对xml格式的文件进行解析的

紧接着创建一个document对象,创建过程如下(不重要,感兴趣自行查看)

java 复制代码
// 在创建文档之前一定要先调用commonConstructor方法完成初始化操作
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      // 创建DocumentBuilderFactory对象
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      // 对DocumentBuilderFactory进行一些列的配置
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      // 名称空间
      factory.setNamespaceAware(false);
      // 忽略注释
      factory.setIgnoringComments(true);
      // 忽略空白
      factory.setIgnoringElementContentWhitespace(false);
      // 把 CDATA 节点转换为 Text 节点
      factory.setCoalescing(false);
      // 扩展实体引用
      factory.setExpandEntityReferences(true);

      // 创建DocumentBuilder对象并进行配置
      DocumentBuilder builder = factory.newDocumentBuilder();
      // 需要注意的就是定义了EntityResolver(XMLMapperEntityResolver),这样不用联网去获取DTD,
      // 将DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,来达到验证xml合法性的目的
      builder.setEntityResolver(entityResolver);
      // ErrorHandler接口的方法都是空实现
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      // 加载xml文件
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

最终的document对象包含了一些子节点的相关信息(正好跟我们xml里面的标签一样 有父子关系的标签)

此时开始执行Configuration的无参构造

java 复制代码
// 上面的6个构造函数最后都会合流到这个函数,传入XPathParser
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // 调用父类初始化configuration
    super(new Configuration());
    // 错误上下文设置成SQL Mapper Configuration(xml文件配置)
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // 将Properties全部设置到configuration里面去
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

默认开始初始化Configuration的成员变量

java 复制代码
//环境
  protected Environment environment;

  //---------以下都是<settings>节点-------
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  //默认启用缓存
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
  protected boolean shrinkWhitespacesInSql;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  //默认为简单执行器
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

这里的属性具体什么意思不过多赘述(具体想了解的可以去Mybatis中文官网)

这里的MapperRegistry提供了对mapper的添加和获取的方法(也是一个映射器注册器)

紧接着继续往下看,这里有一个类型处理注册机

java 复制代码
//类型处理器注册机
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

这个类型处理器注册机做啥用的呢?首先我们要知道啊 数据库的类型和Java的实体类不是一一对应的,那么此时肯定需要我们Mybatis去做处理,类型处理器注册机就把类型定义全部一一初始化好了,如下

java 复制代码
public TypeHandlerRegistry(Configuration configuration) {
    //构造函数里注册系统内置的类型处理器
    //以下是为多个类型注册到同一个handler
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    //以下是为同一个类型的多种变种注册到多个不同的handler
    register(Reader.class, new ClobReaderTypeHandler());
    // StringTypeHandler能够将数据从String类型转换成null类型,所以向typeHandlerMap集合注册该对象,并向allTypeHandlersMap集合注册StringTypeHandler
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
    // NStringTypeHandler能够将数据从String类型转换成NVARCHAR,所以向typeHandlerMap集合注册该对象,并向allTypeHandlersMap集合注册NStringTypeHandler
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new StringTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, unknownTypeHandler);
    register(Object.class, JdbcType.OTHER, unknownTypeHandler);
    register(JdbcType.OTHER, unknownTypeHandler);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, new InstantTypeHandler());
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalTime.class, new LocalTimeTypeHandler());
    register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
    register(OffsetTime.class, new OffsetTimeTypeHandler());
    register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
    register(Month.class, new MonthTypeHandler());
    register(Year.class, new YearTypeHandler());
    register(YearMonth.class, new YearMonthTypeHandler());
    register(JapaneseDate.class, new JapaneseDateTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

接下来就是类型别名注册机

java 复制代码
//类型别名注册机
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

可以看到这里做了大量的别名映射(中文官网也有相关的介绍)

接着还会初始化以下对象

java 复制代码
//映射的语句,存在Map里
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  //缓存,存在Map里
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  //结果映射,存在Map里
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

接着会把一些其它的别名注册到别名注册机当中

java 复制代码
public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

这时configuration对象就已经注册完成了,然后接着调用父类完成configuration对象的初始化工作(这里获取到了类型别名器注册机、类型处理器注册机)并且对configuration对象进行了赋值操作。

java 复制代码
public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

紧接着开始parse的解析工作

java 复制代码
// 解析配置
  public Configuration parse() {
    // 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

因为我们刚刚是把这个xml文件转成了一个Document对象(这里面有父子节点),所以我们需要先解析最外层的 / configuration,然后再解析内层标签(详细解析过程请自行查阅evalNode方法)

java 复制代码
// 解析配置
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      // 解析properties
      propertiesElement(root.evalNode("properties"));
      // 解析settings
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      // 设置vfsImpl字段
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 解析类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析插件
      pluginElement(root.evalNode("plugins"));
      // 对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      // 对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工厂
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//设置具体的属性到configuration对象
      // read it after objectFactory and objectWrapperFactory issue #631
      // 环境
      environmentsElement(root.evalNode("environments"));
      // databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

接着我们可以查看properties如何解析的

java 复制代码
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {

      // 解析properties的子节点的name和value属性,并记录到Properties中
      Properties defaults = context.getChildrenAsProperties();
      // 解析properties的resource和url属性,这两个属性用于确定properties配置文件的位置
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      // resource和url不能同时存在,否则抛出异常
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      // 与Configuration对象中的variables集合合并
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      // 更新XPathParser和Configuration的variables字段
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

解析工作完成后,可以读取到db.properties里面的值

此时还要把刚刚读取到db.properties的值放到parser这个解析器中去,后面再解析数据源的时候才可以把这里的读取到的属性放进去

接下来就是settings的解析过程

java 复制代码
private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    // 解析settings子节点的name和value属性,并返回properties对象
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // 创建configuration对应的MetaClass对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    // 检测Configuration中是否定义了key指定属性的setter方法
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

继续就是typeAliases标签的解析过程 (将解析后的com.mashibing.bean注册到类型别名注册器中)

java 复制代码
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      // 处理全部子节点
      for (XNode child : parent.getChildren()) {
        // 处理package节点
        if ("package".equals(child.getName())) {
          // 获取指定的包名
          String typeAliasPackage = child.getStringAttribute("name");
          // 通过TypeAliasRegistry扫描指定包中所有的类,并解析@Alias注解,完成别名注册
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
          // 处理typeAlias节点
        } else {
          // 获取指定的别名
          String alias = child.getStringAttribute("alias");
          // 获取别名对应的类型
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            // 根据Class名字来注册类型别名
            // 调用TypeAliasRegistry.registerAlias
            if (alias == null) {
              // 扫描@Alias注解,完成注册
              typeAliasRegistry.registerAlias(clazz);
            } else {
              // 注册别名
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

紧接着我们可以看到settingsElement方法,这个方法的作用是把刚刚解析的settings标签里面的内容给设置到configuration对象中的

java 复制代码
private void settingsElement(Properties props) {
    // 如何自动映射列到字段/属性
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    // 自动映射不知道的列
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    // 缓存
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    // proxyFactory (CGLIB | JAVASSIST)
    // 延迟加载的核心技术就是用代理模式,CGLIB/JAVASSIST两者选一
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    // 延迟加载
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    // 延迟加载时,每种属性是否还要按需加载
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    // 允不允许多种结果集从一个单独 的语句中返回
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    // 使用列标签代替列名
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    // 允许 JDBC 支持生成的键
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    // 配置默认的执行器
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    // 超时时间
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    // 默认获取的结果条数
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    // 默认结果集合的类型
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    // 是否将DB字段自动映射到驼峰式Java属性(A_COLUMN-->aColumn)
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    // 嵌套语句上使用RowBounds
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    // 默认用session级别的缓存
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    // 为null值设置jdbctype
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    // Object的哪些方法将触发延迟加载
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    // 使用安全的ResultHandler
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    // 动态SQL生成语言所使用的脚本语言
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    // 枚举类型处理器
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    // 当结果集中含有Null值时是否执行映射对象的setter或者Map对象的put方法。此设置对于原始类型如int,boolean等无效。
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    // 是否使用实际参数名称
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    // logger名字的前缀
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    // 配置工厂
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
  }

开始解析environments标签(default属性会读取到development开发环境)

java 复制代码
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      // 未指定XMLConfigBuilder.environment字段,则使用default属性
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      // 遍历子节点
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        // 与XmlConfigBuilder.environment字段匹配
        if (isSpecifiedEnvironment(id)) {
          // 创建TransactionFactory
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 创建DataSourceFactory和DataSource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          // 创建Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 将Environment对象记录到Configuration.environment字段中
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }

接下来通过读取transactionManager标签来构建得到 JdbcTransactionFactory对象,从而得到JdbcTransactionManager对象来管理整个sql执行过程中的事务。

接着调用dataSourceElement方法来解析数据源

java 复制代码
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 根据type="POOLED"解析返回适当的DataSourceFactory
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

此时的数据源已经拿到了连接信息 (但是此时并没有进行数据库的连接)

构建一个Environment对象,并将该对象放到configuration的environment属性中,此时configuration对象已经对很多属性完成了初始化的工作

databaseIdProviderElement方法是为了解析数据库厂商(有兴趣可以自行阅读,这里不做讲解)

接着看类型处理器typeHandlerElement方法

java 复制代码
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果是package
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          // 调用TypeHandlerRegistry.register,去包下找所有类
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          // 如果是typeHandler
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          // 调用TypeHandlerRegistry.register(以下是3种不同的参数形式)
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

最后看mappers标签如何解析的 (重点)

java 复制代码
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 处理mapper子节点
      for (XNode child : parent.getChildren()) {
        // package子节点
        if ("package".equals(child.getName())) {
          // 自动扫描包下所有映射器
          String mapperPackage = child.getStringAttribute("name");
          // 扫描指定的包,并向mapperRegistry注册mapper接口
          configuration.addMappers(mapperPackage);
        } else {
          // 获取mapper节点的resource、url、class属性,三个属性互斥
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
          if (resource != null && url == null && mapperClass == null) {
            // 使用类路径
            ErrorContext.instance().resource(resource);
            try(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) {
            // 使用绝对url路径
            ErrorContext.instance().resource(url);
            try(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) {
            // 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
            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.");
          }
        }
      }
    }
  }

并且会把mapperPackage的内容加入到mapperRegistry中

然后会得到具体的接口是哪一个,后面进行遍历然后一个一个添加

在添加映射之前会有一系列的判断

java 复制代码
public ResolverUtil<T> find(Test test, String packageName) {
    // 根据包名获取其对应的路径
    String path = getPackagePath(packageName);

    try {
      // 通过VFS.list查找packageName包下所有资源
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
        if (child.endsWith(".class")) {
          // 检测该类是否符合test条件
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

    return this;
  }
java 复制代码
protected void addIfMatching(Test test, String fqn) {
    try {
      // fqn是类的完全限定名,即包括其所在包的包名
      String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
      }

      // 加载指定的类
      Class<?> type = loader.loadClass(externalName);
      // 通过matches方法检测条件是否满足
      if (test.matches(type)) {
        // 将符合条件的类记录到matches集合中
        matches.add((Class<T>) type);
      }
    } catch (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a "
          + t.getClass().getName() + " with message: " + t.getMessage());
    }
  }

添加映射的过程中会判断是不是一个接口,如果不是则有问题

java 复制代码
// 添加映射
  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 {
        // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
        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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        // 如果加载过程中出现异常需要再将这个mapper从mybatis中删除
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);这段代码是为了解析Mapper接口上的注解

紧接着parser.pars()开始具体的解析工作

java 复制代码
public void parse() {
    String resource = type.toString();
    // 检测是否已经加载过该接口
    if (!configuration.isResourceLoaded(resource)) {
      // 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的映射文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      // 解析@CacheNamespace注解
      parseCache();
      // 解析@CacheNamespaceRef注解
      parseCacheRef();
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
          parseResultMap(method);
        }
        try {
          // 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
          parseStatement(method);
        } catch (IncompleteElementException e) {
          // 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
    parsePendingMethods();
  }

loadXmlResource()加载xml文件的资源,Mybatis中写了dao接口必须要有与之对应的xml文件 (下方可以看到已经取到了接口对应的完整的xml路径)

java 复制代码
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();
      }
    }
  }

然后开始加载mapper的映射文件,InputStream inputStream = type.getResourceAsStream("/" + xmlResource);

然后立即构建了XMLMapperBuilder对象,开始解析mapper的映射文件了

java 复制代码
// 解析
  public void parse() {
    // 判断是否已经加载过该映射文件
    if (!configuration.isResourceLoaded(resource)) {
      // 处理mapper节点
      configurationElement(parser.evalNode("/mapper"));
      // 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
      configuration.addLoadedResource(resource);
      // 绑定映射器到namespace
      bindMapperForNamespace();
    }

    // 处理ConfigurationElement方法中解析失败的resultMap节点
    parsePendingResultMaps();
    // 处理ConfigurationElement方法中解析失败的cache-ref节点
    parsePendingCacheRefs();
    // 处理ConfigurationElement方法中解析失败的SQL语句节点
    parsePendingStatements();
  }

开始处理mapper标签

java 复制代码
  private void configurationElement(XNode context) {
    try {
      // 获取mapper节点的namespace属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
      builderAssistant.setCurrentNamespace(namespace);
      // 解析cache-ref节点
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析cache节点
      cacheElement(context.evalNode("cache"));
      // 解析parameterMap节点
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析resultMap节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析sql节点
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析select、update、insert、delete等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);
    }
  }

上述的部分节点解析不重点关注(感兴趣可自行了解解析过程),除了这个buildStatementFromContext方法

java 复制代码
// 配置select|insert|update|delete
  private void buildStatementFromContext(List<XNode> list) {
    // 调用构建语句
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
java 复制代码
// 构建语句
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 构建所有语句,一个mapper下可以有很多select
      // 语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 核心XMLStatementBuilder.parseStatementNode
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // 如果出现SQL语句不完整,把它记下来,塞到configuration去
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

这里已经可以拿到对应的sql语句

然后调用parseStatementNode方法来看如何处理SELECT等多种标签的

java 复制代码
public void parseStatementNode() {
    // 获取SQL节点的id以及databaseId属性
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // 如果databaseId不匹配,退出
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 根据SQL节点的名称决定其SqlCommandType
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //是否要缓存select结果
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
    //这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // 在解析SQL语句之前,先处理其中的include节点
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

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

    //脚本语言,mybatis3.2的新功能
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    //解析之前先解析<selectKey>
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    // 获取selectKey节点对应的SelectKeyGenerator的id
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // SQL节点下存在SelectKey节点
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 根据SQL节点的useGeneratedKeys属性值、mybatis-config.xml中全局的useGeneratedKeys配置,以及是否为insert语句,决定使用的KeyGenerator接口实现
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    //解析成SqlSource,一般是DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    //语句类型, STATEMENT|PREPARED|CALLABLE 的一种
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // 获取每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 获取超时时间
    Integer timeout = context.getIntAttribute("timeout");
    // 引用外部parameterMap
    String parameterMap = context.getStringAttribute("parameterMap");
    // 结果类型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // 引用外部的resultMap
    String resultMap = context.getStringAttribute("resultMap");
    // 结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }

    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyProperty = context.getStringAttribute("keyProperty");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // 通过MapperBuilderAssistant创建MappedStatement对象,并添加到mappedStatements集合中保存
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

最后调用 builderAssistant.addMappedStatement方法将取到的值通过建造者模式得到一个MappedStatement对象然后放入到configuration中

java 复制代码
// 增加映射语句
  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加上namespace前缀
    id = applyCurrentNamespace(id, false);
    // 是否是select语句
    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);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

此时就已经完成了配置文件的所有解析工作了。 此处附赠自己画的流程总结图,帮助大家更好的理解配置文件的解析工作

相关推荐
码农小野3 小时前
基于SpringBoot的自习室预订系统
java·spring boot·后端
ac-er88884 小时前
如何在Flask中实现国际化和本地化
后端·python·flask
Adolf_19935 小时前
Flask-WTF的使用
后端·python·flask
林太白8 小时前
❤Node09-用户信息token认证
数据库·后端·mysql·node.js
骆晨学长8 小时前
基于Springboot的助学金管理系统设计与实现
java·spring boot·后端
蒙娜丽宁9 小时前
深入理解Go语言中的接口定义与使用
开发语言·后端·golang·go
AskHarries9 小时前
java使用ByteBuffer进行多文件合并和拆分
java·后端
不染_是非9 小时前
Django学习实战篇六(适合略有基础的新手小白学习)(从0开发项目)
后端·python·学习·django
代码对我眨眼睛10 小时前
springboot从分层到解耦
spring boot·后端
The Straggling Crow10 小时前
go 战略
开发语言·后端·golang