上一篇文章简单分析了mybatis的原理。实际开发过程中spring帮我们简化了Mybatis的配置。Springboot更是简化了xml配置,更多地使用注解来配置spring容器。这篇文章分析一下springBoot在整合mybatis的时候做了哪些事情。
在项目中,SpringBoot启动类中加入注解MapperScan扫描包路径下定义的mapper接口。
@MapperScan("com.xxx.mapper")
public class ServiceApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ServiceApplication.class).web(true).run(args);
}
}
这个注解引入了MapperScannerRegistrar
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {}
IOC容器启动的时候会调用它的registerBeanDefinitions方法。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
String[] var10 = annoAttrs.getStringArray("value");
int var11 = var10.length;
int var12;
String pkg;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
var10 = annoAttrs.getStringArray("basePackages");
var11 = var10.length;
for(var12 = 0; var12 < var11; ++var12) {
pkg = var10[var12];
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
var11 = var14.length;
for(var12 = 0; var12 < var11; ++var12) {
Class> clazz = var14[var12];
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
这行代码不做详细的解释,注意看最后调用的MapperFactoryBean的doScan方法。MapperFactoryBean实现了FactoryBean接口
public Set doScan(String... basePackages) {
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
processBeanDefinitions方法关键代码
definition.setBeanClass(this.mapperFactoryBean.getClass());
为什么要将接口的Bean设置为MapperFactoryBean呢,就是因为当IOC容器中Bean实现了FactoryBean后,通过getBean获取到的Bean对象并不是FactoryBean的实现类对象,而是实现类getObject方法返回的对象。MapperFactoryBean类的getObject方法如下:
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
这跟上一篇文章getMapper的方法是一致的。不过addmapper是在checkDaoConfig方法里执行的。
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
MapperFactoryBean继承了SqlSessionDaoSupport类,在processBeanDefinitions时会设置SqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
SqlSessionTemplate这个类就是Spring整合Mybatis的核心
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
seqlSessionProxy生成代理对象,调用SqlSession的方法。创建代理时用到了SqlSessionInterceptor这个内部拦截类。调用的时候会执行它的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
首先调用SqlSessionUtils的静态方法getSqlSession,然后调用相应方法,检测事物是否有spring管理。并根据此结果决定是否提交事物,最后清除Session。
SpringBoot在启动时MybatisAutoConfiguration自动配置Mybatis的参数。通过SqlSessionFactoryBean.getObject获取SqlSessionFactory对象。
总结一下Mapper的创建过程。
1、IOC通过注解扫描指定包,在初始化的时候调用@MapperScan注解,执行doScan方法,将所有的Mapper接口的Bean定义为MapperFactoryBean,并将SqlSessionTemplate添加到该类中。
2.SpringIOC在实例化该Bean的时候,需要传入接口类型,并将SqlSessionFactory和SqlSessionTemplate注入到Bean中,并调用addmapper方法,解析配置文件。
3、当调用MapperFactoryBean的getObject方法的时候,事实上是调用SqlSession的getMapper方法。这个方法返回一个动态代理对象,所有这个代理对象的方法调用都是底层的SqlSession的方法。