目录
  • MyBatis整合Spring原理分析
  • MapperScan的秘密
  • 简单总结


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

public class UserDaoTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void setUp() throws Exception{ClassPathResource resource = new ClassPathResource("mybatis-config.xml");InputStream inputStream = resource.getInputStream();sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic 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@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic 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 {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//这步相当于调用sqlSessionFactory的openSesion方法获得SqlSessionSqlSession 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 #22closeSqlSession(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) {//最后强制关闭SqlSessioncloseSqlSession(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 "";//指定自定义的MapperFactoryBeanClass<? 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.1if (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. MyBatis(五)MyBatis整合Spring原理分析

    前面梳理了下MyBatis在单独使用时的工作流程和关键源码,现在看看MyBatis在和Spring整合的时候是怎么工作的 也先从使用开始 Spring整合MyBatis 1.引入依赖,除了MyBati ...

  2. 【Mybatis+spring整合源码探秘】--- mybatis整合spring事务原理

    文章目录 1 mybatis整合spring事务原理 1 mybatis整合spring事务原理 本篇文章不再对源码进行具体的解读了,仅仅做了下面一张图: 该图整理了spring+mybatis整合后 ...

  3. Mybatis——Mybatis整合Spring详解

    mybatis-spring官网 1. MyBatis整合Spring实现 我们首先实现MyBatis和Spring的整合操作. 1.1 添加相关的依赖 这些是整合的依赖,不包括其他分页插件等依赖. ...

  4. MyBatis整合Spring的实现(2)

    2019独角兽企业重金招聘Python工程师标准>>> 分析 MyBatis整合Spring的实现(1)中代码实现的4.1可以知道,XMLConfigBuilder类读取MyBati ...

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

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

  6. Spring学习笔记:Spring整合Mybatis(mybatis-spring.jar)(二:mybatis整合spring)

    http://blog.csdn.net/qq598535550/article/details/51703190 二.Spring整合mybatis其实是在mybatis的基础上实现Spring框架 ...

  7. Mybatis整合spring

    整合思路 1.SqlSessionFactory对象应该放到spring容器中作为单例存在. 2.传统dao的开发方式中,应该从spring容器中获得sqlsession对象. 3.Mapper代理形 ...

  8. mybatis 整合spring之mapperLocations配置的问题

    今天尝试spring整合mybatis时遇到这么一个问题,就是在配置sqlSessionFactory时是否要配置mapperLocations的问题. <bean id="sessi ...

  9. mybatis+springMVC+spring原理及工作流程

    做自己没做过的事情叫做成长 做自己不愿做的事情叫做改变 做自己不敢做的事情叫做突破 共勉 引言 本人在学习ssm中,已经能熟练的编写配置文件与代码了.但于前几天朋友问其原理与工作流程时却只能答出一二, ...

最新文章

  1. Unity 播放音频文件
  2. leetcode算法题--最长数对链
  3. android view绘制过程
  4. python基础课程1(看代码看注释)--基本操作和数据类型
  5. JS学习笔记 等于和包装对象
  6. 牛客网练习赛26B(简单的dp)
  7. Unity3D实现按钮切换Panel的功能
  8. c语言做心理测试程序,求各位大神赐教!我做了一个“心理测试的答题卷”编程,总共有1...
  9. 【html笔记】html介绍和语法入门
  10. Pacemaker 安装与使用
  11. JavaScript 弹出窗口代码大全
  12. 【数据集】语义分割常用的数据集: Pascal VOC、Cityscape、MSCOCO
  13. linux pkg解压工具,Pkg 1.3.0 发布,FreeBSD 的包管理工具
  14. 2015境外人气餐厅榜单!你吃过几家?
  15. 存储器——嵌入式系统
  16. iverilog 提示 Unknown module type 解决办法
  17. 使用DirectX打造游戏GUI界面(一)
  18. 团贷网爆雷启示录:事发前早有预兆 激进资本运作致败局
  19. php get your hands dirty,BBC地道英语:To Get your Hands Dirty 亲自动手
  20. 双屏计算机主机是什么,双屏显示器怎么连接(台式机连接步骤教程) - 双屏显示器怎么设置_双屏显示器怎么连接_双屏显示器有什么用(电脑)...

热门文章

  1. Linux: Shell编程基础
  2. 深入理解Magento – 第七章 – 自定义Magento系统配置
  3. 基于PHP的Google Voice 短信API
  4. 特殊符号的写法 (HTML 4.01 符号实体)
  5. php 操作文件夹 (遍历 计算大小)
  6. Shell编程之变量
  7. 【AI视野·今日CV 计算机视觉论文速览 第190期】Fri, 9 Apr 2021
  8. 数据库的四大特性和事务隔离级别
  9. mysql 中文乱码解决办法
  10. mysql学习笔记 查找技术 1207 0311