MyBatis框架的使用及源码分析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder

在上一篇文章(MyBatis框架的使用及源码分析(一))的demo中看到了SessionFactory的创建过程:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

那么我们就从SqlSessionFactoryBuilder开始,看看Mybatis的加载过程。

SqlSessionFactoryBuilder的核心源码:

package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;


/*
该类的主要功能是。
1、通过读取字符流(Reader)的方式构件SqlSessionFactory。
2、通过字节流(InputStream)的方式构件SqlSessionFacotry。
3、通过Configuration对象构建SqlSessionFactory。
*/
public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }


  //解析inputStream配置。
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  //和下面的build功能是一样的
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  /**
   * 通过Reader读取Mybatis配置,构建SqlSession工厂
   * @param inputStream xml配置文件
   * @param environment 默认是空的, 后面会去读xml配置文件中的environment标签,environment存的是事务管理,数据源
   * @param properties 默认是空的,后面会去读xml配置文件中的properties标签,存的是数据库连接配置。
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {

      //创建XML解析器,用来解析配置文件,包括初始化Configuration对象。
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //configuration是配置类对象。上一步只是初始化了。开始解析,主要过程是把mybatis的标签内容转到configuration对象中。
      Configuration configuration = parser.parse();
      //创建 session 工厂,没做什么,你就理解configuration<==>sqlSessionFactory
      SqlSessionFactory sqlSessionFactory = build(configuration);
      return sqlSessionFactory;
    } 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.
      }
    }
  }

  //以上2个方法最终调用的是build(Configuration config)
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

上面代码主要功能:1、通过读取字符流(Reader)的方式构件SqlSessionFactory。
2、通过字节流(InputStream)的方式构件SqlSessionFacotry。
3、通过Configuration对象构建SqlSessionFactory。

流程:通过源码,我们可以看到SqlSessionFactoryBuilder 通过XMLConfigBuilder 去解析我们传入的mybatis的配置文件,构造出Configuration,最终返回new DefaultSqlSessionFactory(config)的SqlSessionFactory实例。

 

接下来我们看看 XMLConfigBuilder 是怎样解析Mybatis的配置文件的,下面是部分源码:

package org.apache.ibatis.builder.xml;


/**
 * 解析Mybatis配置文件
 */
public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private XPathParser parser;
  private String environment;

  public XMLConfigBuilder(Reader reader) {
    this(reader, null, null);
  }

  public XMLConfigBuilder(Reader reader, String environment) {
    this(reader, environment, null);
  }

  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  public XMLConfigBuilder(InputStream inputStream) {
    this(inputStream, null, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment) {
    this(inputStream, environment, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

  //调用此方法对mybatis配置文件进行解析,返回Configuration对象
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //从根节点configuration,开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
  //解析configuration节点下的10个子节点。
  private void parseConfiguration(XNode root) {
    try {      //解析子节点properties 
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first      //解析子节点typeAliases 别名
      typeAliasesElement(root.evalNode("typeAliases"));      //解析子节点plugins 插件 
      pluginElement(root.evalNode("plugins"));      //解析子节点objectFactory mybatis为结果创建对象时都会用到objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));      //解析子节点objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));      //解析settings定义一些全局性的配置
      settingsElement(root.evalNode("settings"));      //解析environments 可以配置多个运行环境,但是每个SqlSessionFactory 实例只能选择一个运行环境
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631      //解析databaseIdProvider MyBatis能够执行不同的语句取决于你提供的数据库供应商。许多数据库供应商的支持是基于databaseId映射 
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));      //解析typeHandlers 当MyBatis设置参数到PreparedStatement 或者从ResultSet 结果集中取得值时,就会使用TypeHandler  来处理数据库类型与java 类型之间转换
      typeHandlerElement(root.evalNode("typeHandlers"));      //解析mappers 主要的crud操作都是在mappers中定义的
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  
}

从上面可以看出可以配置10个子节点, 分别为:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers。

 

再看SqlSessionFactoryBuilder的这行代码

Configuration configuration = parser.parse();

XMLConfigBuilder调用parse()方法解析Mybatis配置文件,生成Configuration对象。

Configuration类主要是用来存储对Mybatis的配置文件及mapper文件解析后的数据,Configuration对象会贯穿整个Mybatis的执行流程,为Mybatis的执行过程提供必要的配置信息。

 

我们先贴出一个包含了所有属性的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="org/apache/ibatis/databases/blog/blog-derby.properties" />

    <settings>
        <setting name="cacheEnabled" value="true" />
        <setting name="lazyLoadingEnabled" value="false" />
        <setting name="multipleResultSetsEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="useGeneratedKeys" value="false" />
        <setting name="defaultExecutorType" value="SIMPLE" />
        <setting name="defaultStatementTimeout" value="25" />
    </settings>

    <typeAliases>
        <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author" />
        <typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog" />
    </typeAliases>

    <typeHandlers>
        <typeHandler javaType="String" jdbcType="VARCHAR"
            handler="org.apache.ibatis.builder.CustomStringTypeHandler" />
    </typeHandlers>

    <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
        <property name="objectFactoryProperty" value="100" />
    </objectFactory>

    <plugins>
        <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
            <property name="pluginProperty" value="100" />
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value="" />
            </transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver" />
        <property name="DB2" value="db2" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml" />
        <mapper resource="org/apache/ibatis/builder/BlogMapper.xml" />
    </mappers>
</configuration>

再对照看一看Configuration类的成员变量和构造方法:

package org.apache.ibatis.session;


public class Configuration {
  /**   * MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,   * 比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象.   */
  protected Environment environment;
 
  //允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。  protected boolean safeRowBoundsEnabled = false;   //允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false  protected boolean safeResultHandlerEnabled = true;  //是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。  protected boolean mapUnderscoreToCamelCase = false; 
  //当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).
  protected boolean aggressiveLazyLoading = true;  /是否允许单一语句返回多结果集(需要兼容驱动)
  protected boolean multipleResultSetsEnabled = true;   //允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。  protected boolean useGeneratedKeys = false;
  //使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
  protected boolean useColumnLabel = true;  //配置全局性的cache开关
  protected boolean cacheEnabled = true;  /*指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。    注意基本类型(int、boolean等)是不能设置成 null 的。*/
  protected boolean callSettersOnNulls = false;  //指定 MyBatis 增加到日志名称的前缀。
  protected String logPrefix;  //指定 MyBatis 所用日志的具体实现,未指定时将自动查找
  protected Class <? extends Log> logImpl;  /*MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。     默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。     若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。*/
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;  /*当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。     某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。*/
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;  //指定哪个对象的方法触发一次延迟加载。
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));  //设置超时时间,它决定驱动等待数据库响应的秒数。
  protected Integer defaultStatementTimeout;   /*配置默认的执行器。    SIMPLE 就是普通的执行器;    REUSE 执行器会重用预处理语句(prepared statements);     BATCH 执行器将重用语句并执行批量更新。*/  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;  /**   * 指定 MyBatis 应如何自动映射列到字段或属性。    * NONE 表示取消自动映射;   * PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。   * FULL 会自动映射任意复杂的结果集(无论是否嵌套)。   */
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

    //这里配置的属性可以在整个配置文件中使用来替换需要动态配置的属性值    protected Properties variables = new Properties();
  //对象创建工厂,默认的实现类DefaultObjectFactory,用来创建对象,比如传入List.class,利用反射返回ArrayList的实例
  protected ObjectFactory objectFactory = new DefaultObjectFactory();  //对象包装工厂,默认实现类是DefaultObjectWrapperFactory,包装Object实例
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();  //注册Mapper
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);
  //延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
  protected boolean lazyLoadingEnabled = false;  //指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具
  protected ProxyFactory proxyFactory;
  //数据库类型id,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性
  protected String databaseId;
   /**      * 指定一个提供Configuration实例的类. 这个被返回的Configuration实例是用来加载被反序列化对象的懒加载属性值. 
     * 这个类必须包含一个签名方法static Configuration getConfiguration(). (从 3.2.3 版本开始)
     */    protected Class<?> configurationFactory;
  //拦截器链
  protected final InterceptorChain interceptorChain = new InterceptorChain();  //TypeHandler注册
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();  //别名和具体类注册
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();  //这个是指定解析的驱动,比如你可以使用velocity模板引擎来替代xml文件,默认是XMLLanguageDriver,也就是使用xml文件来写sql语句
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    //对应Mapper.xml里配置的Statement
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");  //对应Mapper.xml里配置的cache
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");  //对应Mapper.xml里的ResultMap
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");  //对应Mapper.xml里的ParameterMap
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");  //主键生成器
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  //存储已经加载过的mapper xml资源,见MapperAnnotationBuilder#loadXmlResource
  protected final Set<String> loadedResources = new HashSet<String>();  //存储已经解析过的mapper对应的xml节点
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
  //存储所有未处理的
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();  //存储所有未处理的缓存信息
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();  //存储所有未处理ResultMap的映射信息
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
    /*      * A map holds cache-ref relationship. The key is the namespace that      * references a cache bound to another namespace and the value is the      * namespace which the actual cache is bound to.      */    protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

  public Configuration() {
    //通过使用TypeAliasRegistry来注册一些类的别名
    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 就是 mybatis-config.xml 的对象实体。

mybatis配置文件是由XMLConfigBuilder来解析的,以下是主要功能代码:

//调用此方法对mybatis配置文件进行解析,返回Configuration对象
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //xNode包含整个mybatis配置文件的所有内容,通过xNode我们可以得到配置文件中所有属性值。
  XNode xNode = parser.evalNode("/configuration");
  //将xNode的所有一级标签数据读取出来。
  parseConfiguration(xNode);
  return configuration;
}
/**
 * MyBatis配置文件的核心标签是properties和environments标签。
 * 下面所有的标签都是一级标签
 * 从上面可以看出可以配置10个子节点, 分别为:
 * properties、typeAliases、plugins、objectFactory、objectWrapperFactory、
 * settings、environments、databaseIdProvider、typeHandlers、mappers。
 * @param root
 */
private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    //拿到mybatis配置文件中的properties标签
    XNode properties = root.evalNode("properties");
    propertiesElement(properties);
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    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);

    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));

    //mappers标签配置的是mybatis.xml文件的路径
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

可以看到解析mapper文件的加载解析是从 mapperElement(root.evalNode("mappers"));  开始处理的。

我们继续看mapperElement(XNode parent)方法的代码:

//处理mybatis.xml配置文件中的mapper标签
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        //解析package扫描指定package下的所有mapper接口
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        //解析mapper节点
        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 mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //将Mapper标签里面的resource标签取出来解析,resource标签配的是Mapper.xml配置文件
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

可以看到,mybatisConfig.xml配置下的mappers节点有2种子节点:package节点和mapper节点,我这里先讨论xml的模式,先看mapper节点。mapper节点配置有3个属性:resource,url,class。他们处理的优先级依次是resource,url,class,3个属性只处理一种。resource,url属性映射的是xml的路径,class是mapper接口的类路径。

从源码中我们看到,通过读取resource或url属性得到xml的访问路径后,交给XMLMapperBuilder对象来解析。

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//将Mapper标签里面的resource标签取出来解析,resource标签配的是Mapper.xml配置文件
mapperParser.parse();

我们查看XMLMapperBuilder的parse方法:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));    //从mapper节点开始解析
    configuration.addLoadedResource(resource);  //标记已经加载了次xml资源
    bindMapperForNamespace();  //绑定到命名空间
  }

  parsePendingResultMaps();  //将resultMap映射信息转换成ResultMap对象
  parsePendingChacheRefs();  //将cache映射信息转换成Cache对象
  parsePendingStatements();  //将sql映射转换成MappedStatement
}

//解析xml
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace.equals("")) {
     throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }
}
//绑定到命名空间
private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}

其中configurationElement(XNode context)负责解析所有的xml元素,bindMapperForNamespace() 绑定到命名空间,而parsePendingResultMaps(), parsePendingChacheRefs(), parsePendingStatements()则分别将对应的xml信息转换成ResultMap对象,Cache对象和MappedStatement对象。

ResultMap的处理

private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();  //实际处理的是ResultMapResolver.resolve()方法
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

看代码得知,实际处理的是ResultMapResolver.resolve()方法

package org.apache.ibatis.builder;

import java.util.List;

import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;

/**
 * @author Eduardo Macarron
 */
public class ResultMapResolver {
  private final MapperBuilderAssistant assistant;
  private String id;
  private Class<?> type;
  private String extend;
  private Discriminator discriminator;
  private List<ResultMapping> resultMappings;
  private Boolean autoMapping;

  public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
    this.assistant = assistant;
    this.id = id;
    this.type = type;
    this.extend = extend;
    this.discriminator = discriminator;
    this.resultMappings = resultMappings;
    this.autoMapping = autoMapping;
  }

  public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }

}

接着发现最终调用的是MapperBuilderAssistant.addResultMap 方法

public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);

    ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      resultMappings.addAll(extendedResultMappings);
    }
    resultMapBuilder.discriminator(discriminator);
    ResultMap resultMap = resultMapBuilder.build();
    configuration.addResultMap(resultMap);  //添加到Configuration
    return resultMap;
  }

Cache的处理

private void parsePendingChacheRefs() {
      Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
      synchronized (incompleteCacheRefs) {
          Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
          while (iter.hasNext()) {
              try {
                  iter.next().resolveCacheRef();
                  iter.remove();
              } catch (IncompleteElementException e) {
                  // Cache ref is still missing a resource...
              }
          }
      }
  }

调用的是org.apache.ibatis.builder.CacheRefResolver.resolveCacheRef()方法:

public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
}

最终调用org.apache.ibatis.builder.useCacheRef(String namespace)方法创建Cache对象并添加到Configuration:

public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace);
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }

MappedStatement的处理从parsePendingStatements()方法开始跟踪:

private void parsePendingStatements() {
      Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
      synchronized (incompleteStatements) {
          Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
          while (iter.hasNext()) {
              try {
                  iter.next().parseStatementNode(); //调用XMLStatementBuilder的parseStatementNode方法
                  iter.remove();
              } catch (IncompleteElementException e) {
                  // Statement is still missing a resource...
              }
          }
      }
  }

然后调用org.apache.ibatis.builder.xml.XMLStatementBuilder的parseStatementNode方法

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    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 Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

最后MappedStatement由org.apache.ibatis.builder.MapperBuilderAssistant的addMappedStatement方法创建,并加入到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);
    statementBuilder.resource(resource);
    statementBuilder.fetchSize(fetchSize);
    statementBuilder.statementType(statementType);
    statementBuilder.keyGenerator(keyGenerator);
    statementBuilder.keyProperty(keyProperty);
    statementBuilder.keyColumn(keyColumn);
    statementBuilder.databaseId(databaseId);
    statementBuilder.lang(lang);
    statementBuilder.resultOrdered(resultOrdered);
    statementBuilder.resulSets(resultSets);
    setStatementTimeout(timeout, statementBuilder);

    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

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

 

 

 

本文转载:https://www.cnblogs.com/zsg88/p/7538909.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值