一、环境配置
1.1 pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
1.2 启动类(Application)加上@MapperScan(“Mapper接口所在包路径”)
1.3 application.yml
spring:
application:
name: account-service
datasource:
url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
username: root
password: root
Springboot默认采用的数据源类型是HikariDataSource,全部采用默认配置即可
1.4 BlogMapper.java
在MapperScan扫描路径下新建
@Mapper
public interface BlogMapper {
List<Blog> selectBlog(String deptId);
}
二、Mapper的注入过程
2.1 @MapperScan
先看看注解@MapperScan,上面有一行@Import(MapperScannerRegistrar.class),说明它依赖MapperScannerRegistrar.class,spring会将它实例化
2.2 MapperScannerRegistrar.java
MapperScannerRegistrar.java实现了ImportBeanDefinitionRegistrar接口,因此会回调registerBeanDefinitions方法,往下走会进入重载方法 registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));
可以看出这个方法就是要将MapperScannerConfigurer.class注入容器
在注入之前给basePackage属性设置了值,就是Mapper包路径
MapperScannerConfigurer进入了registry。
2.3 MapperScannerConfigurer.java
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,在注入registry之后就会执行postProcessBeanDefinitionRegistry方法
进入
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));–>doScan(basePackages);–>Set
<BeanDefinitionHolder>
beanDefinitions = super.doScan(basePackages);
这块就是扫描指定包路径下的所有class文件,放入beanDefinitions返回,回到ClassPathMapperScanner.doScan,从processBeanDefinitions(beanDefinitions);进入
红框中给definition设置了属性mapperInterface,并赋值为Mapper.class。然后才将definition(原来是Mapper.class)改成了 MapperFactoryBean.class
2.3.1 Mapper需要加@Repository注解吗
回到postProcessBeanDefinitionRegistry方法看这里
进入 scanner.registerFilters();
这里添加了一个includeFilters,意思是所有class都被扫描到,因此Mapper不需要添加@Repository注解
2.4 MapperFactoryBean.java
MapperFactoryBean继承至SqlSessionDaoSupport,并实现FactoryBean接口,这里用的就是适配器模式。MapperFactoryBean有了FactoryBean包装真正bean的功能(spring容器会对实现了FactoryBean的接口特殊处理,会调用它的getObject方法返回真实bean),同时又有了SqlSessionDaoSupport操作SqlSession 的功能。
MapperFactoryBean里面有些属性,可以看到在processBeanDefinitions(beanDefinitions)都有做设置
执行完processBeanDefinitions(beanDefinitions)之后,definition的className还是BlogMapper,真实类型是MapperFactoryBean,那么当BlogMapper需要注入时就会调用MapperFactoryBean的getObject。
最终还是从SqlSession的Configuration里面拿到Mapper的代理实现(由代理工厂MapperProxyFactory产生的MapperProxy),那么Mapper是何时放入的呢?接着看SqlSessionDaoSupport
2.5 SqlSessionDaoSupport.java
持有SqlSessionTemplate
SqlSessionTemplate实现SqlSession,它里面有SqlSessionFactory,MapperFactoryBean在实例化之前肯定要先将SqlSessionTemplate实例化,而SqlSessionTemplate实例化又依赖SqlSessionFactory,那么SqlSessionTemplate、SqlSessionFactory在哪里实例化呢?
2.6 MybatisAutoConfiguration.java
2.6.1 sqlSessionFactory
MybatisAutoConfiguration定义了一个bean(SqlSessionFactory),进入
return factory.getObject();–>afterPropertiesSet();–> this.sqlSessionFactory = buildSqlSessionFactory();
这块就是给Configuration设置了一些默认值,再返回一个DefaultSqlSessionFactory
2.6.2 sqlSessionTemplate
这个方法的参数sqlSessionFactory就是上面产生的DefaultSqlSessionFactory,进入
return new SqlSessionTemplate(sqlSessionFactory);–> this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());–> this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
得到sqlSessionTemplate4085,持有一个sqlSessionProxy,代理它自己。再用setter方法注入到MapperFactoryBean的sqlSessionTemplate
看参数也是sqlSessionTemplate4085
2.7 DaoSupport.java
SqlSessionDaoSupport继承至DaoSupport
DaoSupport实现InitializingBean,在MapperFactoryBean初始化之后执行afterPropertiesSet,进入
checkDaoConfig();–> configuration.addMapper(this.mapperInterface);
拿到Configuration,将Mapper添加进去
2.8 注入Mapper调用getObject
此时Configuration里面已经有了Mapper
取出来赋值给service的属性
三、Mapper方法执行
执行service的方法调用Mapper接口的方法
前面讲过的MapperProxy是3.5.4版本,最新版的Mybatis有点小改动。但还是先产生MapperMethod,然后在MapperMethod里面用sqlSession执行,注意这里的SqlSession还是SqlSessionTemplate。进入
mapperMethod.execute(sqlSession, args);–>result = executeForMany(sqlSession, args);–> result = sqlSession.selectList(command.getName(), param);
sqlSessionProxy代理SqlSessionTemplate自己(为了每个方法都拦截,产生新的SqlSession),继续执行
每次执行方法前,获取真实的sqlSession,getSqlSession里面会调用sqlSessionFactory的openSession。因此每次执行mapper的方法都会打开新的SqlSession。继续执行就到了Mybatis里面了,前面几篇已经讲过。
四、总结
- 扫描mapper包
- 生成beanDefinition
- 放入BeanDefinitionRegistry
- 修改beanDefinition的class为MapperFactoryBean
- MapperFactoryBean的getObject,会返回从SqlSession的Configuration里面拿到Mapper的代理实现
- 在getObject之前,MapperFactoryBean应该已经存在SqlSession和Configuration
- 在MybatisAutoConfiguration中就配置了SqlSessionTemplate(就是SqlSession)、SqlSessionFactory(包含Configuration)
- MapperFactoryBean实例化时,将SqlSessionTemplate、SqlSessionFactory注入
- 实例化之后注入Service层时调用MapperFactoryBean的getObject,从SqlSession持有的Configuration里拿到mapperRegistry,再取出MapperProxyFactory,调用newInstance生成MapperProxy返回,赋值给Service层的属性
- 执行Mapper方法, SqlSessionTemplate的自身代理拦截方法,打开新的SqlSession再执行