阅读本文需要spring源码知识,和springboot相关源码知识
对于springboot 整合mybatis,以及mybatis源码关系不密切的知识,本文将简单带过
- 涉及到spring ioc原理,可移步学习:Spring源码学习笔记12——总结篇IOC,Bean的生命周期,三大扩展点
- 涉及spring aop原理,可参考 Spring 源码学习笔记10——Spring AOP
- 涉及spring 申明式事务,可参考 pring 源码学习笔记11——Spring事务
- 涉及到springboot自动装配的原理,可参考:SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
一丶从一个问题开始——读已提交情况下mybatis一级缓存造成的问题
上图中,已知道users1的size为5,那么users2的大小为多少昵?
我们暂且抛弃mybatis框架中的知识,从mysql事务隔离级别进行分析,test方法第一次查询到总数,然后重新开启一个事务插入了一条(require_new的传播级别),后续addOne方法将立即提交,再次查询的时候,test方法应该可以立马查询到已经提交的数据,应该比第一次输出的应该多1,这是事务隔离级别指导我们做出的判断
但是事实上是users1大小和 users2一样大,这是为什么昵?
我们看下控制台
发现mybatis并没有进行第二次数据库的查询,这时候我们应该意识到mybatis
具备缓存,从而导致第二次查询并没有访问数据库
也就是说 读已提交的隔离级别下,mybatis如果不关闭缓存将存在错误
(这里的缓存指的一级缓存,二级缓存普遍是不开的)
具体原理,笔者此文将到mybatis缓存后将进行解读,下面我们从springboot 和 mybatis整合,到mybatis执行原理展开讲mybatis的原理
二丶mybatis-springboot-starter的自动装配
通常springboot整合mybatis只需要引入如下依赖
简单描述就是SpringBoot启动的时候会读取META-INF/spring.factories中自动配置的类,加入到容器中,后续springboot会将这些类当作配置类进行解析
上图是mybatis-spring-starter的META-INF/spring.factories
,其中关键的是MybatisAutoConfiguration
1.导入SqlSessionTemplate,SqlSessionFactory
这里可以看到当容器中没有SqlSessionFactory
的时候,MybatisAutoConfiguration
会为我们注入一个SqlSessionFactory
,SqlSessionTemplate
同样如此。
这里我们简单提一下SqlSessionFactory
和SqlSessionTemplate
的作用
1.1.mybatis中的SqlSessionFactory
故名思意,它是创建SqlSession
的工厂
这里Spring构建SqlSessionFactory,使用了SqlSessionFactoryBean#getObject
它实现了InitializingBean
,但是由于没有被注入到容器中,所以其#afterProperties
并不会被spring容器回调,在此方法中会调用buildSqlSessionFactory
进行别名扫描,TypeHandler
注册,xml解析(调用XMLMapperBuilder#parse
),拦截器注册,并且指定事务工厂使用SpringManagedTransactionFactory(mybatis,spring事务结合的关键,后续详细解析)
等工作
那么什么是SqlSession
?
1.2.mybatis中的SqlSession
SqlSession
是mybatis操作数据库抽象出来的接口,它可以执行增删改查,提交事务,回滚事务,创建mapper。我们平时依赖注入的mapper,其实一个动态代理类,其底层其实是调用SqlSession
进行的数据库操作
1.3.mybatis-spring中的SqlSessionTemplate
这个类和上面两个类都不同,它是org.mybatis.spring
这个包下面的,一般是mybatis-spring
这个包会引入的类,它的作用是SqlSession,与Spring事务管理一起工作,以确保实际使用的SqlSession与当前Spring事务关联。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。
它是spring事务和mybatis事务结合的关键,后面用到了我们再详细唠唠
2.注入AutoConfiguredMapperScannerRegistrar
这里可以看到如果没有MapperFactoryBean
和MapperScannerConfigurer
这两个bean ,那么会import一个AutoConfiguredMapperScannerRegistrar
,我们简单说下这三个类的作用,后续用到了详细解析其原理
2.1MapperFactoryBean
MapperFactoryBean 是一个FactoryBean,FactoryBean
中有一个方法叫getObject
负责创建一个对象交给spring容器管理,通常我们定义的Controller,Service都具备实现类,而非一个接口,spring可以实例化一个service的实现,但是mybatis中的mapper往往是一个接口,spring不知道如何实例化这个mapper,这时候发现mapper的BeanDefinition
中标记了这个class是MapperFactoryBean
就会调用MapperFactoryBean#getObject
实例化一个mapper,这个mapper便是我们注入到service中使用的mapper,它是源mapper的动态代理实现类,从而在代理类中调用Sqlsesession
执行对应的sql操作
2.2AutoConfiguredMapperScannerRegistrar
在没有MapperScannerConfigurer
,mybatis自动装配会为我们注入它,它是一个ImportBeanDefinitionRegistrar
spring解析配置类的时候,若发现一个bean是ImportBeanDefinitionRegistrar
的实现,那么会调用其registerBeanDefinitions
方法,从而注入其他bean的BeanDefinition
,这里bean便是MapperScannerConfigurer
(ImportBeanDefinitionRegistrar注入的MapperScannerConfigurer
扫描的时候要求mapper标注@Mapper注解)
2.3 MapperScannerConfigurer
MapperScannerConfigurer
还可以使用@MapperScan
或者@MapperScans
注解,进行引入,若我们使用了@MapperScan
或者@MapperScans
,上面的AutoConfiguredMapperScannerRegistrar
将不会被Import,AutoConfiguredMapperScannerRegistrar
的作用便是默认配置一个MapperScannerConfigurer
是一个BeanDefinitionRegistryPostProcessor
spring容器在启动的时候,会回调它的postProcessBeanDefinitionRegistry
在这个方法里面会扫描所有的mapper接口,指定其class为MapperFactoryBean
,从而在后续的实例化中,调用MapperFactoryBean#getObject
生成mapper接口的动态代理对象
三丶mybatis扫描mapper接口,注册mapper的Beanfinition
1.MapperScannerConfigurer是如何被注册到spring容器中的
上文中,我们说到,如果我们没有使用@MapperScan
或者@MapperScans
注解标注在配置类上面,那么会默认添加一个MapperScannerConfigurer
,进行mapper接口的扫描注册工作
通常启动类都有这样的@MapperScan
@MapperScan上面存在@Import,会导入一个MapperScannerRegistrar
,这是一个ImportBeanDefinitionRegistrar
会在这里注册MapperScannerConfigurer
的bean定义信息
其实就是把@MapperScan注解上的配置,绑定到MapperScannerConfigurer
的属性上,
@MapperScan注解,可以指定mapper在的包,mapper接口必须标注的注解,Mapper接口动态代理对象生成使用的MapperFactoryBean
等
2.MapperScannerConfigurer 如何进行扫描注册mapper的
其实扫描注册的工作委托给了ClassPathMapperScanner
,调用scan
方法进行扫描注册
它是一个ClassPathBeanDefinitionScanner
的子类,ClassPathBeanDefinitionScanner就是负责包路径扫描,注册BeanDefinition的
这里的扫描调用了ClassPathBeanDefinitionScanner
的doScan方法,这个方法会根据包路径解析成Resouce
对象,然后根据路径下的类包装成BeanDefinition(ScannedGenericBeanDefinition)
重点看下processBeanDefinitions
这里最关键的是definition.setBeanClass(this.mapperFactoryBeanClass)
,即将mapper接口的BeanDefinition类型指定为MapperFactoryBean
,这样在spring后续实例化mapper的时候就调用MapperFactoryBean#getObject
方法进行实例化了
至此我们学习了SpringBoot是如何和mybatis进行结合的,下面总结成一图
四丶Mapper bean的实例化
当我们一个Service需要注入一个mapper的时候,会从Spring容器中找对应的实例,这时候边会涉及到这个mapper的实例化,但是我们mapper明明是一个接口呀,如何实例化昵?
虽然我们mapper是一个接口,但是注入到service属性上的是这个接口的实现类,它是mybatis动态代理后生成的对象。
这个实例化的入口便是AbstractBeanFactory#getBean
方法
1.获取mapper对应的BeanDefinition
这里获取的beanDefinition便是源自ClassPathMapperScanner
注册到容器中的
2.实例化MapperFactoryBean
我们上面说到过,实例化mapper需要调用MapperFactoryBean#getObject
,那么首先需要实例化一个MapperFactoryBean
这里实例化MapperFactoryBean边是使用的createBean
方法,然后Spring会使用反射调用构造方法实例化出MapperFactoryBean(Spring还存在使用CGLIB生成子类然后实例化的方式),其中调用的是
这个构造方法需要一个入参,表示Mapper接口类型,那么这个mapperInterface入参来自那么昵?ClassPathMapperScanner扫描完mapper接口,生成BeanDefinition后,还会在BeanDefinition中记录全限定类型,这个全限定类名将作为MapperFactoryBean的构造器入参
3.MapperFactory进行属性注入
上面我们得到一个MapperFactoryBean,但是它构造出一个mapper需要借助SqlSession,这里使用的SqlSession其实是SqlSessionTemplate
,我们指导MybatisAutoConfiguration会让容器中注入一个SqlSessionTemplate
,那么spring是如何把这个SqlSessionTemplate
设置到mapperFactoryBean的属性上的昵?
这一步就发生在populateBean
方法中,其会调用applyPropertyValues
,它会根据javaBean的内省,获取其需要SqlSessionFactory和SqlSessionTemplate,然后从容器中获取MybatisAutoConfiguration
注入的实例,进行反射调用Set方法注入
4.MapperFactory的初始化
MapperFactory的父类SqlSessionDaoSupport
继承自DaoSupport()
,其中DaoSupport
又实现了InitializingBean
,在Spring实例化MapperFactory,完成依赖注入后将回调InitializingBean#afterPropertiesSet
其中checkDaoConfig
方法被MapperFactoryBean重写
这里会调用configuration.addMapper
解析xml和mybaits相关的注解,然后进行注册和接口进行绑定,但是这一步解析xml操作通常不会真正进行,因为在创建SqlSessionFactory的时候已经进行了
5.调用MapperFactory#getObject实例化出一个mapper
实例化出一个Mapper接口的动态代理对象,调用的是SqlSessesionTemplate#getMapper
那么到底mapper方法调用的时是如何操作数据库的昵?这一点我们后面继续说
至此我们知道了我们service注入的mapper其实是mybatis使用动态代理生成的对象,表面是一个什么方法实现都没有的接口,其实是动态代理"负重前行",下图展示了一个mapper被创造出来的全流程
五丶Mybatis 和spring事务的结合
上面我们知道了xxMapper其实是一个jdk动态代理生成的对象 ,其InvocationHandler
是MapperProxy
当mapper被调用其接口中声明的方法的时候,会调用到InvocationHandler#invoke
这时候MapperProxy就会大显身手
1.MapperProxy#invoke
MapperProxy内部使用了一个Map缓存方法和对应的执行器(MapperMethodInvoker
),这个map通常来自MapperProxyFactory的ConcurrentHashMap属性。而真正方法的调用又委托给了MapperMethod#execute
,MapperMethod根据方法调用的类型(增删改查)调用MapperProxy中的属性SqlSession(spring环境下的sqlSession实现类是SqlSessionTemplate)`对应的方法
2.SqlSessionTemplate 与mybatis spring事务
SqlSessionTemplate实现了SqlSession
接口,但是真正进行数据库操作的时候,都是委托给属性SqlSessionProxy
,SqlSessionTemplate
存在的意义在于"模板"
——复用SqlSession,那么为什么需要复用,为何要复用?我们接着看下它的构造方法
可以看到,其内部的sqlSessionProxy
是一个动态代理类,我们看下SqlSessionInterceptor
,它是一个InvocationHandler
2.1 mybatis 和 spring结合后即使没有开启事务也能自动提交的原因
上图可以看到如果事务并非交给spring管理(调用mapper执行单条增删改查的数据库操作,会自动提交事务)在反射调用sqlsession方法后,会进行事务提交。
//上面无事务注解 下面这条语句会调用到sqlsession的动态代理对象,进行自动提交
public void test(){
xxxMapper.insertOne(xx);
}
笔者校招的时候,面试官问过这个问题,我尼玛扯到了mysql的自动提交
标签:mapper,事务,调用,SpringBoot,spring,如何,源码,mybatis,方法 From: https://www.cnblogs.com/cuzzz/p/16974644.html