一,前言
MyBatis可以独立于Spring使用,这里我们关注一下Mybatis和Spring是怎么整合在一起的。在Spring中使用MyBatis时我们只需要定义一个mapper接口,并配置好对应的mapper.xml,这样就可以直接通过mapper接口直接执行数据库操作。但是我们并没有手动的实例化该接口,那它是如何进行实例化并加入spring容器的呢?
二,Mybatis-Spring 1.3.2 版本源码执行流程
1,通过入口 @MapperScan
导入了 MapperScannerRegistrar
类,用于扫描 Mapper 接口并生成对应代理对象,注入到 IOC 容器;
@Configuration
@MapperScan({"com.base.core.mapper"})
public class MybatisPlusConfig {
}
// org.mybatis.spring.annotation.MapperScan
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
2,MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar
接口,所以 Spring 在启动时会调用MapperScannerRegistrar 类中的 registerBeanDefinitions()
方法,扫描接口并生成对应 BeanDefinition;
3,在 registerBeanDefinitions 方法中定义了一个 ClassPathMapperScanner
对象,用来扫描 mapper 接口,因为在 Spring 中是不会扫描接口的,同时因为 ClassPathMapperScanner 中重写了 isCandidateComponent
方法,默认只扫描接口;
// org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//定义ClassPathMapperScanner对象,用来扫描mapper接口
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 省略 ...
scanner.registerFilters();
// 扫描mapper接口,并生成对应 BeanDefinition
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
// org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
4,通过利用 Spring 的扫描后,接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean
,把 AutowireMode 修改为 byType ;扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean 了,相当于每个 Mapper 对应一个FactoryBean(单例);
// org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// BeanDefinition 加工
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
接下来看看 processBeanDefinitions(beanDefinitions) 方法
// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 把 BeanClass 修改为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 省略 ...
// 把 AutowireMode 修改为 byType
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
5,在 MapperFactoryBean
中 的 getObject()
方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象。
前面扫描步骤 doScan
已经设置 MapperFactoryBean 的 AutowireMode 为 byType,所以Spring会自动调用set方法。SqlSessionDaoSupport 有两个set方法,一个 setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的 bean,所以 Spring 容器中要存在 SqlSessionFactory
类型的 bean 或者 SqlSessionTemplate
类型的 bean;
如果你定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性;
// org.mybatis.spring.mapper.MapperFactoryBean#getObject
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
下面看看 SqlSessionDaoSupport 的 getSqlSession() 方法:
// org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
// 通过setSqlSessionFactory来设置 SqlSession
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
// 最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性;
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
// 通过 setSqlSessionTemplate 来设置 SqlSession
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
/**
* Users should use this method to get a SqlSession to call its statement methods
* This is SqlSession is managed by spring. Users should not commit/rollback/close it
* because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSession;
}
}
而在 SqlSessionTemplate 类中就存在一个 getMapper
方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象;到时候,当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程。
// org.mybatis.spring.SqlSessionTemplate#getMapper
public class SqlSessionTemplate implements SqlSession, DisposableBean {
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
}
流程简图:
三,Mybatis-Spring 2.0.6 版本源码执行流程
- 通过
@MapperScan
导入了MapperScannerRegistrar类 - MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动 时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法
- 在registerBeanDefinitions方法中注册一个MapperScannerConfigurer类型的 BeanDefinition
- 而MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以 Spring在启动过程中时会调用它的postProcessBeanDefinitionRegistry()方法
- 在postProcessBeanDefinitionRegistry方法中会生成一个ClassPathMapperScanner对象,然 后进行扫描
- 后续的逻辑和1.3.2版本一样。 带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.base.core.mapper");
return mapperScannerConfigurer;
}
评论区