20-Mybatis 核心流程01-初始化阶段

核心流程-初始化阶段

一、核心流程-初始化阶段

  • Mybatis的核心流程三大阶段是:初始化–>动态代理–>数据读写阶段,本文主要分析初始化阶段。初始化阶段主要是完成XML配置文件和注解配置信息的读取,创建全局单例的Configuration配置对象,完成各部分的初始化工作,具体的创建过程需要三个核心类来完成解析。

二、核心类

2.1 创建Configuration的三个核心类

  • 创建Configuration的核心类和作用如下
类名作用
XmlConfigBuilder加载解析主配置文件
XmlMapperBuilder加载解析mapper映射文件中非SQL节点部分
XmlStatementBuilder加载解析mapper映射文件中SQL节点部分
  • 这三个类都比较复杂,使用到了建造者模式,关于建造者模式可以参考:01-创建型模式(上)
  • 上面三个类共同完成全局配置文件的加载解析,来构建Configuration配置对象。这三个类看起来并没有使用到建造者模式的流式风格,但是借鉴了建造者模式的思想,在CacheBuilder里面的build方法是典型的建造者模式加载核心配置文件来创建全局的配置对象。

2.2 其他核心类

类名作用
Configuration单例对象,存在于程序的整个生命周期,包含所有的配置信息(初始化核心就是创建该对象)
MapperRegistrymapper接口动态代理的注册中心,注册的就是Java接口
MapperProxy动态代理类,实现了InvocationHandler接口,用于为接口生成动态代理实例,实例就是MapperProxy类型的。
MapperProxyFactory用于生成MapperProxy实例
ResultMap解析映射配置文件中的resultMap节点,内部包含一个ResultMapping类型的List,里面就封装了id,result等子元素
MappedStatement存储映射文件中的insert
SqlSource映射文件中的SQL语句会解析成SqlSource对象,解析sqlSource后得到的sql语句只包含占位符,可以直接交给DB执行

三、Configuration源码解析

3.1 Configuration属性

/**
 * @author Clinton Begin
 * Mybatis的核心配置文件
 * 1.包含Mybatis全部的配置信息
 * 2.全局单例的
 * 3.生命周期贯穿整个Mybatis的生命周期,应用级生命周期
 */
public class Configuration {

    //环境信息
    protected Environment environment;
    //是否启用行内嵌套语句
    protected boolean safeRowBoundsEnabled = false;
    protected boolean safeResultHandlerEnabled = true;
    //是否启用下划线转驼峰命名的属性
    protected boolean mapUnderscoreToCamelCase = false;
    //延迟加载
    protected boolean aggressiveLazyLoading = true;
    //是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true 
    protected boolean multipleResultSetsEnabled = true;
    //允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false
    protected boolean useGeneratedKeys = false;
    protected boolean useColumnLabel = true;
    //是否开启2级缓存
    protected boolean cacheEnabled = true;
    protected boolean callSettersOnNulls = false;
    protected boolean useActualParamName = true;

    //日志打印所有的前缀 
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    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 Integer defaultFetchSize;
    //执行类型,有simple、resue及batch
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    //指定 MyBatis 应如何自动映射列到字段或属性
    protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

    protected Properties variables = new Properties();
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    //MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    //延迟加载的全局开关
    protected boolean lazyLoadingEnabled = false;
    //指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具
    protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

    protected String databaseId;
    /**
     * Configuration factory class.
     * Used to create Configuration for loading deserialized unread properties.
     *
     * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
     */
    //插件集合
    protected Class<?> configurationFactory;

    //mapper接口的动态代理注册中心
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    //TypeHandler注册中心
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    //TypeAlias别名注册中心
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    //mapper文件中增删改查操作的注册中心
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    //mapper文件中配置cache节点的 二级缓存
    protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
    //mapper文件中配置的所有resultMap对象  key为命名空间+ID
    protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
    protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
    //mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID
    protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

    //加载到的所有*mapper.xml文件
    protected final Set<String> loadedResources = new HashSet<String>();
    //mapper文件中配置的sql元素,key为命名空间+ID
    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>();
    protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
    protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();


    public Configuration(Environment environment) {
        this();
        this.environment = environment;
    }

    //注册默认的别名
    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);
    }

    //相关属性的get/set方法,和其他方法,省略
}

四、解析对应图

image

  • 上图展示了配置文件和Configuration对象之间的对应关系,每一个配置都会映射到Configuration对象内部的属性。

五、初始化分析

5.1 XMLConfigBuilder

  • 示例如下,初始化过程在代码的角度看,只有SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) 这一句代码,我们从这一句代码分析其背后初始化所走过的流程。
    public void test() throws IOException {
        String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1.读取mybatis配置文件创SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2.从SqlSessionFactory获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应mapper
        PlayerDao mapper = sqlSession.getMapper(PlayerDao.class);
        // 4.执行查询语句并返回结果
        Player player = mapper.findPlayerById(1);
        System.out.println(player);
        inputStream.close();
    }
5.1.1 入口方法build
  • SqlSessionFactoryBuilder#build(java.io.InputStream) // new SqlSessionFactoryBuilder().build(inputStream) ,最后调用的方法是:SqlSessionFactoryBuilder#buildbuild(InputStream inputStream, String environment, Properties properties),
//XMLConfigBuilder#parse方法是配置解析的主要方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //1.构建XMLConfigBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //2.建造者模式,XMLConfigBuilder对象的parse方法就可以构造Configuration对象,屏蔽了所有实现细节,并且将返回的Configuration对象作为参数构
      //造SqlSessionFactory对象(SqlSessionFactory的默认实现类DefaultSqlSessionFactory),DefaultSqlSessionFactory拿到了配置对象之后,就具备生产SqlSession的能力了
      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.
      }
    }
  }

  //DefaultSqlSessionFactory是SqlSessionFactory的默认实现
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
5.1.2 XMLConfigBuilder#parse
  • XMLConfigBuilder#parse方法是配置解析的核心方法
public Configuration parse() {
    //1.判断是否已经解析过,不重复解析
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //2.读取主配置文件的configuration节点下面的配置信息,parseConfiguration方法完成解析的流程
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
5.1.3 XMLConfigBuilder#parseConfiguration
  • XMLConfigBuilder#parseConfiguration方法完成全部的配置解析主流程
/**
   * 解析核心配置文件的关键方法,
   * 读取节点的信息,并通过对应的方法去解析配置,解析到的配置全部会放在configuration里面
   * */
private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      loadCustomVfs(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //将settings填充到configuration
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>节点,里面会使用XMLMapperBuilder
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  • 我们看到这个方法的解析流程都很类似,最后面引入了XMLMapperBuilder解析节点,我们在前面挑一个解析typeAliases的看看细节,其他的就不一一分析了,然后进入5.2小节XMLMapperBuilder解析阶段的分析
5.1.4 XMLConfigBuilder#typeAliasesElement
  • typeAliasesElement解析别名节点,typeAliases有两种配置方式,如下所示:
    //配置示例,整个包配置或者配置单个类
    <typeAliases>
        <package name="com.intellif.mozping.entity"/>
        <typeAlias alias="Product" type="com.intellif.mozping.entity.Product"/>
    </typeAliases>
  • XMLConfigBuilder#typeAliasesElement
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      //1.非空才会处理,依次遍历所有节点
      for (XNode child : parent.getChildren()) {
        //2.处理package类型配置
        if ("package".equals(child.getName())) {
          //2.1获取包名
          String typeAliasPackage = child.getStringAttribute("name");
          //2.2注册包名,将包名放到typeAliasRegistry里面,里面拿到包名之后还会进一步处理
          //最后会放到TypeAliasRegistry.TYPE_ALIASES这个Map里面去
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          //3.处理typeAlias类型配置
          //3.1获取别名
          String alias = child.getStringAttribute("alias");
          //3.2获取类名
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            //3.3下面的注册逻辑其实比前面注册包名要简单,注册包名要依次处理包下的类,也会调用registerAlias方法,
            //这里直接处理类,别名没有配置也没关系,里面会生成一个getSimpleName或者根据Alias注解去取别名
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            //4.其他类型直接报错
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

5.2 XMLMapperBuilder

  • 最前面我们说过XMLMapperBuilder是用于解析Mapper.xml映射文件的,因此在前面的XMLConfigBuilder#parseConfiguration方法的解析流程最后一个步骤是mapperElement,该方法内部XMLMapperBuilder就会登场进行mapper.xml映射文件的解析工作,我们先看看这个方法:
5.2.1 XMLConfigBuilder#mapperElement
  • XMLConfigBuilder#mapperElement方法解析节点,这个方法包含解析mapper节点的主流程,但是解析的细节还看不到,解析的细节在后面的XMLMapperBuilder#parse里面,我们先通过这个方法看一下解析的主流程,mappers有多种配置方式,如下所示:
    <mappers>
        <!--直接映射到相应的mapper文件 -->
        <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
        <mapper url="xx"/>
        <mapper class="yy"/>
        <package name="com.intellif.mozping"/>
    </mappers>
  • XMLConfigBuilder#mapperElement
/**
   * 解析配置文件的mappers子节点,方法主要是实现一个大体框架,按照resource->url->class的优先级读取配置,具体的解
   * 析细节是依赖于XMLMapperBuilder来实现的,XMLMapperBuilder通过parse方法屏蔽了细节,内部完成解析过程
   * */
  private void mapperElement(XNode parent) throws Exception {
    //
    if (parent != null) {
        //1.节点非空,遍历子节点逐个处理,因为mapperElement(root.evalNode("mappers"))解析的mappers里面可能有多个标签
      for (XNode child : parent.getChildren()) {
          //1.1 处理package类型的配置
        if ("package".equals(child.getName())) {
          //1.2 按照包来添加,扫包之后默认会在包下找与java接口名称相同的mapper映射文件,name就是包名,
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //1.3 一个一个Mapper.xml文件的添加 , resource、url和class三者是互斥的,resource优先级最高
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            //1.4 按照resource属性实例化XMLMapperBuilder来解析xml配置文件
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //1.5解析配置,因为XMLMapperBuilder继承了BaseBuilder,BaseBuilder内部持有Configuration对象,因此
            //XMLMapperBuilder解析之后直接把配置设置到Configuration对象
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //1.6 按照url属性实例化XMLMapperBuilder来解析xml配置文件
            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) {
            //1.7 按照class属性实例化XMLMapperBuilder来解析xml配置文件
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            //resource、url和class三者是互斥的,配置了多个或者不配置都抛出异常
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  • 结合代码注释和配置文件的结构,流程还是比较清晰的,这里面会将mappers里面的节点信息全部注册到Configuration所持有的MapperRegistry对象里面去,2.2说过MapperRegistry是mapper接口动态代理的注册中心,总而言之Configuration里包含所有的配置信息,最后一种情况使用的是configuration.addMapper(mapperInterface);方法,其实前面几种情况最后也是走的这个方法,这里我们看到了主体流程,5.2.1我们开始看解析的具体过程分析。
5.2.2 XMLMapperBuilder#parse
  • XMLMapperBuilder#parse进入了XMLMapperBuilder解析mapper节点的详细流程,最后把mapper节点信息保存到Configuration的MapperRegistry里面去,并且会对Mapper.xml映射文件的内存进行解析,映射文件的内容是非常复杂的,我们慢慢跟进看
public void parse() {
        //1.首先判断是否已经加载过了,没有加载才继续加载(loadedResources是一个set集合,保存了已经加载的映射文件,如果一个配置在mappers里面写了2次,那么第二次就不加载了)
        if (!configuration.isResourceLoaded(resource)) {
            //2.处理mapper子节点
            //这里使用XPathParser来解析xml文件,XPathParser在XMLMapperBuilder构造方法执行的时候就已经初始化好了,已经将
            //UserMapper.xml读取转换为一个document对象了
            configurationElement(parser.evalNode("/mapper"));
            //3.将解析过的文件添加到已经解析过的set集合里面
            configuration.addLoadedResource(resource);
            //4.注册mapper接口
            bindMapperForNamespace();
        }

        //5.处理解析失败的节点
        //把加载失败的节点重新加载一遍,因为这些节点可能在之前解析失败了,比如他们继承的节点还未加载导致,
        //因此这里把失败的部分再加载一次,之前加载失败的节点会放在一个map里面
        //6.处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //7.处理解析失败的CacheRef节点
        parsePendingChacheRefs();
        //8.处理解析失败的Sql语句节点
        parsePendingStatements();
    }
5.2.3 XMLMapperBuilder#configurationElement
  • configurationElement方法是解析mapper映射文件的主流程,我们可以看到每种类型节点的解析过程,注意第八步是解析sql信息,我们前面说过,XMLMapperBuilder解析mapper文件但是不解析sql节点,sql节点是由XmlStatementBuilder来负责的,后面我们会看到XmlStatementBuilder的作用
    /**
     * 解析mapper.xml映射文件的主流程
     */
    private void configurationElement(XNode context) {
        try {
            //1.获取namespace属性(对应java接口的全路径名称);不能为空
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            //2.把namespace属性交给建造助手builderAssistant
            builderAssistant.setCurrentNamespace(namespace);
            //3.解析cache-ref节点
            cacheRefElement(context.evalNode("cache-ref"));
            //4.重点:解析cache节点,和缓存相关
            cacheElement(context.evalNode("cache"));
            //5.解析parameterMap节点(已废弃)
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //6.重点:解析resultMap节点
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            //7.重点:解析sql节点
            sqlElement(context.evalNodes("/mapper/sql"));
            //8.重点:解析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);
        }
    }
5.2.4 XMLMapperBuilder#bindMapperForNamespace
  • bindMapperForNamespace负责将mapper映射文件对应的java接口类(这个接口类的名称就是mapper文件里面的namespace)注册到Configuration的MapperRegistry里面,这代表该接口已经注册,这也是5.2.1主流程的目的
    /**
     *注册mapper接口 
     */
    private void bindMapperForNamespace() {
        //1.获取命名空间,在bindMapperForNamespace前面的configurationElement方法将namespace已经set到builderAssistant了,这里直接取出
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                //1.1 通过命名空间获取mapper接口的class对象
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                //ignore, bound type is not required
            }

            if (boundType != null) {
                //1.2 是否已经注册过该mapper接口?
                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
                    //1.3如果没有注册,将命名空间添加至configuration.loadedResource集合中
                    configuration.addLoadedResource("namespace:" + namespace);
                    //1.4将mapper接口添加到mapper注册中心
                    configuration.addMapper(boundType);
                }
            }
        }
    }
5.2.5 XMLMapperBuilder#parsePendingResultMaps
    //处理之前加载失败的resultMap
    private void parsePendingResultMaps() {
        //1之前加载失败的resultMap会保存在这个集合中,这里需要做的就是遍历这个集合并处理
        Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
        synchronized (incompleteResultMaps) {
            Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
            while (iter.hasNext()) {
                try {
                    //2.遍历并且调用resolve处理即可,如果异常直接忽略,因为最后还会重试的
                    iter.next().resolve();
                    //3.处理完就从集合移除
                    iter.remove();
                } catch (IncompleteElementException e) {
                    // ResultMap is still missing a resource...
                }
            }
        }
    }
  • parsePendingChacheRefs(),parsePendingStatements()和parsePendingResultMaps方法是类似的,就不多解析了,这一步我们主要是梳理XMLMapperBuilder#parse方法,并对里面几个关键的方法做了大概的跟踪,但是底层的实现逻辑我们暂时不跟进,否则内容太多。

5.3 XmlStatementBuilder

  • 前面的XMLMapperBuilder我们跟了一部分源码,看到了XMLMapperBuilder解析mapper映射文件的主体流程,现在我们看XmlStatementBuilder是如何解析mapper中的SQL语句的,解析sql语句属于解析mapper文件的一部分,我们XMLMapperBuilder#configurationElement的
    最后一个流程方法buildStatementFromContext里面跟进去,就可以看到他的身影,我们跟进去看看主流程:
5.3.1 XMLStatementBuilder#parseStatementNode
  • parseStatementNode方法是解析sql语句的主流程方法

    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    
    //解析sql语句节点并注册到condifuration对象
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }
    

   private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            //1.由第三个火枪手XmlStatementBuilder来解析sql语句,并注册到configuration对象
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                //2.XMLStatementBuilder和XMLMapperBuilder一样都是继承自BaseBuilder,BaseBuilder持有Configuration对象,因此
                //直接解析,让后将配置信息set进去即可
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                //3.如果解析异常,就把对象添加到未完成的Map集合里面
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }
    
5.3.2 XMLStatementBuilder#parseStatementNode
  • XMLStatementBuilder#parseStatementNode是解析sql节点的核心方法,该方法是解析sql节点的主流程,包括四种sql节点,解析完毕之后会将sql语句信息封装之后,注册到Configuration对象里面的mappedStatements集合里面去,2.2说过MappedStatement存储了映射文件中的insert|delete|update|select节点的重要信息
    /**
     *解析sql节点的核心方法
     */
    public void parseStatementNode() {
        //1.获取sql节点的id
        String id = context.getStringAttribute("id");
        //2.获取databaseId
        String databaseId = context.getStringAttribute("databaseId");

        //不符合就返回
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
        //3.获取sql节点的各种属性
        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);

        //4.根据sql节点的名称获取操作的类型SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        //5.获取操作的各种配置,比如缓存,resultOrdered之类的
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        //6.flushCache默认值是!isSelect,如果是查询语句,没有配置flushCache就是false,不是查询语句,没有配置flushCache就是true
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

        // Include Fragments before parsing
        //7.在解析sql语句之前先解析<include>节点
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());

        // Parse selectKey after includes and remove them.
        //8.在解析sql语句之前,处理<selectKey>子节点,并在xml节点中删除
        processSelectKeyNodes(id, parameterTypeClass, langDriver);

        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        //9.解析sql语句是解析mapper.xml的核心,实例化sqlSource,使用sqlSource封装sql语句
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        //10.获取resultSets属性
        String resultSets = context.getStringAttribute("resultSets");
        //11.获取主键信息keyProperty
        String keyProperty = context.getStringAttribute("keyProperty");
        //12.获取主键信息keyColumn
        String keyColumn = context.getStringAttribute("keyColumn");

        //13.根据<selectKey>获取对应的SelectKeyGenerator的id
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

        //14.获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                    configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                    ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        }

        //15.通过MapperBuilderAssistant类型的builderAssistant实例化MappedStatement,并注册至configuration对象
        //相当于自身主要复杂属性的解析和读取,构造对象的过程交给助手来做
        //注意前面解析了mapper节点之后是注册到mapperRegistry,那是注册mapper节点信息,注册的实际上是对应的java接口
        //这里是注册到MappedStatement,注册的是sql语句信息,但是二者都是保存在Configuration对象里面的Map集合
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
5.3.3 MapperBuilderAssistant#addMappedStatement()
  • 在XMLStatementBuilder#parseStatementNode的最后一步,通过调用builderAssistant.addMappedStatement方法来完成MappedStatement对象实例化和注册到Configuration对象的mappedStatements集合,sql语句解析之后,对应到的java的数据结构类是MappedStatement,它是保存sql语句的数据结构。sql语句的节点的全部配置,都能够在MappedStatement中找到对应的属性。到此XmlConfigBuilder、XmlMapperBuilder和XmlStatementBuilder三者分工协作,依次完成了主配置文件、mapper映射文件(不含sql信息)和mapper文件sql节点信息配置的加载,最终都把这些信息放到了Configuration这个大配置对象里面去了,到此处虽然还有很多细节没有分析,但是基本上初始化的流程已经看得出个大概了。 下面是代码细节:
    /**
     *通过builderAssistant实例化MappedStatement,并注册至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) {

        //1.确保Cache-ref节点已经被解析过了
        if (unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        }

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

        //2.典型的建造者模式,创建MappedStatement对象
        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);
        }
        //3.build方法创建MappedStatement对象
        MappedStatement statement = statementBuilder.build();
        //4.添加MappedStatement对象到全局Configuration配置对象的对应Map中
        configuration.addMappedStatement(statement);
        return statement;
    }

六、补充

6.1 resultMap加载

  • 在5.2.3 XMLMapperBuilder#configurationElement解析mapper.xml映射文件的主流程的第6步是解析resultMap配置节点。resultMap的数据结构相对比较复杂,设计的映射关系,属性比较多,比如id,type,里面又可能有构造方法节点,id,result等。因此加载解析也比较复杂在,Configuration对象里面有一个resultMap类型的Map对象来保存全部的resultMap,key是namespace+id,保证全局唯一。解析后对应的数据结构,可以参考org.apache.ibatis.mapping.ResultMap这个类。属性展示如下,这个类代码不多,可以参考源码理解。
  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;
  • 典型的resultMap配置
 <resultMap id="BaseResultMap" type="Player">
        <!-- <constructor>
            可以指定构造方法
            <idArg column="id" javaType="int"/>
            <arg column="user_name" javaType="String"/>
        </constructor>  -->
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="playName" property="playName" jdbcType="VARCHAR"/>
        <result column="playNo" property="playNo" jdbcType="INTEGER"/>
        <result column="team" property="team" jdbcType="VARCHAR"/>
        <result column="height" property="height" jdbcType="VARCHAR"/>
    </resultMap>
  • 加载resuleMap的流程在XMLMapperBuilder#resultMapElement()方法内,代码如下,只有部分注释,有兴趣可以继续研究
//解析resultMap节点
    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        //1.获取id
        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
        //2.获取type
        String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
                resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
        //3.获取extends
        String extend = resultMapNode.getStringAttribute("extends");
        //4.获取autoMapping配置
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        //5.根据type解析到类型
        Class<?> typeClass = resolveClass(type);
        //6.discriminator配置默认为null
        Discriminator discriminator = null;
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        resultMappings.addAll(additionalResultMappings);
        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 {
                List<ResultFlag> flags = new ArrayList<ResultFlag>();
                if ("id".equals(resultChild.getName())) {
                    flags.add(ResultFlag.ID);
                }
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
            }
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
    }

6.2 Mybais的初始化流程图

image

6.3 小结

  • 更多的节点配置信息源码,有兴趣可以自行解读,代码量比较大,但是熟悉了Mybatis框架和配置之后,总体结构还是比较清晰的,阅读这些源码本身难度不是很大,有助于帮助我们了解Mybatis框架,关键还是理解里面不同模块运用的设计思想和设计模式。下一篇文章我们分析核心流程的第二阶段—动态代理阶段
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值