Spring 整合 Mybatis 源码分析
2021-12-07 17:39:24 0 举报
Spring 整合 Mybatis 源码分析
作者其他创作
大纲/内容
所以,这个操作导致了 Spring 整合 Mybatis 时 一级缓存失效
MapperFactoryBean # getObject
Set<BeanDefinition> candidates = findCandidateComponents(basePackage)
ClassPathBeanDefinitionScanner 是 spring 的类,负责扫描 java 类封装为 BeanDefinition
for
获取 SqlSession,这里获取的 SqlSession ,就是 DefaultSqlSession
MapperFactoryBean
这个类是Mybatis提供的,继承了 spring 的类,实现 mapper 扫描
当使用 SqlSessionTemplate 作为实现类时,他并不像使用 DefaultSqlSession 一样直接进行查询,而是用一个代理对象执行接下来的步骤
此时就可以看出,当 spring 将userMapper 放到单例池里,实际存放的并不是 Mapper 本身,而是一个实现了 FactoryBean 接口的 工厂 bean
return this.sqlSession;
factory.getObject()
BeanDefinitionHolder holder : beanDefinitions
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;
将扫描到的 Mapper 接口封装成 BeanDefinition后注册进 BeanDefinitionMap,后边的流程作者在之前的作品 《Spring 源码分析》中已经详细画过,这里不再赘述
definition.setBeanClass(this.mapperFactoryBean.getClass())
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader)
首先看他的继承连:
执行 commit 操作,清空一级缓存
ClassPathBeanDefinitionScanner
processBeanDefinitions(beanDefinitions)
candidate instanceof AbstractBeanDefinition
这是 spring 提供的一个初始化 Bean 的接口,实现该接口的类必须实现他的 afterPropertiesSet 方法
ClassPathMapperScanner
构建 SqlSessionFactory
getSqlSession()
newInstance(mapperProxy);
getSqlSession().getMapper(this.mapperInterface)
此图较大,建议读者缩放后再观看
extenfs
SqlSessionTemplate # selectOne
准备一个 LinkedHashSet 存放 BeanDefinitionHolder,BeanDefinitionHolder 是可以看做一种数据结构,BeanDefinitionName,BeanDefinition 作为重要属性存放在这个类里
注意红框框住的两个接口
afterPropertiesSet()
问题一: @MapperScan 是如何扫描 mapper 接口的
之后的流程就是 创建 Configuration (mybatis 的全局配置对象),扫描 mapper 映射文件,创建 Mapper 接口的 代理对象,这里的流程作者在之前的作品 《Mybatis 源码分析》中详细阐述了 SqlSessionFactory 的创建流程,不清楚的朋友可以看看
executor.query
SqlSessionInterceptor # invoke
String basePackage : basePackages
获取 Mapper 接口的代理对象,后边的流程如有不解,见作者的 《Mybatis 源码分析》
总结: Mapper 接口在 Spring 扫描的时候,是以 XxxMapper.class 作为类的, 但在封装 BeanDefinition的时候,Mybatis 将 BeanDefinition 的 class 属性由 XxxMapper.class 手动改为 MapperFactoryBean<T>(泛型即使 Mapper.class),所以 Mapper 接口在 Spring 容器中实际存放的对象是 MapperFactoryBean, 当 sprng 需要向其他对象注入 Mapper 对象时, getBean(\"xxxMapper\")----->MapperFactoyBean ----> getObject ----> getMapper()
配置 SqlSessionFactory 时,并不是直接配置的 SqlSessionFactory,而是配的 SqlSessionFactoryBean
sqlSession.close()
true
sqlSession.commit(true)
SqlSessionDaoSupport
e
操作完后提交事务
FactoryBean
写在前面
sbd.setSource(resource)
return beanDefinitions
return userMapper.findUserById(id)
SqlSessionFactory 是 SqlSessionFactoryBean 的一个属性,如果不为空,则直接返回,如果为 null
问题三:Mapper 接口在Spring 中存放的 Bean 是一个什么样的对象, Spring 是如何注入 Mapper 接口的代理对象的?注入的对象和在容器里存放的对象是同一个对象吗?
问题四:为什么 Sping 整合 Mybatis 后,Mybaitis 的一级缓存会失效?
SqlSessionInterceptor 是SqlSessionTemplate 的一个私有内部类, 实现了 InvocationHandler 接口,是一个调用处理器,
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath)
this.font color=\"#f44336\
找到包中的 mapper 接口,并封装为 Set<BeanDefinition > 并返回
getBean(\"userMapper\")
注意,这里 SqlSession 的实现类用的是 DefaultSqlSession
执行数据库读写操作,详细流程见笔者另一幅作品《Mybatis 源码分析》
scanCandidateComponents(basePackage)
isCandidateComponent(metadataReader)
getMapper(this.mapperInterface)
设置 bean 的自动注入模型---按类型注入
sqlSession != null
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
return candidates
getObject()
InitializingBean
之前作者已经完成了 《Spring 源码分析》 和 《Mybatis 源码分析》两幅源码分析图,趁热打铁,将 Spring 和 Mybatis 整合的要点捋一下,在画这个图之前,作者一开始也不知道该从哪讲起,毕竟在这之前已经分析了 Spring 和Mybatis 的源码,如果按流程重头来,重复工作太多,作为一个码农,绝不重复造轮子,这是原则!(手动傲娇) 于是乎,作者准备抓 Spring 整合 Mybatis 的核心要点,以 提问题----看源码----解决问题 的方式详细阐述 ,希望读者们能够喜欢。 如有不同意见,可以留言讨论,或者加作者 QQ:275394982 WX:rabbitmq0(是零不是O) 最后,如果觉得这幅图对您有帮助,不妨点赞收藏,如果家有余粮,也不妨克隆支持一下!谢谢!
获取 sqlSession,众所周知,当 mybatis 单独使用不与 Spring 整合时,使用的 sqlSession 的实现类是 DefaultSqlSession,但与 Spring 整合之后,使用的实现类是 SlSessionTemplate,这二者有什么区别?这个问题先留在这儿,讲后面问题的时候再解决
implements
candidates.add(sbd)
这里作者就直接从 get userMapper 开始,如果对 Spring 属性注入不太了解的话可以看作者之前的作品 《Spring 源码分析》
判断是否需要交给 spring 管理
扫描解析 javaConfig 配置类
从 BeanDefinitionHolder 拿出 BeanDefinition
ClassPathMapperScanner # scanner
catch
判断这个 beanDefinition 是不是 AbstractBeanDefinition 的子类对象,在作者另一篇 《Spring源码解析》中有详细的继承图,不了解的可以翻看
beanDefinition.applyDefaults(this.beanDefinitionDefaults)
一级缓存 是sqlSession对象的缓存,自带的(不需要配置)不可关闭的。一级缓存的生命周期与sqlSession一致。
我们再来看当 Spring 整合 Mybatis 后,执行一次查询的流程
getConfiguration()
如图可见,一级缓存被清空无外乎三种情况:1、数据库执行了 写操作2、sqlSession 销毁(执行了 commit 或者 close 操作)3、sqlSession 调用 clearCache() 方法自己手动清空缓存
MapperScannerRegistrar
Executo 执行查询操作,后面的流程作者已经在另一幅作品 《Mybatis 源码分析》 中详细画过,此处不再赘述
循环 mapper 的包,因为可能有多个
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages)
List<String> basePackages = new ArrayList<String>()
从这个方法名就可以看出,当 spring 去get userMapper 时,获取的是一个实现了 FactoryBean 接口的类型
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>()
这个类是Spring 提供的
首先我们来看如果我们单独使用 Mybatis,执行一次查询的流程
User userById = userMapper.findUserById(1)
.........
@Import(MapperScannerRegistrar.class)
spring 容器启动
DefaultSqlSession # selectOne
this.sqlSessionFactory == null
sbd.setResource(resource)
scanner.doScan(StringUtils.toStringArray(basePackages))
这是包的搜索路径
this.sqlSessionFactory
至此,mapper 接口的扫描就结束了,不过此时只是 BeanDefinition,还未创建代理对象
sping 创建 UserSreviceImpl bean 对象时,在执行属性注入 populateBean 方法时,会从 Sping 容器中找寻有没有可以注入给 UserMapper 的类型,如果没有,则开始创建
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry)
return mapperProxyFactory.newInstance(sqlSession)
将扫描到的 需要交给Spring管理的 mapper 封装为 BeanDefinition,添加到 Set 集合中
@MapperScan(\"com.qmy.dao\")
循环 Resource,拿到 com.qmy 下的每一个 java 类,封装为 ScannedGenericBeanDefinition
finally
getObject() 是 FactoryBean 定义的方法,实现FactoryBean 接口的类都需要实现该方法,当 spring 创建 SlqSessionFactoryBean时, 因为 SlqSessionFactoryBean 实现了 FactoryBean 接口,则会自动调用他的 getObject 方法,getObject 返回的才是需要创建或者获取的 bean
准备好 Set<BeanDefinition>,最后会作为返回值返回
this.sqlSessionFactory = buildSqlSessionFactory()
配置 beanDefinition 的一些基本属性
获取 Mapper扫描器
扫描 packageSearchPath 即 com.qmy.dao 包下边的所有 mapper 文件
ClassPathScanningCandidateComponentProvider # findCandidateComponents
Set<BeanDefinition> candidates = new LinkedHashSet<>()
MapperFactoryBean 解析
false
getResourcePatternResolver()
问题二:上面作者详细阐述了 @MapperScanner 是如何帮助 spring 完成 mapper 接口扫描的,接下来我们再来讨论,SlqSessionFactory 是如何被 Spring 创建管理的
抽象类
关键点,从名字就可以看出,这里把 SqlSession 给关闭了
Resource resource : resources
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
处理异常,如果有的话
AUTHOR:helloworld
注意图片中红方框框起来的注释,当前的 BeanDefinition 的 class 属性是 mapper Interface 的 class,这里要把 BeanDefinition 的 class 改为 MapperFactoryBean ,这个 MapperFactoryBean 实现了 FactoryBean 接口,以后当 sprin 需要 mapper 对象时,会getBean---> getObject ---> getSqlSession().getMapper(this.mapperInterface)这里就可以看出,最终还是调用getMapper 获取代理对象,这个过程可以看作者的另一幅图,《Mybatis 源码分析》
这里就能看出,当 Spring 整合 Mybatis 后,使用的 SqlSession 实现类并非同一个,这在上一个问题里就已经提到过
getResources(packageSearchPath)
BeanDefinition candidate : candidates
如果 sqlSession 部不为null,正常情况下这里肯定不为 null
这个方法返回值是 spring 的 Web容器
object = font color=\"#f44336\
ImportBeanDefinitionRegistrar
MapperScannerRegistrar # registerBeanDefinitions
definition = (GenericBeanDefinition) holder.getBeanDefinition();
try
一级缓存分析
从注解上获取扫描的包
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX : 固定常量 ,值为 classpath*: resolveBasePackage(basePackage) 这个就是在 @MapperScan 注解中传入的value值 com.qmy.daothis.resourcePattern 也是一个固定常量,\"**/*.class\"所以这个 packageSearchPath 的值为 classpath*:com/qmy/dao/**/*.class
extends
获取 mybatis 的全局配置对象,这个 Configuration 是在 构建 SqlSessionFactory时创建好了的
ClassPathBeanDefinitionScanner # doScan
0 条评论
回复 删除
下一页