前面梳理了下MyBatis在单独使用时的工作流程和关键源码,现在看看MyBatis在和Spring整合的时候是怎么工作的

也先从使用开始

Spring整合MyBatis

1.引入依赖,除了MyBatis的依赖,还需要引入 mybatis-spring依赖
2.在spring的配置文件applicationContext.xml里配置SqlSessionFactoryBean,从名字可以看出我们是通过这个Bean来创建SqlSessionFactory

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:mybatis-config.xml"></property><property name="mapperLocations" value="classpath:mapper/*.xml"></property><property name="dataSource" ref="dataSource"/></bean>

3.在applicationContext.xml配置扫描Mapper的路径

可以通过三种方式
1.配置MapperScannerConfigurer
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.XXX.XXX"/></bean>2.配置<scan>标签<context:component-scan base-package="com.XXX.XXX">3.使用@MapperScan注解

原理分析

创建会话工厂

先看下前面配置的SqlSessionFactoryBean,其类图如下所示

其中的InitializingBean有一个待实现方法afterPropertiesSet,会在初始化Bean之后调用,这里就是在afterPropertiesSet实现方法里创建了SqlSessionFactory对象

//在Bean初始化之后调用
public void afterPropertiesSet() throws Exception {Assert.notNull(this.dataSource, "Property 'dataSource' is required");Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");//创建SqlSessionFactorythis.sqlSessionFactory = this.buildSqlSessionFactory();}
 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {XMLConfigBuilder xmlConfigBuilder = null;Configuration targetConfiguration;//如果configuration已经存在,则把当前Bean的Properties属性也加入到Configurationif (this.configuration != null) {targetConfiguration = this.configuration;if (targetConfiguration.getVariables() == null) {targetConfiguration.setVariables(this.configurationProperties);} else if (this.configurationProperties != null) {targetConfiguration.getVariables().putAll(this.configurationProperties);}//如果configuration不存在,但是存在configLocation,则使用XmlConfigBuilder解析对应的配置文件} else if (this.configLocation != null) {xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();} else {//如果Configuration对象不存在,configLocation路径也没有,则使用默认的configurationProperties给configuration赋值LOGGER.debug(() -> {return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";});targetConfiguration = new Configuration();Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);}//基于当前factoryBean里已有的属性,对targetConfiguration对象里面的属性进行赋值Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);String[] typeHandlersPackageArray;if (StringUtils.hasLength(this.typeAliasesPackage)) {typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);LOGGER.debug(() -> {return "Scanned package: '" + packageToScan + "' for aliases";});});}if (!ObjectUtils.isEmpty(this.typeAliases)) {Stream.of(this.typeAliases).forEach((typeAlias) -> {targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);LOGGER.debug(() -> {return "Registered type alias: '" + typeAlias + "'";});});}if (!ObjectUtils.isEmpty(this.plugins)) {Stream.of(this.plugins).forEach((plugin) -> {targetConfiguration.addInterceptor(plugin);LOGGER.debug(() -> {return "Registered plugin: '" + plugin + "'";});});}if (StringUtils.hasLength(this.typeHandlersPackage)) {typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {targetConfiguration.getTypeHandlerRegistry().register(packageToScan);LOGGER.debug(() -> {return "Scanned package: '" + packageToScan + "' for type handlers";});});}if (!ObjectUtils.isEmpty(this.typeHandlers)) {Stream.of(this.typeHandlers).forEach((typeHandler) -> {targetConfiguration.getTypeHandlerRegistry().register(typeHandler);LOGGER.debug(() -> {return "Registered type handler: '" + typeHandler + "'";});});}if (this.databaseIdProvider != null) {try {targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));} catch (SQLException var23) {throw new NestedIOException("Failed getting a databaseId", var23);}}Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);//如果XMLConfigBuilder不为空,就调用parse方法,这跟MyBatis里调用的一样if (xmlConfigBuilder != null) {try {xmlConfigBuilder.parse();LOGGER.debug(() -> {return "Parsed configuration file: '" + this.configLocation + "'";});} catch (Exception var21) {throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);} finally {ErrorContext.instance().reset();}}//创建environment,事务工厂(默认使用SpringManagedTransactionFactory)targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));//根据配置的MapperLocation 使用XMLMapperBuilder解析mapper.xml,把接口和对应的MapperProxyFactory注册到MapperRegistry 中。if (!ObjectUtils.isEmpty(this.mapperLocations)) {Resource[] var24 = this.mapperLocations;int var4 = var24.length;for(int var5 = 0; var5 < var4; ++var5) {Resource mapperLocation = var24[var5];if (mapperLocation != null) {try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());xmlMapperBuilder.parse();} catch (Exception var19) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);} finally {ErrorContext.instance().reset();}LOGGER.debug(() -> {return "Parsed mapper file: '" + mapperLocation + "'";});}}} else {LOGGER.debug(() -> {return "Property 'mapperLocations' was not specified or no matching resources found";});}//使用SqlSessionFactoryBuilder的build方法构建SqlSessionFactoryreturn this.sqlSessionFactoryBuilder.build(targetConfiguration);}

创建SqlSession

Spring在创建SqlSession的时候不是直接使用MyBatis的DefaultSqlSession,而是自己封装了一个SqlSessionTemplate,这是因为DefaultSqlSession不是线程安全的,所以Spring特性封装了一个线程安全的SqlSessionTemplate(线程安全问题在web场景下不可避免)

SqlSessionTemplate里创建的SqlSession是使用的jdk动态代理,所有方法的调用实际都是通过这个proxy来调用的

 private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());}public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));}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;//通过JDK动态代理创建代理对象this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());}//创建代理对象的InvocationHandler
private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() {}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获得SqlSession对象SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {//调用目标对象方法,实际调用了DefaultSqlSession的方法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;}}

关于SqlSessionTemplate是如何保证线程安全的参考这篇博客 MyBatis(六)SqlSessionTemplate是如何保证线程安全的

知道Spring中是使用SqlSessionTemplate来保证线程安全的,那么我们怎么获取这个Template并使用呢?在spring的早期版本,想要使用SqlSessionTemplate,需要让我们的dao类去继承SqlSessionDaoSupport,它持有一个SqlSessionTemplate 对象,并提供了getSqlSession的方法

public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSessionTemplate sqlSessionTemplate;public SqlSessionDaoSupport() { }//在继承SqlSessionDaoSupport的时候需要调用setSqlSessionFactory把SqlSessionFactory注入(也可以通过xml注入)public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);}}protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}public final SqlSessionFactory getSqlSessionFactory() {return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null;}//对外提供的获取SqlSessionTemplate的方法(SqlSessionTemplate也实现了SqlSession接口)public SqlSession getSqlSession() {return this.sqlSessionTemplate;}...
}

但是这样一来,我们的DAO层的接口,如果要操作数据库,就都需要实现SqlSessionDaoSupport去拿到一个SqlSessionTemplate

目前的spring早已经优化了使用SqlSessionTemplate的方式,可以直接使用@Autowired 在Service层自动注入的 Mapper 接口

接口的扫描注册

这些Mapper就是在applicationContext.xml配置MapperScannerConfigurer时注册上的

<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.XXX.XXX"/></bean>

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor(该接口就是用于在BeanDefinition注册后做一些后置处理,比放说修改BeanDefinition等)接口的postProcessBeanDefinitionRegistry方法

//MapperScannerConfigurer
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {this.processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.registerFilters();//扫描包路径,注册Mapperscanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));}-----scanner.scan最后会调用ClassPathMapperScanner的doScan方法public Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> {return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";});} else {//对BeanDefinitions进行处理的方法this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {Iterator var3 = beanDefinitions.iterator();while(var3.hasNext()) {BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> {return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";});//the mapper interface is the original class of the bean//but the actual class of the bean is  MapperFactoryBean    //构造方法参数添加为当前接口的类名  definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;//向BeanDefinition的属性加入SqlSessionFactory和sqlSessionTemplateif (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(() -> {return "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(() -> {return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";});}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {LOGGER.debug(() -> {return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";});//表示当前bean会自动填充setter方法的属性,容器中配置了SqlSessionFactory的bean,所以会调用setSqlSessionFactory()方法来注入属性。definition.setAutowireMode(2);}}}

从上面可以看到,我们扫描到的Mapper会把其BeanDefinition的BeanClass设置成MapperFactoryBean,所以在实例化的时候实际生成的也是MapperFactoryBean对象,而我们的MapperFactoryBean就继承了SqlSessionDaoSupport,而前面我们又向BeanDefinition里添加了SqlSessionTemplate和SqlSessionFactory的属性,所以生成的MapperFactoryBean实际就是一个继承了SqlSessionDaoSupport并且注入了SqlSessionTemplate和SqlSessionFactory的对象

接口注入的使用

前面说了,目前spring在使用Mapper的时候,直接在加了Service 注解的类里面使用@Autowired注入Mapper接口就好了

@Service
public class EmployeeService {@AutowiredEmployeeMapper employeeMapper;//按id查询public Employee getEmp(Integer id) {Employee employee = employeeMapper.selectByPrimaryKey(id);return employee;}
}

spring在启动的时候会去实例化EmployeeService,而EmployeeService里又需要注入EmployeeMapper对象,
Spring会根据Mapper的名字从BeanFactory 中获取它的BeanDefination,再从BeanDefination 中 获 取 BeanClass ,此时其BeanClass已经被替换成MapperFactoryBean了

接下来就只需要创建MapperFactoryBean实例对象就可以了,因为MapperFactoryBean实现了FactoryBean接口,所以会通过getObject()来获取对应的实例对象

 //实际调用的是SqlSession(这里的返回的对象是SqlSessionTemplate)的getMapper方法public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);}----SqlSessionTemplate 这里调用了Configuration的getMapperpublic <T> T getMapper(Class<T> type) {return this.getConfiguration().getMapper(type, this);}----Configuration 最终是通过configuration的mapperRegistry获得对应的Mapper,实际拿到的是
通过工厂类MapperProxyFactory获得的用MapperProxy增强的代理对象public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession);}

总结一下:

关键类 功能
SqlSessionTemplate Spring 中 线程安全的SqlSession实现类,通过代理的方式调用 DefaultSqlSession 的方法
SqlSessionInterceptor(内部类) 定义在在 SqlSessionTemplate ,用来增强代理 DefaultSqlSession
SqlSessionDaoSupport 用于获取 SqlSessionTemplate,需要继承它并且注入SqlSessionFactory
MapperFactoryBean 注册到 IOC 容器中的Mapper接口替换类,继承了 SqlSessionDaoSupport 可以用来获取SqlSessionTemplate,注入接口的时候,会调用用它的 getObject()方法 最终调用Configuration的getMapper方法
SqlSessionHolder 管理SqlSession,帮助实现spring中SqlSession的线程安全

MyBatis(五)MyBatis整合Spring原理分析相关推荐

  1. MyBatis整合Spring原理分析

    目录 MyBatis整合Spring原理分析 MapperScan的秘密 简单总结 假如不结合Spring框架,我们使用MyBatis时的一个典型使用方式如下: public class UserDa ...

  2. MyBatis处理多参数及原理分析

    一.多参数处理方式 1.1使用@Param注解 MyBatis 允许在mapper 接口中使用@Param注解来处理多个参数. mapper 接口: /** @Param 注解中的值可以是任意的 */ ...

  3. Mybatis3.4.x技术内幕(二十二):Mybatis一级、二级缓存原理分析

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis的一级缓存,指的是SqlSession级别的缓存,默认开启:Mybatis的二级缓存,指的是SqlSession ...

  4. Mybatis Plus启动注入 SQL 原理分析

    1) 问题: xxxMapper 继承了 BaseMapper<T>, BaseMapper 中提供了通用的 CRUD 方法, 方法来源于 BaseMapper, 有方法就必须有 SQL, ...

  5. mysql第五话 - mysql索引原理分析

    在工作中听到最多的一句话,sql查询太慢怎么办?加个索引吧! 今天来探索一下mysql的索引原理. 1.索引是什么? 可以毫不夸张的说,系统中sql的快慢,是能直接决定你系统的快慢的.但是sql的快慢 ...

  6. 《一周学完光线追踪》学习 十一点五 离焦模糊代码原理分析

    蒙特卡洛光线追踪技术系列 见 蒙特卡洛光线追踪技术 首先分析一下生成随机Ray的程序: vec3 random_in_unit_disk() {vec3 p;do {p = 2.0*vec3(rand ...

  7. Mybatis 与Spring整合及原理

    Mybatis 与Spring原理分析 http://www.mybatis.org/spring/zh/index.html 这里我们以传统的Spring 为例,因为配置更直观,在Spring 中使 ...

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

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

  9. Spring原理学习系列之三:Spring AOP原理(从源码层面分析)-------上部

    引言 本文是Spring原理分析的第三篇博文,主要阐述Spring AOP相关概念,同时从源码层面分析AOP实现原理.对于AOP原理的理解有利于加深对Spring框架的深入理解.同时我也希望可以探究S ...

最新文章

  1. Eclipse的java代码出错:The import org.apache cannot be resolved
  2. 【洛谷习题】南蛮图腾
  3. 《scikit-learn》交叉验证
  4. Identity Server 4 原理和实战(完结)_建立Angular 客户端
  5. Windows说明Linux分区和挂载点
  6. yousa_team团队项目——兼职平台网站 工作进度
  7. s3c2410多通道adc驱动及测试程序(使用write控制多通道)
  8. ogg格式怎么转mp3格式?
  9. 【高等数学笔记】格林公式、高斯公式、斯托克斯公式、场论
  10. iPhone手机越狱不只是为了安装盗版应用、越狱的十大好处
  11. Centos下安装FastDFS
  12. SpringCloud客户端Client启动时自动停止
  13. QT绘制不规则多边形
  14. 什么样的耳机戴着舒服些、最好用的的几款骨传导蓝牙耳机推荐
  15. 电脑硬盘分区不见了怎么恢复数据?方法来啦
  16. Dubbo源码分析:全集整理
  17. Python网络爬虫入门(五)—— 巧用抓包,爬遍SCU玻璃杯事件所有神回复
  18. 计算机公共课1-信息技术与计算机文化
  19. c和c++的一些训练题(4)(小学生比较国家面积)
  20. TiKV源码分析(一)RaftKV层

热门文章

  1. 列表隔行变色、显示和隐藏下拉菜单、列表的高亮显示效果
  2. mysql 界面 创建用户名和密码是什么_PLSQL操作Oracle创建用户和表(含创建用户名和密码)...
  3. Feign深入学习(二)
  4. 【note】fill函数和memset函数的区别和使用
  5. flask sqlalchemy一对多关系详解
  6. The 'microsoft.jet.oledb.4.0' provider is not registered on the local machin
  7. Dump文件:线程dump和堆dump
  8. 网络编程中如何得知一次请求(或响应)的数据已接收完
  9. Object与RTTI
  10. xcode6是否导入framework