<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
引入一个这个注解,就有了mybatis的强大功能,神奇
mybaits使用流程:
引入 mybaits starter会引入这个autoconfigure
MybatisAutoConfiguration引入,项目启动时会扫描到对应的autoconfigure下的spring.factories文件中的配置。
引入mybaits-starter会自动引入的jar包:
mybaits自动配置类
也就是扫描到我们定义的mapper接口,变成BeanDefinition。
这里是两个问题:
1.接口默认不会被spring装载成BeanDefinition。所以mybatis框架要处理。
2.接口不会被实例化,需要对接口类型进行封装。
在启动类加注解:@MapperScan(“xxx.xxx.xxx.mapper”)
作用就是会扫描到配置的包下面的所有mapper接口,扫描这些接口的目的是为了创建代理对象,因为接口不能实例化,需要用代理方式执行接口中的方法。这里用的是JDK动态代理。
这个注解能生效是因为引入了MapperScannerRegistrar类
mapper啥啥啥输入注册,应该就是这个意思
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar.registerBeanDefinitions
而MapperScannerRegistrar.registerBeanDefinitions会先创建MapperScannerConfigurer类到beanfactory中。然后再通过MapperScannerConfigurer再去扫描我们自己定义的mapper接口
MapperScannerConfigurer实现的BeanDefinitionRegistryPostProcessor接口,因为在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:
第一个是BeanDefinitionRegistryPostProcessor;
第二个是BeanFactoryPostProcessor。
而执行BeanDefinitionRegistryPostProcessor,会执行postProcessBeanDefinitionRegistry方法,在这里就是MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法
这里新建了一个ClassPathMapperScanner类,执行它的scan方法,它的父类是ClassPathBeanDefinitionScanner
注意上面最后一行 this.basepackage就是配置的扫描路径
会调用ClassPathBeanDefinitionScanner的Scan方法
先执行ClassPathMapperScanner中的doScan,这里会先调用了父类的doScan,也就是ClassPathBeanDefinitionScanner类的doScan方法(功能就是根据配置的包路径,扫描自定义mapper接口,转换成BeanDefinition)。调用之后执行processBeanDefinitions
扫描包路径下的所有mapper
一般情况下,**mapper是接口类。默认情况下,spring对接口interface是不会生成BeanDefinition 对象。在mybatis里,为了生成BeanDefinition 对象,则重写了isCandidateComponent方法,上面箭头指的方法就会调用,是接口类型也要生成BeanDefinition **。
这里解决了标题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定义为mapperFactoryBeanClass,利用mapperFactoryBeanClass是通过getObject()来进行实例化,即通过jdk代理的方式,生成的代理对象。
这里解决了标题3的第二个问题。
总结:
先生成MapperScannerConfigurer的bean定义,并放入bean定义注册表中。然后通过MapperScannerConfigurer.postProcessBeanDefinitionRegistry扫描所有mapper类,创建mapper的bean定义,也是放入bean定义注册表中,并将mapper的bean定义的BeanClass属性值设置为mapperFactoryBeanClass,为后面创建bean做准备。
@mapper
这样需要在每一个mapper接口添加此注解
这个注解是怎么生效的呢
加载MybatisAutoConfiguration类后,因为MybatisAutoConfiguration内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions。这个概念和全局配置那种方式是一样的。
到这就已经把mapper接口的定义信息存到beanFactory了,等待实例化。
接下来,执行到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。
在加载mybatis自动配置类时,会先判断SqlSessionFactory(mybatis jar包中)类和SqlSessionFactoryBean(mybatis-spring jar包中)类是否引入了,这里肯定是引入了(在mybatis-starter中),这里是为了注入bean,只是扫描到了SqlSessionFactory类,但是并没有实例化,所以会在这里进行实例化。
传入的是datasource,这个应该是在配置文件配置好的
最后会调用getObject
继续调用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接口和xml映射文件的绑定。
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);
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);
}
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//1.解析mapper标签以及内部的标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//2.构建mapper与dao的关系
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析mapper标签以及内部的标签
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接口封装成MapperProxyFactory然后放到knownMappers属性中。
mapper接口在前面被封装成了MapperFactoryBean,实现了InitializingBean接口,所以应该重写该接口的方法afterPropertiesSet,但是在MapperFactoryBean类中没有重新这个方法,而他的父类SqlSessionDaoSupport也没重写,所以是在SqlSessionDaoSupport的父类DaoSupport类中重写的,所以初始化MapperFactoryBean时会先调用DaoSupport中的afterPropertiesSet方法。
然后执行MapperFactoryBean的checkDaoConfig方法
mapper是什么时候放到Configuration中的呢,请看上面的checkDaoConfig(也可能是在实例化sqlSessionFactory过程中,通过xmlMapperBuilder加载进来的,两种方式最终都是调用mapperRegistry.addMapper方法加载进来)
初始化之后,因为MapperFactoryBean是FactoryBean,所以会执行MapperFactoryBean.getObject,
上面的getSqlSession()获取的就是上面创建的SqlSessionTemplate对象,这是mapper的SqlSession,然后继续执行SqlSessionTemplate.getMapper,
通过jdk代理方式创建的mapper代理对象,代理实现类为MapperProxy,以后有人调用mapper方法,就会调用代理对象的invoke方法了。也就是MapperProxy的invoke方法了。
最后把实例化mapper的bean放到bean缓存池中,至此,实例化mapper结束。
然后就是前端发请求调用controller,到service,到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是SqlSessionInterceptor代理创建的,所以,接下来走SqlSessionInterceptor.invoke方法,通过上面实例化sqlSessionTemplate可以看出,实际用的是反射机制,执行sqlSession方法。
继续执行selectone
这里创建的sqlSession是与数据库交互的会话sqlSession,注意与sqlSessionTemplate这个sqlSession的区别,然后通过method.invoke反射调用到具体的 DefaultSqlSession.selectList 方法。
最终调用Executor方法执行,Executor有两个方法:BaseExecutor 和 CachingExecutor,分别代表两种缓存机制,BaseExecutor 是一级缓存机制使用,CachingExecutor是二级缓存机制使用(如果查询二级缓存中没有结果,则会调用BaseExecutor的方法查询一级缓存,然后把查询结果放到二级缓存)。
所以先来到二级缓存查询
继续执行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);
}
}
SimpleStatementHandler ,这个对应的 就是JDBC 中常用到的 Statement 接口,用于简单SQL的处理
PreparedStatementHandler , 这个对应的就是JDBC中的 PreparedStatement,用于预编译SQL的处理
CallableStatementHandler , 这个对应JDBC中 CallableStatement ,用于执行存储过程相关的处理
RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用
最后获取结果
这里为了测试改了下sql语句
数据库对应数据
这样一次完整的查询就走完了。
流程:
执行mapper方法,实际就是通过jdk代理机制,执行MapperMethod的execute方法。然后通过sqlSessionTemplate的jdk代理机制,执行method.invoke方法。然后通过反射机制,执行DefaultSqlSession.selectList方法。通过configuration.getMappedStatement获取MappedStatement,这时找到了实际的sql了,然后到了Executor模块。执行SimpleExecutor的doQuery方法,然后执行PreparedStatementHandler的query方法,最终调用jdbc操作执行sql,最后解析结果并返回。
需要处理的问题应该就是:mapper是一个接口,接口不能实例化,也就是不能干活,想让他干活的话就要用动态代理,找一个代理类帮他干活。mapper.xml有具体的sql,想让它执行就要把mapper接口的方法和xml中的sql关联起来
。
springboot整合mybaits的问题
mapperscan注解或mapper注解,并且mybatis要重写isCandidateComponent方法,保证是接口也要生成beanDefinition
注册的时候将mapper接口的类型转换为MapperFactoryBean,然后通过MapperFactoryBean的getObject方法实现实例化(通过jdk代理生成了bean的代理对象)。
初始化的过程,创建SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,这是基础。在此过程,通过MapperScannerConfigurer扫描指定包下的所有mapper接口生成beanDefinition,并放到bean定义注册表中(类型为:MapperFactoryBean)。
执行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