mybatis-plus源码分析-1-sqlSessionFactory的生成


注:本篇本人看的较为混乱,而且个人觉得实际收获不是很大,因此可以选择跳过,直接去看源码分析-2-MapperScanner。**

预备知识:

在spring整合mybatis时,我们需要配置以下3个东西:

  • dataSource(数据源)
  • sqlSessionFactory
  • MapperScanner(Mapper扫描器)

本次将介绍的是sqlSessionFactory的生成,在讲解具体代码之前,先列出mybatis-plus的SqlSessionFactory包含的6种重要属性,以方便后续代码理解:

<bean id="sqlSessionFactory"  
 class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">  
 <!-- 配置数据源 -->  
 <property name="dataSource" ref="dataSource" />  
 <!-- 自动扫描 Xml 文件位置 -->  
 <property name="mapperLocations" value="classpath*:com/ds/orm/mapper/**/*.xml" />  
 <!-- 配置 Mybatis 配置文件(可无) -->  
 <property name="configLocation" value="classpath:mybatis-config.xml" />  
 <!-- 配置包别名,支持通配符 * 或者 ; 分割 -->  
 <property name="typeAliasesPackage" value="com.ds.orm.model" />  
 <!-- 枚举属性配置扫描,支持通配符 * 或者 ; 分割 -->  
 <property name="typeEnumsPackage" value="com.baomidou.springmvc.entity.*.enums" /> 

 <!-- MP 全局配置注入 -->  
 <property name="globalConfig" ref="globalConfig" />  
</bean>  

提前总结:流程图

1556618422137

正式开始

第一步:项目中引入mybatis-plus:

pom.xml
	<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.1.1</version>
  </dependency>

第二步:可以从扩展库(external Libraries)中查看到mybatis-plus-boot-starter的源码。

image

红框部分spring.factories中记录了spring-boot-starter类项目的入口为MybatisPlusAutoConfiguration:

spring.factories:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

接下来查看MybatisPlusAutoConfiguration.java是如何完成初始化配置的。

从注解的角度看:

image

  • @Configuration是将该类加入spring容器当中,

  • @ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})

    SqlSessionFactory,MybatisSqlSessionFactoryBean类的依赖存在。

  • @ConditionalOnSingleCandidate(DataSource.class) DataSource这个实例必须存在

  • @AutoConfigureAfter(DataSourceAutoConfiguration.class) 其他的类加载完之后,再加载DataSourceAutoConfiguration这个类,它主要是完成数据配置初始化。

阅读代码后发现,该类的核心便是配置SqlSessionFactory,因此接下来进行具体了解:

配置SqlSessionFactory

代码如下:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
//设置configurationLocation到factory中
if (StringUtils.hasText(this.properties.getConfigLocation())) {
   factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}

//设置configuration到factory中,后面会用到!
applyConfiguration(factory);

if (this.properties.getConfigurationProperties() != null) {
   factory.setConfigurationProperties(this.properties.getConfigurationProperties());

   
//设置TypeAliasesPackage、TypeAliasesSuperType、TypeHandlersPackage、MapperLocations
  if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  }
  if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
  }
  if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  }
  if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
  }         
   
//...设置其他属性到factory中,代码略
	 ...
...    

   
factory.setGlobalConfig(globalConfig);
	return factory.getObject();
}

先看注释和方法声明:@ConditionalOnMissingBean是在Spring容器中缺少bean的时候,创建SqlSessionFactory这个对象,DataSource这个对象会在这个方法中会自动注入进来,这是Spring的IOC来完成的。

再看具体实现:创建一个MybatisSqlSessionFactoryBean的实例,它是实现Spring中FactoryBean接口的类,然后在这个实例中设置DataSource,VFS,ConfigLocation,MybatisConfiguraition等属性。

    private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
        // 取出configuration
        MybatisConfiguration configuration = this.properties.getConfiguration();
        //若configuration为空,new或者自定义生成。
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new MybatisConfiguration();
        }
        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        //设置到factory中
        factory.setConfiguration(configuration);
    }

接下来就是设置各种属性。(代码略)

需要注意的是,sqlSessionFactory返回的值是 return factory.getObject();因此我们接着查看 factory.getObject()方法。

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

        return this.sqlSessionFactory;
    }

可以看到getObject获取SqlSessionFacoty,会调用afterPropertiesSet(),代码如下:

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

可以看到afterPropertiesSet紧接着会调用 buildSqlSessionFactory()方法,该方法便用来构造SqlSessionFactory,代码很长,我们一点一点分析:

构建SqlSessionFactory

下面的代码

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    	//第一部分:将configuration、configurationProperties以及globalConfig设置到targetConfiguration中
        MybatisXMLConfigBuilder xmlConfigBuilder = null;
        MybatisConfiguration targetConfiguration;
        //因为前面有设置configuration,所以一定会进入这一个if
    	if (this.configuration != null) 
            targetConfiguration = this.configuration;
            if (targetConfiguration.getVariables() == null) {
                targetConfiguration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                targetConfiguration.getVariables().putAll(this.configurationProperties);
            }
        } else if (this.configLocation != null) {
            xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
            //略
        }
    
        this.globalConfig = (GlobalConfig)Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults);
        this.globalConfig.setDbConfig((DbConfig)Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(DbConfig::new));
        targetConfiguration.setGlobalConfig(this.globalConfig);
        if (targetConfiguration.isMapUnderscoreToCamelCase()) {
            targetConfiguration.setObjectWrapperFactory(new MybatisMapWrapperFactory());
        }

这一整块代码的唯一功能:将configuration、configurationProperties以及globalConfig设置到targetConfiguration中。

因为前文提到了configuration已经设置到factory中,因此一定会进入第一个if,而第一个if的作用是将configuration和configurationProperties设置到targetConfiguration中。

typeEnumsPackage及typeAliasesPackage的注册

下面一段代码主要功能为:获取枚举属性(typeEnumsPackage)以及包别名(typeAliasesPackage),并进行注册

枚举的注册,就是把其存入Map<JdbcType, TypeHandler<?>> map这一内存中。

别名的注册,是将别名(例如user)及其类型(例如User.class)存入 Map<String, Class<?>> typeAliases这一内存中。

        if (StringUtils.hasLength(this.typeEnumsPackage)) {
            //这一整个if-else的功能:生成typeEnumsPackage的class文件,并存入Set<Class<?>> classes中。
            Object classes;
            //假如包名包含*或,或; 例如:com.study.*
            if (this.typeEnumsPackage.contains("*") && !this.typeEnumsPackage.contains(",") && !this.typeEnumsPackage.contains(";")) {
                //1.将包名com.study.*转化成具体路径:classpath*: com/study/*.class
                //2.根据具体路径生成Class类,然后存入Set<Class<?>>中,返回该Set
                classes = PackageHelper.scanTypePackage(this.typeEnumsPackage);
                if (((Set)classes).isEmpty()) {
                    LOGGER.warn(() -> {
                        return "Can't find class in '[" + this.typeEnumsPackage + "]' package. Please check your configuration.";
                    });
                }
            } else {
                //1.把报名按照",; \t\n"进行切割。
                //2.切割后步骤同上:转化成具体路径然后存入Set<Class<?>>中
                String[] typeEnumsPackageArray = StringUtils.tokenizeToStringArray(this.typeEnumsPackage, ",; \t\n");
                com.baomidou.mybatisplus.core.toolkit.Assert.notNull(typeEnumsPackageArray, "not find typeEnumsPackage:" + this.typeEnumsPackage, new Object[0]);
                classes = new HashSet();
                Stream.of(typeEnumsPackageArray).forEach((typePackage) -> {
                    Set<Class<?>> scanTypePackage = PackageHelper.scanTypePackage(typePackage);
                    if (scanTypePackage.isEmpty()) {
                        LOGGER.warn(() -> {
                            return "Can't find class in '[" + typePackage + "]' package. Please check your configuration.";
                        });
                    } else {
                        classes.addAll(PackageHelper.scanTypePackage(typePackage));
                    }

                });
            }

            //遍历Set<Class<?>> classes,并注册到typeHandlerRegistry中。
            //所谓的注册,就是把其存入并注册到typeHandlerRegistry中的Map<JdbcType, TypeHandler<?>> map这一内存中。
            TypeHandlerRegistry typeHandlerRegistry = targetConfiguration.getTypeHandlerRegistry();
            ((Set)classes).stream().filter(Class::isEnum).filter((cls) -> {
                return IEnum.class.isAssignableFrom(cls) || EnumTypeHandler.dealEnumType(cls).isPresent();
            }).forEach((cls) -> {
                typeHandlerRegistry.register(cls, EnumTypeHandler.class);
            });
        }

		//生成包别名(typeAliasesPackage)
        if (StringUtils.hasLength(this.typeAliasesPackage)) {
            Set var10000 = this.scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType);
            TypeAliasRegistry var10001 = targetConfiguration.getTypeAliasRegistry();
            var10000.forEach(var10001::registerAlias);
        }

		//注册别名到TypeAliasRegistry中
        if (!ObjectUtils.isEmpty(this.typeAliases)) {
            Stream.of(this.typeAliases).forEach((typeAlias) -> {
                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
                LOGGER.debug(() -> {
                    return "Registered type alias: '" + typeAlias + "'";
                });
            });
        }

紧接着配置plugins、typeHandlersPackage、typeHandlers、DatabaseId以及事物工厂TransactionFactory到targetConfiguration中。(代码略)

mapperLocation中xml文件的解析及注册

最后对于属性mapperLocation,使用了ibatis自带的XMLMapperBuilder.parse()方法来验证mapperLocation的合法性

        if (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
                LOGGER.warn(() -> {
                    return "Property 'mapperLocations' was specified but matching resources are not found.";
                });
            } else {
                Resource[] var50 = this.mapperLocations;
                int var54 = var50.length;

                for(int var5 = 0; var5 < var54; ++var5) {
                    Resource mapperLocation = var50[var5];
                    if (mapperLocation != null) {
                        try {
                            //核心代码:用到的是ibatis自带的XMLMapperBuilder.parse()方法来验证mapperLocation的合法性
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                            xmlMapperBuilder.parse();
                        } catch (Exception var41) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var41);
                        } finally {
                            ErrorContext.instance().reset();
                        }

                        LOGGER.debug(() -> {
                            return "Parsed mapper file: '" + mapperLocation + "'";
                        });
                    }
                }
            }
        } 

这里可以看到使用了XMLMapperBuilder.parse()方法来进行解析,具体解析及后续步骤与mybatis的无异,详见**“mybatis动态代理.md”**

猜你喜欢

转载自blog.csdn.net/bintoYu/article/details/89748464