Mybatis-SpringBoot源码解析之一:SqlSessionFactory加载流程

Mybatis-SpringBoot源码解析之一:SqlSessionFactory加载流程


1、入口MybatisAutoConfiguration自动配置类

Mybatis同样通过自动配置的方式进行作为入口,将自动配置类本身以及内部的@Bean加载到beanDefinitionMap中,在后续创建单例的bean进行具体的解析创建真正的对象。

所以这儿会将MybatisAutoConfiguration、以及内部的SqlSessionFactorySqlSessionTemplate封装成BeanDefinition放到beanDefinitionMap中。

MybatisAutoConfiguration重要代码如下所示:

public class MybatisAutoConfiguration implements InitializingBean {
    1、构造方法
        public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        2MybatisProperties属性,也就是读取配置文件的属性
           类上面用到的@EnableConfigurationProperties(MybatisProperties.class)进行读取
        this.properties = properties;
        3、如果配置了的话获取拦截器
        this.interceptors = interceptorsProvider.getIfAvailable();
        4、获取类型处理器
        this.typeHandlers = typeHandlersProvider.getIfAvailable();
        this.languageDrivers = languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }
    
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
     ······省略
  }
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	······省略
  }
    
}

2、SqlSessionFactory流程sqlSessionFactory方法

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    1、 创建一个新的sql会话工厂
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    2、获取Configuration 对象,Configuration 这个对象非常重要,贯穿了整个mybatis的流程
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    2.1、在配置文件中获取 Configuration 对象 如果 获取不到 创建一个新的 Configuration 对象 (设置常用别名)
    applyConfiguration(factory);
    3、获取配置文件中configurationProperties属性
    if (this.properties.getConfigurationProperties() != null) {
        factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    4、如果当前拦截器不为空,将拦截器写入工厂中
    if (!ObjectUtils.isEmpty(this.interceptors)) {
        factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
        factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    5、配置文件中获取的实体类包名
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
        factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    6、配置文件中获取的实体类型
    if (this.properties.getTypeAliasesSuperType() != null) {
        factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    7、配置文件中类型处理器包
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    8、设置类型处理器
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        factory.setTypeHandlers(this.typeHandlers);
    }
    9、获取所有的mapper.xml文件设置到数组中
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
        // Need to mybatis-spring 2.0.2+
        factory.setScriptingLanguageDrivers(this.languageDrivers);
        if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
            defaultLanguageDriver = this.languageDrivers[0].getClass();
        }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
        // Need to mybatis-spring 2.0.2+
        factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
	10、最终调用
    return factory.getObject();
}

流程较长,但是还是比较简单,就是设置一系列属性到工厂中,比较常见的就是mapperLocations以及typeAliasesPackage分别是mapper.xml资源以及实体包,最后调用getObject方法获取最终的sqlSessionFactory对象,
这儿可以先提前说一下最终返回的是DefaultSqlSessionFactory,有一个比较重要的属性是Configuration,后续会将所有的参数属性注入到该类中去。

3、getObject方法

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      1、这个方法应该比较眼熟,spring初始化的方法,在这儿主动调用了。
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
      前面就是一堆校验,省略了只看重点
    1、构建sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

4、buildSqlSessionFactory构建工厂

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    1、因为前面已经创建这儿configuration不为null
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        1.1、设置配置文件中的variables属性是一个Map
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      1.2、根据本地的configLocation创建targetConfiguration
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }
	2、到这儿肯定会得到一个Configuration对象,后续Configuration发挥着巨大最作用。
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
	3、获取实体包,将实体包下面的实体设置到typeAliasRegistry属性上
    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }
	3.1、同上,也是将实体设置到typeAliasRegistry属性上
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
	4、设置插件
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
	5、设置类型处理器,其实就是java类型与数据库类型的对应,mybatis默认会设置很多
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));
	6、重点:在这儿解析所有的mapper.xml文件
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
          6.1、遍历mapper.xml文件进行解析
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
             6.2、创建xml的构造器
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
	7、构建DefaultSqlSessionFactory工厂,将Configuration属性封装进去
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

设置一系列属性到Configuration中,后续会将该配置类注入到sqlSessioin工厂中。

这儿比较重要的就是解析所有的mapper.xml。

5、parse解析

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      1、解析mapper标签以及内部的标签
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      2、构建mapper与dao的关系
      bindMapperForNamespace();
    }
	3、下面就是对上述解析失败的再次进行尝试解析
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

1、解析mapper标签以及内部的标签,见 第6章,这儿得到一个重要的对象MappedStatement,xml的sql标签被解析封装成该对象。

2、构建mapper与dao的关系

6、configurationElement解析

  private void configurationElement(XNode context) {
    try {
      1、获取当前mapper.xml的namespace属性也就是对应的dao
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      2、解析cache-ref标签
      cacheRefElement(context.evalNode("cache-ref"));
      3、解析cache标签
      cacheElement(context.evalNode("cache"));
      4、解析parameterMap标签
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      5、解析resultMap标签
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      6、解析sql标签
      sqlElement(context.evalNodes("/mapper/sql"));
      7、解析定义的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);
    }
  }

注意一下7、解析定义的sql语句,在这儿会对select、update等标签进行解析

7、buildStatementFromContext

1、解析所有的select、update、delete、insert标签
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) {
    2、遍历标签
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        3、真正的解析的方法
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

8、parseStatementNode解析sql语句

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

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

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

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  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))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

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

这儿就贴出来了,就不细讲了,就是解析标签上的所有的属性,然后封装成MappedStatement对象最终放到配置类Configuration的mappedStatements属性上。

在这里插入图片描述

这儿为什么一下保存了两条,说实话我也没看懂。明明只put了一次,不过都是指向一个MappedStatement

回到代码块6进行进入到 2 。

9、bindMapperForNamespace构建dao与mapper的映射器

private void bindMapperForNamespace() {
  1、获取namespace
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      2、反射出namespace类型
      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);
        3、放入到mapperRegistry的knownMappers属性上。
        configuration.addMapper(boundType);
      }
    }
  }
}

3、这儿会将type进一步封装成MapperProxyFactory代理。

10、addMapper

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 {
      1、将MapperProxyFactory放入到knownMappers中
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      2、对使用注解的解析,如果该dao使用了的话。
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

将dao封装成MapperProxyFactory然后放到knownMappers属性中。

11 、总结

回想一下,SqlSessionFactoryBean干了哪些事:

  • 处理所有的属性构建Configuration
  • 解析mapper.xml内的各种标签,比较重要的就是将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.xxx.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。
  • 解析mapper.xml文件中对应的namespace,也就是对应的dao,将该类封装到mapperRegistryknownMappers 缓存中,key为dao对应的Class,value为MapperProxyFactory
  • 最后将Configuration封装到DefaultSqlSessionFactory了上,得到SqlSessionFactory对象。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个错误通常是由于Maven无法解析mybatis-spring-boot-starter依赖库而引起的。这可能是由于以下原因导致的: 1. Maven没有在本地或远程仓库中找到mybatis-spring-boot-starter库。 2. 您的网络连接出现了问题,导致Maven无法从远程仓库中下载mybatis-spring-boot-starter库。 为了解决这个问题,您可以尝试以下步骤: 1. 确保您的Maven配置文件正确配置了仓库地址。您可以检查您的Maven配置文件(settings.xml)是否指向了正确的远程仓库地址。如果没有配置,您可以在settings.xml文件中添加以下代码: ```xml <repositories> <repository> <id>central</id> <url>https://repo.maven.apache.org/maven2</url> <releases> <enabled>true</enabled> </releases> </repository> </repositories> ``` 2. 检查您的网络连接是否正常。您可以尝试使用浏览器或者其他网络工具测试您的网络连接是否正常。 3. 如果您已经确认了网络连接正常,并且仓库地址也正确,那么您可以尝试清理Maven本地仓库。您可以在Maven的命令行中执行以下命令: ```bash mvn dependency:purge-local-repository ``` 这将删除本地Maven仓库中所有的依赖库,然后重新下载它们。 4. 如果以上步骤都没有解决问题,您可以尝试手动下载并安装mybatis-spring-boot-starter库。您可以从Maven中央仓库手动下载mybatis-spring-boot-starter库,并使用以下命令将其安装到本地Maven仓库: ```bash mvn install:install-file -Dfile=<path-to-file> -DgroupId=org.mybatis.spring.boot -DartifactId=mybatis-spring-boot-starter -Dversion=<version> -Dpackaging=jar -DgeneratePom=true ``` 其中,`<path-to-file>`是mybatis-spring-boot-starter库的路径,`<version>`是mybatis-spring-boot-starter库的版本号。 通过上述步骤,应该能够解决无法解析mybatis-spring-boot-starter库的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值