1.pom
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
引入一个这个注解,就有了mybatis的强大功能,神奇
mybaits使用流程:
- 引入依赖
- 配置数据库信息
- 创建数据库对应的实体类
- 创建mapper接口,定义查询方法 @Mapper
- 创建mapper对应的xml,编写sql语句
- 创建service,调用mapper接口的方法 @Service
- 创建controller,调用service的方法 @Controller
2.自动配置
引入 mybaits starter会引入这个autoconfigure
MybatisAutoConfiguration引入,项目启动时会扫描到对应的autoconfigure下的spring.factories文件中的配置。
引入mybaits-starter会自动引入的jar包:
mybaits自动配置类
3.扫描mapper接口
refresh的invokeBeanFactoryPostProcessors(beanFactory);
也就是扫描到我们定义的mapper接口,变成BeanDefinition。
这里是两个问题:
1.接口默认不会被spring装载成BeanDefinition。所以mybatis框架要处理。
2.接口不会被实例化,需要对接口类型进行封装。
3.1.mybatis-1.1.1-全局配置方式
在启动类加注解:@MapperScan(“xxx.xxx.xxx.mapper”)
作用就是会扫描到配置的包下面的所有mapper接口,扫描这些接口的目的是为了创建代理对象,因为接口不能实例化,需要用代理方式执行接口中的方法。这里用的是JDK动态代理。
这个注解能生效是因为引入了MapperScannerRegistrar类
mapper啥啥啥输入注册,应该就是这个意思
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar.registerBeanDefinitions
在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:
第一个是BeanDefinitionRegistryPostProcessor;(主要 功能是注册beanDefinition)
第二个是BeanFactoryPostProcessor。
具体流程:
1.AbstractApplicationContext类的refresh方法的invokeBeanFactoryPostProcessors方法
2.PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法
- invokeBeanFactoryPostProcessors方法会执行三次 第一次扫描conpnent注解的bean,第二次默认情况下不执行,第三次扫描mapper接口。
3.PostProcessorRegistrationDelegate类的invokeBeanDefinitionRegistryPostProcessors方法
4.ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法
5.ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法
6.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions方法
这里configurationmodel有很多,关键就是这个启动类,在启动类加了mapperScan注解,他引入了MapperScannerRegistrar。
7.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass方法
这里是关键,当遍历到MapperScannerRegistrar时,会执行他的registerBeanDefinitions方法,因为他实现了ImportBeanDefinitionRegistrar接口。这里的传参就是configClass的ImportBeanDefinitionRegistrars属性,这个集合里有当前类中所有实现了ImportBeanDefinitionRegistrar接口的类
- 如果既加了mapperscan又加了@mapper 则执行@mapper时 下面的第一个if会进去,然后直接返回。也就是mapperscan优先于@mapper,会先生效。
8.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法
遍历所有ImportBeanDefinitionRegistrars,调用他们的registerBeanDefinitions方法
9.MapperScannerRegistrar.java类的registerBeanDefinitions方法
就是这个MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。
10.ClassPathMapperScanner类的doScan方法
会调用父类ClassPathBeanDefinitionScanner的doScan方法
11.ClassPathBeanDefinitionScanner类的doScan方法
12.ClassPathBeanDefinitionScanner类的registerBeanDefinition方法
13.BeanDefinitionReaderUtils类的registerBeanDefinition方法
14.最后是DefaultListableBeanFactory类的registerBeanDefinition方法。
扫描包路径下的所有mapper
一般情况下,**mapper是接口类。默认情况下,spring对接口interface是不会生成BeanDefinition 对象。在mybatis里,为了生成BeanDefinition 对象,则重写了isCandidateComponent方法,上面箭头指的方法就会调用,是接口类型也要生成BeanDefinition,所以这里service接口也会返回 **。
这里解决了标题3的第一个问题。
通过这个方法就可以生成我们自己的mapper接口的beandefinition了。
spring是扫描所有基于@Component注解的类,并生成bean定义,然后放到bean定义注册表中。而我们这里的mapper没有加@Component注解,所以在执行上述逻辑之前,实际的bean定义注册表中是没有这些mapper的bean定义的。执行上述逻辑之后,才在bean定义注册表中加入了这些mapper的beanDefinition
补充上面的第十步
将beanDefinitions放到beanFactory之后,还有一步重要的操作,就是执行本类ClassPathBeanDefinitionScanner中的processBeanDefinitions
这里是把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass,这样做是为了让后面创建bean时,可以使用MapperFactoryBean来创建bean。
保存了自定义mapper的信息,并将类型变为mapperFactoryBeanClass。这样动态代理的时候就可以找到我们自定义的mapper接口了。
为什么要把mapper的BeanClass设置为mapperFactoryBeanClass?
因为mapper是接口,但是接口是不能实例化的,所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBean类型,它的父类SqlSessionDaoSupport的父类DaoSupport实现了InitializingBean接口,所以spring在实例化对象时会调用对应的afterPropertiesSet方法,作用是添加mapper到configuration(一般不会用到,在初期解析mapper.xml时就已经加了),实现了FactoryBean接口,所以后续会调用getObject方法,这个方法就是创建mapper的代理对象的,也就是相当于实例化了mapper。
这里解决了标题3的第二个问题。
3.2.mybatis-1.1.1-单接口配置方式
@mapper
这样需要在每一个mapper接口添加此注解
这个注解是怎么生效的呢
加载MybatisAutoConfiguration类后,因为MybatisAutoConfiguration内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions。这个概念和全局配置那种方式是一样的。
注:AutoConfiguredMapperScannerRegistrar的注入受mapperscan注解的影响,如果有mapperscan注解,则不会注入AutoConfiguredMapperScannerRegistrar类
MybatisAutoConfiguration类
到这就已经把mapper接口的定义信息存到beanFactory了,等待实例化。
具体流程
1.refresh方法
2.AbstractApplicationContext类的invokeBeanFactoryPostProcessors方法
3.PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法
4.ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法
.5.ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法
6.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions
7.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass方法
8.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法
9.MybatisAutoConfiguration类的内部类AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions方法
就是这个内部类实现了ImportBeanDefinitionRegistrar接口
10.ClassPathMapperScanner的dosacn方法
11.ClassPathBeanDefinitionScanner类的doScan方法
12.BeanDefinitionReaderUtils类的registerBeanDefinition方法
会调用DefaultListableBeanFactory类的registerBeanDefinition,完成beandefinition的注册
hasBeanCreationStarted方法
注:上面mybatis-spring-boot-starter版本为1.1.1版本,mybatis-spring-boot-starter 2.2.2版本的mapperscan和mapper注解则是都实例化一个MapperScannerConfigurer类,它实现了BeanDefinitionRegistryPostProcessor接口(和ConfigurationClassPostProcessor一样),容器启动时会调用该类的postProcessBeanDefinitionRegistry方法,然后调用ClassPathBeanDefinitionScanner的scan方法,然后调用子类ClassPathMapperScanner的doscan方法把所有的maper变成beandefinition并且通过processBeanDefinitions方法把所有beandefinition变成MapperFactoryBean类型。注入前会通过registerFilters方法判断是mapperscan还是mapper。
注册MapperScannerConfigurer类型
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()会执行三次invokeBeanDefinitionRegistryPostProcessors方法。
第一次是执行ConfigurationClassPostProcessor,把自定义的bean变成beanDefinition,还有自动配置类,这时如果加了mapperScan,则会在mapperScan引入的MapperScannerRegistrar
类中注册MapperScannerConfigurer,如果没加mapperScan,则会在mybaitsAutoConfiuration
类的内部类AutoConfiguredMapperScannerRegistrar
中注册MapperScannerConfigurer。然后在第三次执行invokeBeanDefinitionRegistryPostProcessors时,通过MapperScannerConfigurer,扫描到所有mapper,变成beanDefinition,把所有beandefinition变成MapperFactoryBean类型。
3.3.mybatis-2.2.2
明确:MapperScannerConfigurer属于BeanFactoryPostProcessor,用于处理BeanDefinition的。
3.3.1.加mapperScan
加载每个配置类(第一个invokeBeanDefinitionRegistryPostProcessors方法)
当扫描到主启动类时,会扫描到启动类加的mapperScan注解引入的MapperScannerRegistrar
处理实现了ImportBeanDefinitionRegistrar接口的类,也就是MapperScannerRegistrar
然后会执行MapperScannerRegistrar的registerBeanDefinitions方法
然后执行重载的registerBeanDefinitions方法
然后注册beanDefinition 类型是MapperScannerConfigurer 名称是MapperScannerRegistrar
3.3.2.不加mapperScan
MybatisAutoConfiguration的注册也是在第一个invokeBeanDefinitionRegistryPostProcessors方法
不加注解的话则会走MybatisAutoConfiguration类的AutoConfiguredMapperScannerRegistrar类,因为AutoConfiguredMapperScannerRegistrar也实现了ImportBeanDefinitionRegistrar接口,所以也会走他的registerBeanDefinitions方法。
AutoConfiguredMapperScannerRegistrar类的registerBeanDefinitions方法
注册了MapperScannerConfigurer为beanDefinition。
这个类能生效前提是没加mapperScan注解,如果加了就不会注入这个类
3.3.3.MapperScannerConfigurer扫描mapper变成beanDefinition
加或者不加mapperScan逻辑相同
PostProcessorRegistrationDelegate
类的invokeBeanFactoryPostProcessors
方法
这里能获取到MapperScannerConfigurer
是因为它实现了BeanDefinitionRegistryPostProcessor
接口
然后继续执行最终会调用ClassPathMapperScanner
类的doscan
方法
然后调用父类ClassPathBeanDefinitionScanner
的doScan
方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
//这里basePackage 如果加了mapperScan注解并且指定了包路径就用指定的包路径
//如果没加mapperScan注解则是启动类所在的包路径
for (String basePackage : basePackages) {
//mybaits要单独处理 获取mapper接口
//这里如果加了mapperscan注解则不需要扫描mapper注解,否则会扫描mapper注解。
//所以 如果没加mapperscan 则必须要加mapper注解
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//遍历注册
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册beandefinition
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
3.3.4.整体流程(重要)
1.先注册MapperScannerConfigurer
,它实现了BeanDefinitionRegistryPostProcessor
接口。这个过程是在PostProcessorRegistrationDelegate
#invokeBeanFactoryPostProcessors
方法第一次执行,
方法调用顺序为:
AbstractApplicationContext#refresh方法的invokeBeanFactoryPostProcessors(beanFactory)方法 ->
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中的invokeBeanDefinitionRegistryPostProcessors方法 -> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中的processConfigBeanDefinitions
方法 -> parser.parse(candidates)和this.reader.loadBeanDefinitions(configClasses)方法。
这两个关键方法很关键。
parser.parse(candidates)负责处理compnentscan controller等。关键的一点是会把实现了ImportBeanDefinitionRegistrar接口的bean放到ConfigurationClass属性的importBeanDefinitionRegistrars属性中。这其中就包括@mapperscan注解引入的MapperScannerRegistrar
。
然后ConfigurationClassBeanDefinitionReader
类的loadBeanDefinitions
方法则会处理实现了ImportBeanDefinitionRegistrar接口的bean,调用他们的registerBeanDefinitions
方法,这是方式之一,方式之二就是没加@mapperscan注解的话,由mybatis自动配置类实现,他会引入另一个实现了ImportBeanDefinitionRegistrar接口的bean,也就是AutoConfiguredMapperScannerRegistrar
,然后引入MapperScannerConfigurer的过程就和方式1相同了。
关键的MapperScannerConfigurer就是这么来的。
然后就是MapperScannerConfigurer
干活的时候了,这个过程是在PostProcessorRegistrationDelegate
#invokeBeanFactoryPostProcessors
方法第三次执行,能执行是因为他实现了BeanDefinitionRegistryPostProcessor接口,就像把他扫描到的ConfigurationClassPostProcessor一样,但是它干的活也就到这了,后面是交给了
ClassPathBeanDefinitionScanner
及他的子类ClassPathMapperScanner
,方法是ClassPathBeanDefinitionScanner#doScan
。ClassPathMapperScanner
由mybatis提供,重写isCandidateComponent
方法保证mapper接口也可以被spring扫描到。
doScan方法针对加没加@mapperscan也有两种逻辑,加了的话会扫描到所有mapper,没加的话在自动配置类(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar类的registerBeanDefinitions方法中)指明@mapper注解也要被扫描到。
再总结一下,先注册MapperScannerConfigurer(通过自动配置或mapperscan注解),然后通过MapperScannerConfigurer,实现mapper的扫描,具体工作的bean为ClassPathBeanDefinitionScanner
及他的子类ClassPathMapperScanner
。
4.实例化
接下来,执行到refresh.finishBeanFactoryInitialization(beanFactory),在这里将会把bean定义注册表中的所有的beanDefinition进行实例化,然后放到bean缓存池中,供应用程序调用。
注意这里是把bean定义注册表中的所有beanDefinition遍历处理挨个进行实例化,那么mapper、SqlSessionFactory、SqlSessionTemplate实例化的先后顺序是怎样的呢? 这里需注意一点,在每个创建bean实例之后,初始化bean之前,会执行populateBean进行属性赋值,如果依赖其他的bean,那么会先创建依赖的bean,所以,即使先实例化的是controller(正常controller依赖的service,service依赖mapper,mapper依赖sqlSessionTemplate,sqlSessionTemplate依赖SqlSessionFactory,但实例化的顺序是反过来的),最终还是先实例化SqlSessionFactory。
DataSource自动初始化就是配置数据源,默认hikari,springboot会引入spring-boot-starter-jdbc,这里会引入hikari,所以不配置的话默认使用hikari数据源。
4.1.SqlSessionFactory
在加载mybatis自动配置类时,会先判断SqlSessionFactory(mybatis jar包中)类和SqlSessionFactoryBean(mybatis-spring jar包中)类是否引入了,这里肯定是引入了(在mybatis-starter中),这里是为了注入bean,只是扫描到了SqlSessionFactory类,但是并没有实例化,所以会在这里进行实例化。
传入的是datasource,这个应该是在配置文件配置好的,很重要,保存数据库的连接信息,会被封装到SqlSessionFactory唯一的一个属性Configuration属性的Environment属性中。
最后会调用SqlSessionFactoryBean的getObject,构建SqlSessionFactory,返回SqlSessionFactoryBean的SqlSessionFactory属性。
继续调用buildSqlSessionFactory(),主要就是填充前面创建的configuration对象(在MybatisAutoConfiguration类的sqlSessionFactory方法中创建的)。
这个configuration里有很多属性,
对应的xml
buildSqlSessionFactory就是通过XMLConfigBuilder解析mybatis配置,通过XMLMapperBuilder解析mapper.xml的配置(重点是生成mappedStatements、resultMaps、sqlFragments等),以及其他的配置,最终放到Configuration里,供后面使用。
在XMLMapperBuilder.parse()里通过mapperRegistry.addMapper方法,会把mapper接口添加到knownMappers中(knownMappers结构为HashMap,每个mapper的value为MapperProxyFactory对象),那么后面调用getMapper的时候就可以直接获取了。所以XMLMapperBuilder.parse()方法,完成了mapper接口和mapperproxyfactory这个mapper代理工厂的绑定。(获取是为了实例化mapper接口,也就是创建mapper代理对象)
这里进行绑定的前提是在resources/mapper/**下面要有*mapper.xml文件,因为mybaitsPlus可能不会写mapper.xml,所以如果没写的话会在实例化mapper的时候进行绑定。afterPropertiesSet方法(InitializingBean接口),
- configuration创建
factory.setConfiguration(configuration)就是把构建的SqlSessionFactoryBean.class赋值给SqlSessionFactoryBean的configuration属性。后面buildSqlSessionFactory会用到。
yml配置
自定义配置类
- buildSqlSessionFactory方法
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
//就是上面创建的configuration对象,继续完善这个对象
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 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);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
//指定Mybatis的实体目录
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);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
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 + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
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));
//重要1 在这儿解析所有的mapper.xml文件
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
//遍历mapper.xml文件进行解析
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//创建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.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
重要1:
- parse方法
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//1.解析mapper标签以及内部的标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//2.构建mapper与dao的关系
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析mapper标签以及内部的标签
- configurationElement方法
private void configurationElement(XNode context) {
try {
//获取namespace 也就是mapper接口
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析定义的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);
}
}
list是mapper.xml中的sql语句
parseStatementNode方法做具体工作,就是解析标签上的所有的属性,然后封装成MappedStatement对象最终放到配置类Configuration的mappedStatements属性上。key 为 id ,mapper接口对应方法的全限定名,value 为 MappedStatement。
执行完parse方法中的 configurationElement(parser.evalNode(“/mapper”))方法:
这里能获取到两个,早期是通过方法名方式进行查询的。现在是根据全限定名
构建mapper接口与代理工厂的映射关系
- bindMapperForNamespace();
addmapper方法
将mapper接口封装成MapperProxyFactory然后放到knownMappers属性中。
key是mapper接口的全限定名称,value是对应的MapperProxyFactory,通过这个代理工厂去创建对应的mapper代理类。
构建SqlSessionFactory时会构建Configuration对象,这个对象维护在MybatisProperties类中,
MybatisProperties关联MybatisAutoConfiguration自动配置类
如果在配置文件配了相关属性 例如mybatis.configuration.map-underscore-to-camel-case=true
则Configuration会在MybatisProperties中实例化,否则会在构建SqlSessionFactory时实例化,
applyConfiguration方法中。
4.2.sqlSessionTemplate
创建sqlSessionTemplate需要上面创建的sqlSessionFactory
通过动态代理
- 上面最后一行代码使用jdk动态代理生成SqlSession代理对象
- 第一个参数表示生成代理对象的类加载器,第二个参数表示被代理类对象,第三个参数表示代理实现类(里面主要是执行代理对象时要做的事情)
4.3.mapper
mapper的实例化依赖上面创建的sqlSessionTemplate
mapper接口在前面被封装成了MapperFactoryBean,实现了InitializingBean接口,所以应该重写该接口的方法afterPropertiesSet,但是在MapperFactoryBean类中没有重新这个方法,而他的父类SqlSessionDaoSupport也没重写,所以是在SqlSessionDaoSupport的父类DaoSupport类中重写的,所以初始化MapperFactoryBean时会先调用DaoSupport中的afterPropertiesSet方法。
然后执行MapperFactoryBean的checkDaoConfig方法
mapper是什么时候放到Configuration中的呢,请看上面的checkDaoConfig(也可能是在实例化sqlSessionFactory过程中,通过xmlMapperBuilder加载进来的,两种方式最终都是调用mapperRegistry.addMapper方法加载进来)
为啥是这两种方式呢,因为mybaitsplus可能没有写mapper.xml文件,所以在初始化sqlSessionFactory时不会解析xml,需要在这里进行addMapper操作。
这个操作是在createBean时完成的,完成后 mapper接口就变成了MapperFactoryBean类型。并执行了MapperFactoryBean的父类的父类(DaoSupport)的afterPropertiesSet方法
下面的操作是createBean之后(实例化初始化之后)的
getObjectForBeanInstance会调用MapperFactoryBean.getObject方法。
初始化之后,因为MapperFactoryBean是FactoryBean,所以会执行MapperFactoryBean.getObject(这里执行的前提条件是有service注入了这个mapper,不然是不会调用这个方法的)
为什么呢 因为跟着spring的步骤,mapper接口在调用getBean方法时会加&前缀,然后getObjectForBeanInstance判断如果加了前缀则直接返回。但是通过service的属性注入调用的getbean方法则不会加&前缀,保证了MapperFactoryBean.getObject的方法调用
上面的getSqlSession()获取的就是上面创建的SqlSessionTemplate对象,这是mapper的SqlSession,然后继续执行SqlSessionTemplate.getMapper,
通过jdk代理方式创建的mapper代理对象,代理实现类为MapperProxy,以后有人调用mapper方法,就会调用代理对象的invoke方法了。也就是MapperProxy的invoke方法了。
最后把实例化mapper的bean放到bean缓存池中,至此,实例化mapper结束。
然后就是前端发请求调用controller,到service,到mapper,到数据库,具体是怎么执行查询了。
4.4.总结
1.实例化SqlSessionFactory的作用
构建configuration对象,保证mybatis的各项配置生效。
解析*mapper.xml文件,保证xml中的sql执行。(解析完也放到configuration属性中)
构建mapper接口与代理工厂的映射关系,保证mapper实例化。
2.实例化sqlSessionTemplate的作用
封装SqlSessionFactory,替SqlSessionFactory工作,比如mapper接口的实例化,sql的执行。
3.实例化mapper的作用
创建mapper接口的代理,service中调用接口方法时,执行mapper代理的方法完成mapper接口的方法的执行。
5.执行mapper操作
执行mapper是调用的MapperProxy.invoke,最终调用的MapperMethod的execute方法
执行测试会执行MapperProxy类的invoke方法
测试为查询,且没有返回list之类的,所以查询一条
method和command来自MapperMethod的有参构造函数,去config中拿到sql类型和mapper中方法的全限定名
这里的sqlSession其实就是上面生成的sqlSessionTemplate,在初始化MapperMethod时候,通过SqlCommand从Configuration中的MappedStatement获取限定名和SQL类型(select|insert|update|delete),然后执行sqlSessionTemplate的selectOne方法。
继续执行selectone
sqlSessionProxy是sqlSessionTemplate的一个成员,在创建sqlSessionTemplate时赋值,是SqlSession的代理,所以,接下来走SqlSessionInterceptor.invoke方法,通过实例化sqlSessionTemplate可以看出,实际用的是反射机制,执行sqlSession方法。
可以看出method是sqlsession的method,所以在执行method.invoke时会执行DefaultSqlSession的selectOne方法。
这里的sqlSession是与数据库交互的会话sqlSession(DefaultSqlSession),根据sqlSessionFactory中的Configuration属性中的Environment属性去构建(主要是dataSource属性,但是这里还不会根据datasource进行连接),注意与sqlSessionTemplate这个sqlSession的区别,然后通过method.invoke反射调用到具体的 DefaultSqlSession.selectList 方法。
最终调用Executor方法执行,Executor有两个方法:BaseExecutor 和 CachingExecutor,分别代表两种缓存机制,BaseExecutor 是一级缓存机制使用,CachingExecutor是二级缓存机制使用(如果查询二级缓存中没有结果,则会调用BaseExecutor的方法查询一级缓存,然后把查询结果放到二级缓存)。
所以先来到二级缓存查询
这里在查询前需要拿到sql和缓存key,sql存储在configuration的mappedStatements的sqlSource属性中,解析${} #{} 等动态sql。
继续执行SimpleExecutor的query方法
然后执行doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取到configuration
Configuration configuration = ms.getConfiguration();
//创建statementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
stmt = prepareStatement(handler, ms.getStatementLog());
//调用 StatementHandler.query() 方法执行
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
prepareStatement方法
这个方法的getConnection会调用HikariDataSource类的getConnection方法,根据datasource中的url等配置和数据库进行连接。
这几个子类的意义:
SimpleStatementHandler ,这个对应的 就是JDBC 中常用到的 Statement 接口,用于简单SQL的处理
PreparedStatementHandler , 这个对应的就是JDBC中的 PreparedStatement,用于预编译SQL的处理
CallableStatementHandler , 这个对应JDBC中 CallableStatement ,用于执行存储过程相关的处理
RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用
最后获取结果
这里为了测试改了下sql语句
数据库对应数据
这样一次完整的查询就走完了。
流程:
执行mapper方法,实际就是通过jdk代理机制,执行MapperMethod的execute方法。execute方法里会调用session的方法,这个session是sqlSessionTemplate,创建sqlSessionTemplate时创建了一个代理类,也就是SqlSessionInterceptor,然后通过sqlSessionTemplate的jdk代理机制,执行SqlSessionInterceptor的invoke方法,这个方法会获取到DefaultSqlSession,然后执行method.invoke方法,然后通过反射机制,执行DefaultSqlSession.selectList方法。通过configuration.getMappedStatement获取MappedStatement,这时找到了实际的sql了,然后到了Executor模块。执行SimpleExecutor的doQuery方法,然后执行PreparedStatementHandler的query方法,最终调用jdbc操作执行sql,最后解析结果并返回。
6.总结
需要处理的问题应该就是:mapper是一个接口,接口不能实例化,也就是不能干活,想让他干活的话就要用动态代理,找一个代理类帮他干活。mapper.xml有具体的sql,想让它执行就要把mapper接口的方法和xml中的sql关联起来
。
springboot整合mybaits的问题
- mapper怎么被spring注册为beanDefinition
- mapper怎么被实例化
- mapper方法怎么被执行
6.1.mapper怎么被spring注册为beanDefinition:
mapperscan注解或mapper注解,并且mybatis要重写isCandidateComponent方法,保证是接口也要生成beanDefinition。
6.2.mapper是接口怎么被实例化
注册的时候将mapper接口的类型转换为MapperFactoryBean,然后通过MapperFactoryBean的getObject方法实现实例化(通过jdk代理生成了bean的代理对象)。
6.3.相关属性初始化
初始化的过程,创建SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,这是基础。在此过程,通过MapperScannerConfigurer扫描指定包下的所有mapper接口生成beanDefinition,并放到bean定义注册表中(类型为:MapperFactoryBean)。
- SqlSessionFactory
初始化SqlSessionFactory主要是填充Configuration属性。这里有两个重要参数:MappedStatement和MapperRegistry。
MappedStatement是解析mapper.xml后封装成的对象。通过XMLMapperBuilder实现。
在这里也完成了mapper接口与mapper.xml的绑定。MapperRegistry类内的knownMappers缓存: key为namespace对应的dao的class,value为MapperProxyFactory。 - SqlSessionTemplate
使用SqlSessionTemplate构造器创建SqlSessionTemplate对象,其中用了jdk代理方式创建了SqlSession代理对象。也就是执行查询的时候调用SqlSessionTemplate的方法实际上调用的是SqlSession的方法。所有访问数据库的操作都是通过SqlSession来的。这么做是为了解耦Mapper和SqlSession。
6.4.执行mapper
执行mapper方法的过程,主要是先通过两个代理类,即先执行mapper代理实现类MapperProxy的invoke方法,然后执行SqlSessionTemplate代理实现类的invoke方法,然后进入DefaultSqlSession相应方法中,这里会根据mapper的限定名获取MappedStatement,然后调用Executor相应方法,而Executor是封装了jdbc的操作,所以最终是通过jdbc执行sql,最后再把执行的结果解析返回。
整个SpringBoot整合Mybatis的过程,就是在spring容器初始化的过程中生成mapper的代理对象,然后在执行mapper方法的过程,利用代理机制,执行目标方法,最终底层通过jdbc执行sql。
参考链接
https://blog.csdn.net/zhengguofeng0328/article/details/125945926
https://blog.csdn.net/u013521882/article/details/120624374