美文网首页
Mybatis源码分析(一)

Mybatis源码分析(一)

作者: 非典型_程序员 | 来源:发表于2019-06-16 15:49 被阅读0次

最近我又暂时到其他项目组支援去了,使用的是spring boot,ORM框架使用的jpa,自己以前学习过一点皮毛,所以上手开发还是没问题的,相对来讲空闲时间比较多。所以我觉得有必要好好看下Mybatis的源码,毕竟从实际开发情况来看,使用Mybatis依然是主流。这次学习目的不是去深究源码,而是理清spring下Mybatis到底是怎样的一个执行过程,以前也仅仅知道Mybatis使用的也是动态代理,其底层使用的是JDBC而已,具体的执行过程却不怎么清楚,所以今天就来看看Mybatis的真实面目。

一、Mybatis执行入口

在我们的项目中Mybatis主要有两个部分,一个是mapper接口类,一个是mapper.xml,为了方便表述,下文中用mapper代替mapper接口类,xml代替mapper.xml文件。这两个文件是通过xml文件的namespace进行映射的,也就是说通过xml的namespace可以唯一确定一个mapper。而通过xml文件中的select | update | delete | insert标签的id值也可以唯一确定一个mapper的方法。详细内容后面再说,我们先看项目启动时Mybatis做了那些工作。
spring boot启动过程中会有一个非常重要的配置,就是MybatisAutoConfiguration,这个类是mybatis自动配置,也就是说这个类相当与是自动配置的和@EnableAutoConfiguration一样,不过它是针对mybatis的。根据spring boot的启动过程,MybatisAutoConfiguration会在容器刷新时执行构造方法,下面是部分代码:

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {

  private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  private final MybatisProperties properties;
  private final Interceptor[] interceptors;
  private final ResourceLoader resourceLoader;
  private final DatabaseIdProvider databaseIdProvider;
  private final List<ConfigurationCustomizer> configurationCustomizers;
  public MybatisAutoConfiguration(MybatisProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,
                                  ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                  ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }
...省略代码
}

这个类上的注解还是很多的,我们一一看下。@ConditionalOnClass,表示的是classpath下必须有制定的类文件才会执行,即必须有SqlSessionFactory、SqlSessionFactoryBean这两类;@ConditionalOnBean表示的是必须在制定bean存在的情况下才会执行,即DataSource必须作为bean注入到spring容器中之后;@EnableConfigurationProperties表示允许指定的类自动注入spring中,即一种更方便的形式注入MybatisProperties,不需要显性使用@Bean注解即可注入;@AutoConfigureAfter表示必须在指定的配置完成配置后执行,也就是必须在完成DataSourceAutoConfiguration后才执行MybatisAutoConfiguration。
我们看下MybatisAutoConfiguration构造函数,因为作为bean肯定会执行它的构造函数(其实在这之前它的setBeanFactory和setResourceLoader方法会先执),这里主要会完成其成员变量的初始化:构造参数中MybatisProperties表示是mybatis的一些配置,比如xml和mapper位置,类的别名、类型处理器等等,可以看做是mybatis的配置属性类;ResourceLoader其实质是spring boot的上下文对象,是AnnotationConfigServletWebServerApplicationContext;interceptorsProvider、databaseIdProvider、configurationCustomizersProvider类型实质上都是DefaultListableBeanFactory。


二、创建SqlSessionFactory的bean

接下来会执行@PostConstruct注解的checkConfigFileExists,主要是判断是否需要检查xml,即检查xml是否存在,一般情况下我们不会专门设置这个属性值(默认是false,不检查)。接着会执行MybatisAutoConfiguration类一个很重要的方法sqlSessionFactory,即SqlSessionFactory的构造,我们看下具体方法:

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    ......
    return factory.getObject();
  }

这个方法有@ConditionalOnMissingBean注解,即在容器中不存在SqlSessionFactory的时候才会执行,这个方法中首先创建一个SqlSessionFactoryBean对象,然后填充其相关属性如:dataSource、typeAliasesPackage、typeHandlersPackage、mapperLocations等,然后调用SqlSessionFactoryBean的getObject方法,见下一步。

1

getObject方法会判断SqlSessionFactoryBean的成员变量sqlSessionFactory(SqlSessionFactory对象)是否为null,如果不为null,将其返回;否则执行afterPropertiesSet方法,也就是说sqlSessionFactory创建其实是在afterPropertiesSet中完成的,如下:

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

这个方法中主要执行的是buildSqlSessionFactory方法,也就是说这个方法会创建SqlSessionFactory实例,因为代码比较多,所以我删除了一部分代码,保留了部分重要代码,如下:

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    ...省略部分代码
    Configuration configuration;
    XMLConfigBuilder xmlConfigBuilder = null;

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    ..省略部分代码
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    ..省略部分代码
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    } else {
        ....省略代码
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

这个方法内容比较多,简单介绍一下,省略的代码主要是设置或者添加Configuration的一些属性值。另外就是注册一些类型别名、类型处理器等注册到Configuration的对于注册容器中,比如TypeHandlerRegistry、TypeAliasRegistry等;另外就是判断是否有事物管理器工厂,如果没有则会创建一个SpringManagedTransactionFactory。然后以environment、transactionFactory、dataSource为构造参数,创建Environment,并设置到Configuration上。可以说前面这部分代码主要还是对Configuration成员变量的一些赋值,或者说填充数据,因为下面要用到Configuration。
接下来会遍历所有的xml文件,每遍历一个xml文件都会创建一个对应的XMLMapperBuilder对象。XMLMapperBuilder以xml文件的InputStream、configuration、xml文件的具体路径、连同configuration的sqlFragments作为构造参数,创建XMLMapperBuilder对象。
看下图XMLMapperBuilder的构造函数:


图-1.png

首先执行的是上图中构造方法1,这里面会先以xml的InputStream、configuration的variables变量(是一个Properties对象)、XMLMapperEntityResolver对象等为参数构造创建XPathParser对象。
XPathParser构造函数中先执行commonConstructor方法,完成其对象成员变量初始化,接着执行createDocument方法将xml的InputStream转换成转化成一个Document对象,并将这个Document赋值给XPathParser的成员变量document。然后XPathParser作为参数执行构造方法2。
构造方法2中以configuration为参数执行XMLMapperBuilder的父类构造方法,即BaseBuilder的构造方法,完成BaseBuilder成员变量初始化。接着以configuration、xml文件的具体路径为构造函数创建MapperBuilderAssistant对象,然后将MapperBuilderAssistant对象赋值给XMLMapperBuilder的成员变量builderAssistant。并将XPathParser对象、sqlFragments、resource参数赋值给XMLMapperBuilder的相应的成员变量,完成XMLMapperBuilder成员变量初始化,XMLMapperBuilder初始化结束,执行其parse方法,见下一步。

2

执行XMLMapperBuilder的parse方法。这个方法是核心代码,看下代码:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

这段代码行数并不多,但是这里面又涉及到了多个方法,我们一行一行分析。
if条件判断的是configuration是否已经加载了当前的xml文件,这里resource是具体的xml路径。如果结果为true,就执行执行2.1,否则执行2.2。

2.1

这里面代码只有3行,第二行configuration.addLoadedResource(resource);
,这行代码就是将当前的xml文件加到configuration的一个属性集合中,表示该xml文件已经读取过了,不需要再次读取,重点是第一行和第三行。

configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
2.1.1

先看第一行代码中的parser.evalNode("/mapper")方法,这个执行的是XPathParser的evalNode方法,这个方法其实就是将XPathParser持有的Document对象(xml文件)转成一个XNode对象,这个对象包含了xml文件的的所有内容,可以理解为XNode是一个只有"mapper"标签之间内容的对象如下:

<mapper namespace="com.ypc.mybatis.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.ypc.mybatis.model.User">
        <result column="id" jdbcType="INTEGER" property="id"/>
        <result column="userName" jdbcType="VARCHAR" property="userName"/>
        <result column="age" jdbcType="INTEGER" property="age"/>
        <result column="sex" jdbcType="VARCHAR" property="sex"/>
    </resultMap>
    <sql id="BaseColumnList">
        id, userName, age, sex
    </sql>
    <!-- 根据主键查询-->
    <select id="selectByUserId" parameterType="int" resultType="com.ypc.mybatis.model.User">
        select
        <include refid="BaseColumnList"></include>
        from t_user where id = #{id}
    </select>
    <!--查询所有用户-->
    <select id="findAll" resultType="com.ypc.mybatis.model.User">
        select
        <include refid="BaseColumnList"></include>
        from t_user
    </select>
</mapper>
2.1.2

接下来configurationElement方法本身,上面我们知道了configurationElement方法参数是一个XNode,即一个xml的内容。下面具体的看下这个方法的代码,如下:

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || 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. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

这个方法会将XNode(xml文件)进行解析,获取到xml中所有相关属性值。context.getStringAttribute("namespace")获取xml的namespace,并赋值给MapperBuilderAssistant对象;然后是"cache-ref"和"cache",接着是配置的"parameterMap"、"resultMap"、"sql"、"select|insert|update|delete"等,说白了就是将xml的内容全部的读取出来,并进行相应的处理,比如赋值给MapperBuilderAssistant对象,具体的方法不再继续深究,因为内容实在是太多了,我们主要看下buildStatementFromContext,因为这个方法处理的xml中的sql,见下一步。

2.1.2-1

buildStatementFromContext方法,其实根据名称和参数就可以猜到它主要构建sql的。context.evalNodes方法会将xml中的每一条sql语句都转成一个XNode对象,即select、insert、update、delete这些标签。看下面的方法代码:

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

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

这段代码中会创建一个XMLStatementBuilder对象,然后执行其parseStatementNode方法,其实核心就是XMLStatementBuilder的parseStatementNode方法,继续向下看:

public void parseStatementNode() {
    .....
    LanguageDriver langDriver = getLanguageDriver(lang);
    ......
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    KeyGenerator keyGenerator;
    ......
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

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

这个方法代码比较多,上面省略了很多,留下了一点主要的代码。
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
如果不特意指定的话,langDriver实际上是一个XMLLanguageDriver对象,方法具体内容看2.1.1。
另外会判断当前的sql标签是否使用了"keyGenerator"属性,且sql是不是"insert"类型,最终返回相应的keyGenerator对象,Jdbc3KeyGenerator.INSTANCE或者 NoKeyGenerator.INSTANCE。然后将这条sql的所有参数信息添加到MapperBuilderAssistant中。

2.1.2-2

我们主要看一下createSqlSource方法。在XMLLanguageDriver的createSqlSource方法会创建一个XMLScriptBuilder对象,然后执行XMLScriptBuilder对象的parseScriptNode方法,代码如下:

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

  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

parseScriptNode方法内会先执行parseDynamicTags方法,这个方法的主要作用就是完整的解析当前的sql片段,即把select、delete、insert、update这些sql标签的内容完整的读取出来。这个方法使用context(一个XNode对象)作为参数,读取里面的所有string和子标签内容,并将读取到的没一个sql片段变成SqlNode对象,添加到contents中,最后以contentx作为构造函数,创建一个MixedSqlNode对象并返回。比如现在这个XNode对象的id是selectByUserId,xml如下:

    <select id="selectByUserId" parameterType="int" resultType="com.ypc.mybatis.model.User">
        select
        <include refid="BaseColumnList"></include>
        from t_user where id = #{id}
    </select>

注意一点的是这个XNode并不是整个<select>标签中的内容,它这里是一层层遍历的。通过debug模式就可以看出具体的XNode值以及最后转译的结果,如下图:


图-2.png

根据上图并结合着上面的xml文件应该更直观的了解到这个方法到底做了哪些工作,其实说白了这个方法才是是真正的将xml的sql标签转成相应的string。接着根据isDynamic属性值,执行不同类型SqlSource的构造函数。看下面的代码:

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }

RawSqlSource构造函数中先执行getSql方法,这个方法将遍历SqlNode对象的contents,将contents的sql片段拼接成一条完整的sql语句,这时候sql语句变成了

select  id,username,age,sex from t_user where id = #{id}

接着会在构造函数中创建一个SqlSourceBuilder对象,并通过它的parse方法创造需要的SqlSource,代码如下:

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

这个方法会将上面的sql转成如下形式的JDBC的sql语句:

select  id,username,age,sex from t_user where id = ?

并且这个方法中会根据sql的参数类型、configuration和一个额外参数类型集合创建一个ParameterMappingTokenHandler对象,这个对象可以获取到所有相关的参数。
方法最后创建一个StaticSqlSource对象,该对象包含了上面的sql语句、Configuration和一个List<ParameterMapping>,即当前这条sql参数的List。最后返回该对象。到这里buildStatementFromContext算是执行完了,相应的configurationElement也就执行结束。

2.1.3

接着我们看bindMapperForNamespace方法。这个方法根据名称应该猜到它的作用就是根据xml的"namespace"和相应的mapper进行绑定,看下面的代码:

  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);
        }
      }
    }
  }

首先从MapperBuilderAssistant获取到当前"namespace"的值,然后获取到对应的mapper类对象。如果mapper对象没有注册到configuration的mapperRegistry(MapperRegistry对象)属性中,则将其注册到mapperRegistry,为防止spring重复加载,将其添加到已加载的集合中。我们着重看下 configuration.addMapper(boundType)方法,代码如下:

//Configuration类
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
// MapperRegistry类
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(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 {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

主要是MapperRegistry的addMapper方法,这个方法先判断是不是接口类型,然后判断该接口类对象是否已经注册到MapperRegistry,即是否添加到knownMappers中。没有则以该接口类对象为key,新建的MapperProxyFactory对象(接口类对象为构造参数)为value,添加到knownMappers集合中。接着创建MapperAnnotationBuilder对象,并执行其parse方法,详情看下面3.1部分。

2.1.3-1

MapperAnnotationBuilder根据名称应该是和mapper接口注解的相关的,代码如下:

  public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace('.', '/') + ".java (best guess)";
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;

    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);

    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
  }

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

构造方法就不再讲了,主要就是一些初始化的操作,添加一些常用的sql注解。如果configuration还没有加载过resource则执行里面的方法,这个需要注意一点,这时候resource的值是"interface com.ypc.mybatis.mapper.UserMapper",因为type是一个类对象。接着执行loadXmlResource方法,这个方法主要是根据接口类名称判断configuration是否加载过相应的mapper,从而决定要不要去加载相应的xml,因为前面已经添加过了,所以不再需要重新加载xml文件,这个方法其实就是上面2中的讲过的XMLMapperBuilder的parse方法,感兴趣的话可以具体看相应的代码。
接着会获取到接口类的所有public方法,接着对方法数字进行遍历,如果当前遍历的方法不是一个bridge方法,执行parseStatement方法。

2.1.3-2

这个方法其实和2.1中的parseStatementNode比较类似,只不过这个方法操作对象是mapper上的sql注解。因为我代码上没有添加相关sql注解,这里就不在细说了,感兴趣的可以自己好好看下。

2.1.3-3

parse中最后会执行parsePendingMethods方法,这个方法是解析尚未完成的方法,即先从configuration中获取到尚未执行过MapperAnnotationBuilder的parse的方法集合,然后对该集合遍历,对每个方法执行解析,如上面的一样。到这里MapperRegistry的addMapper执行完成,bindMapperForNamespace也就完成了。
bindMapperForNamespace和configurationElement比较相似,区别之处在于bindMapperForNamespace解析的是mapper,而configurationElement解析的是xml,作用我觉得是相同的那就是解析sql。


2.2

代码如下,这部分代码和一部分中的parsePendingMethods方法类似,只是解析对象不同而已,都是从configuration获取相关的集合进行遍历,判断是否需要解析。

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();

执行完一部分和二部分的代码,XMLMapperBuilder的parse方法执行完毕。接着需要执行的是SqlSessionFactoryBuilder的build方法:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

Configuration作为构造参数创建DefaultSqlSessionFactory对象,并返回,至此完成了MybatisAutoConfiguration中sqlSessionFactory方法,即完成了sqlSessionFactory的bean创建完成。


三、结语

本来还想继续往下看的,但是无奈篇幅太长了,而且整体写的内容感觉也很乱,因为方法一层层的,看着还是很累,感觉看源码的方法有问题,看了这么多才只是看到SqlSessionFactory-bean的创建。而后面的内容还有很多,只能下次继续了。SqlSessionFactory-bean的创建过程中做的工作还是非常多的,总结起来我觉得无非有2点:
1、解析xml,解析整个xml文件,主要是sql的解析,将xml中的sql转成JDBC类型sql。
2、解析mapper,主要是解析mapper接口上的sql注解,另外就是创建接口类的MapperProxyFactory对象。
当然实际过程并不像总结的这么简单,实际涉及的点还是很多的,包括很多我都没有深究的地方,不过对整个流程应该是比较清楚了。MybatisAutoConfiguration中还有一个bean,那就是sqlSessionTemplate,这部分代码我还没有仔细去看,下周有时间的话会专门的看这部分代码,最后是项目启动完成后,mapper代理类的执行过程。今天就到这里,有什么疑问欢迎大家相互交流。

相关文章

网友评论

      本文标题:Mybatis源码分析(一)

      本文链接:https://www.haomeiwen.com/subject/ccrmfctx.html