假如不结合Spring框架,我们使用MyBatis时的一个典型使用方式如下:

public class UserDaoTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
public void setUp() throws Exception{
ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} @Test
public void selectUserTest(){
String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
SqlSession sqlSession = sqlSessionFactory.openSession();
CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);
Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);
System.out.println(cbondissuer);
sqlSession.close();
} }

我们首先需要SqlSessionFactory,然后通过SqlSessionFactory的openSession方法获得SqlSession。通过SqlSession获得我们定义的接口的动态代理类(MapperProxy)。当我们整合Spring框架时,我们使用MyBatis的方式简单的“难以想象”:

@Autowore
private CbondissuerMapper cbondissuerMapper;

非常Spring形式的使用方式,直接注入就可以了。那这个是怎么实现的呢?Spring是怎么把上面略显复杂的模板代码省略的呢?我的第一直觉是Spring在启动的时候做了Mybatis的初始化工作,然后一次性获取了所有Mapper接口的动态代理实现类并将其放入Spring容器中进行管理。

下面来验证下自己的猜想对不对。

MyBatis整合Spring原理分析

下面以Spring Boot中的MyBatisAutoConfigration为列子做下简单分析:

  //SqlSessionFactoryBean的最终作用就是解析MyBati配置文件,并最终生成Configration对象,然后生成DefaultSqlSessionFactory,并加入Spring的容器管理。可以看出SqlSessionFactoryBean的作用和SqlSessionFactoryBeanBuilder的作用很像。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
} return factory.getObject();
} //创建SqlSessionTemplate这个Bean,这个类动态代理了DefaultSqlSession,所以最后还是调用了DefaultSqlSession,下面会重点分析下SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

以下是SqlSessionTemplate的源代码,由于源码较多,只贴出重点部分:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;
//SqlSessionTemplate动态代理了DefaultSqlSession,所用对sqlSessionProxy的调用都会经过代理对象
private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; .... public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
} ....
//对sqlSessionProxy的CRUD调用都会先调用到这里
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这步相当于调用sqlSessionFactory的openSesion方法获得SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//调用DefaultSqlSession的CRUD方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
//最后强制关闭SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}

到此为止,整个Spring中获取SqlSessionFactory、获得SqlSession、执行CRUD、以及关闭SqlSession的流程都进行分析了。还有一个有疑问的地方就是Mapper接口的实现类是在什么时候动态生成的。

MapperScan的秘密

我们知道MapperScan是用来扫描Mapper接口的,所以很自然的想到去MapperScan这个类里面一探究竟。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//秘密在MapperScannerRegistrar里面
@Import(MapperScannerRegistrar.class)
public @interface MapperScan { String[] value() default {};
//设置扫描的包
String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends Annotation> annotationClass() default Annotation.class; Class<?> markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default "";
//指定自定义的MapperFactoryBean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class; }

下面就看下MapperScannerRegistrar里面做了什么:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//创建了一个ClassPathMapperScanner,并根据@MapperScan里面的配置设置属性
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(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(BeanUtils.instantiateClass(generatorClass));
} Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
} scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
//开始扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}

以上创建了一个ClassPathMapperScanner,并根据@MapperScan里面的配置设置属性,开始扫描。下面再看ClassPathMapperScanner。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//对扫描到的BeanDefinition做进一步处理
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//这边是重点,将扫描到的Mapper接口的BeanClass设置为MapperFactoryBrean
//MapperFactoryBrean是工厂Bean,用于生成MapperProxy;
//Spring在自动注入Mapper的时候会自动调用这个工厂Bean的getObject方法,生成MapperProxy并放入Spring容器。
definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
} if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

到此Spring自动生成Mapper接口实现类的过程也分析完了。

简单总结

通过以上分析,印证了一开始的猜想:Spring在启动的时候做了Mybatis的初始化工作,然后一次性获取了所有Mapper接口的动态代理实现类并将其放入Spring容器中进行管理。大致流程如下:

  • 配置SqlSessionFactoryBean,这个Bean的最终作用是创建DefaultSqlSessionFactory,并将DefaultSqlSessionFactory加入Spring容器管理;
  • 创建SqlSessionTemplate,这个对象代理了MyBatis中的DefaultSqlSession,通过SqlSessionTemplate调用任何CRUD方法都会经历openSession、调用DefaultSqlSession的CRUD方法和关闭Session的过程;

Mapper接口生成的流程大致如下:

  • @MapperScan注解扫描到所有的Mapper接口生成相应的BeanDefinition;
  • ClassPathMapperScanner的处理方法对这些BeanDefinition做进一步配置,其中最终要的一步就是将这些BeanDefinition的Beanclass属性设置为MapperFactoryBean(这是一个工厂Bean,专门用于生成Mapper的动态代理实现MapperProxy,一个Dao接口会对应一个MapperFactoryBean);
  • Spring在检测到需要自动注入Mapper时通过层层调用,最终会调用到MapperFactoryBean这个工厂Bean的getObject方法生成对应的MapperProxy,并将这个对象纳入Spring管理。

以上大致就是MyBatis整合进Spring的原理。我们发现其实质和传统的Mybatis使用是一样的,只不过是通过Spring做了一些自定配置等。

分析的有点乱,先这样吧~

MyBatis整合Spring原理分析的更多相关文章

  1. JAVAEE——Mybatis第二天:输入和输出映射、动态sql、关联查询、Mybatis整合spring、Mybatis逆向工程

    1. 学习计划 1.输入映射和输出映射 a) 输入参数映射 b) 返回值映射 2.动态sql a) If标签 b) Where标签 c) Sql片段 d) Foreach标签 3.关联查询 a) 一对 ...

  2. mybatis整合spring 之 基于接口映射的多对一关系

    转载自:http://my.oschina.net/huangcongmin12/blog/83731 mybatis整合spring 之  基于接口映射的多对一关系. 项目用到俩个表,即studen ...

  3. Mybatis接口编程原理分析(三)

    前面两篇博客Mybatis接口编程原理分析(一)和Mybatis接口编程原理分析(二)我们介绍了MapperProxyFactory.MapperProxy和MapperMethod的操作及源码分析, ...

  4. Mybatis接口编程原理分析(二)

    在上一篇博客中 Mybatis接口编程原理分析(一)中我们介绍了MapperProxyFactory和MapperProxy,接下来我们要介绍的是MapperMethod MapperMethod:它 ...

  5. mybatis整合spring获取配置文件信息出错

    描述:mybatis整合spring加载jdbc.properties文件,然后使用里面配置的值来 配置数据源,后来发现用户变成了admin- jdbc.properties的配置: 加载配置: 报错 ...

  6. Mybatis整合Spring -- typeAliasesPackage

    Mybatis整合Spring 根据官方的说法,在ibatis3,也就是Mybatis3问世之前,Spring3的开发工作就已经完成了,所以Spring3中还是没有对Mybatis3的支持. 因此由M ...

  7. 160330、Mybatis整合Spring

    转自csdn文章 http://haohaoxuexi.iteye.com/blog/1843309 Mybatis整合Spring 根据官方的说法,在ibatis3,也就是Mybatis3问世之前, ...

  8. (转)MyBatis框架的学习(六)——MyBatis整合Spring

    http://blog.csdn.net/yerenyuan_pku/article/details/71904315 本文将手把手教你如何使用MyBatis整合Spring,这儿,我本人使用的MyB ...

  9. 不需要怎么修改配置的Mybatis整合Spring要点

    首先对于Mybatis的主配置文件,只需要修改一处地方,将事务交给Spring管理,其它地方可以原封不动. <?xml version="1.0" encoding=&quo ...

随机推荐

  1. 【Java】机考常用知识

    基本操作 数组 声明数组 方法一: int a[] = null; //声明一维数组 //int[] a = null; 也行,个人习惯 a = new int[10];//分配内存给一维数组 方法二 ...

  2. 峰哥说技术:08-Spring Boot整合FreeMarker视图

    Spring Boot深度课程系列 峰哥说技术—2020庚子年重磅推出.战胜病毒.我们在行动 08  峰哥说技术:Spring Boot整合FreeMarker视图 前面带着大家整合了Thymelea ...

  3. js中的this和arguments.callee

    this和 arguments.callee this 全局作用域下,this指向Window 其他情况下,谁调用this就指向谁 console.log(this) //对调用就指向谁系列 func ...

  4. 19 JPQL

    使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Qu ...

  5. IntelliJ IDEA 2020 全家桶注册码

    WU78YHTY7E-eyJsaWNlbnNlSWQiOiJPUVQzT0oyNVhFIiwibGljZW5zZWVOYW1lIjoi5rC45LmF5r+A5rS7IGlkZWEubWVkZW1pb ...

  6. Python学习之布尔和数字

    布尔有True和Flase两种值 数字0.None,以及元素为空的容器类对象都可视为False,反之为Ture.

  7. OpenGL 实践之贝塞尔曲线绘制

    说到贝塞尔曲线,大家肯定都不陌生,网上有很多关于介绍和理解贝塞尔曲线的优秀文章和动态图. 以下两个是比较经典的动图了. 二阶贝塞尔曲线: 三阶贝塞尔曲线: 由于在工作中经常要和贝塞尔曲线打交道,所以简 ...

  8. 通过欧拉计划学Rust编程(第500题)

    由于研究Libra等数字货币编程技术的需要,学习了一段时间的Rust编程,一不小心刷题上瘾. "欧拉计划"的网址: https://projecteuler.net 英文如果不过关 ...

  9. C++ 按行读取文件并打印

    #include<iostream> #include<fstream> #include<string> #include <vector> #inc ...

  10. 面试官:HashMap死循环形成的原因是什么?

    介绍 HashMap实现原理 之前的文章已经分析了HashMap在JDK1.7的实现,这篇文章就只分析HashMap死循环形成的原因 死循环形成是在扩容转移元素的时候发生的 void resize(i ...