首页 > 数据库 >SpringBoot源码2——SpringBoot x Mybatis 原理解析(如何整合,事务如何交由spring管理,mybatis如何进行数据库操作)

SpringBoot源码2——SpringBoot x Mybatis 原理解析(如何整合,事务如何交由spring管理,mybatis如何进行数据库操作)

时间:2022-12-11 22:12:19浏览次数:70  
标签:mapper 事务 调用 SpringBoot spring 如何 源码 mybatis 方法

阅读本文需要spring源码知识,和springboot相关源码知识
对于springboot 整合mybatis,以及mybatis源码关系不密切的知识,本文将简单带过

系列文章目录和关于我

一丶从一个问题开始——读已提交情况下mybatis一级缓存造成的问题

image-20221204165445817

上图中,已知道users1的size为5,那么users2的大小为多少昵?

我们暂且抛弃mybatis框架中的知识,从mysql事务隔离级别进行分析,test方法第一次查询到总数,然后重新开启一个事务插入了一条(require_new的传播级别),后续addOne方法将立即提交,再次查询的时候,test方法应该可以立马查询到已经提交的数据,应该比第一次输出的应该多1,这是事务隔离级别指导我们做出的判断

但是事实上是users1大小和 users2一样大,这是为什么昵?

我们看下控制台

image-20221204165622947

发现mybatis并没有进行第二次数据库的查询,这时候我们应该意识到mybatis具备缓存,从而导致第二次查询并没有访问数据库

也就是说 读已提交的隔离级别下,mybatis如果不关闭缓存将存在错误(这里的缓存指的一级缓存,二级缓存普遍是不开的)

具体原理,笔者此文将到mybatis缓存后将进行解读,下面我们从springboot 和 mybatis整合,到mybatis执行原理展开讲mybatis的原理

二丶mybatis-springboot-starter的自动装配

通常springboot整合mybatis只需要引入如下依赖

image-20221204170322472

简单描述就是SpringBoot启动的时候会读取META-INF/spring.factories中自动配置的类,加入到容器中,后续springboot会将这些类当作配置类进行解析

image-20221204170738122

上图是mybatis-spring-starter的META-INF/spring.factories,其中关键的是MybatisAutoConfiguration

1.导入SqlSessionTemplate,SqlSessionFactory

image-20221204171347652

这里可以看到当容器中没有SqlSessionFactory的时候,MybatisAutoConfiguration会为我们注入一个SqlSessionFactorySqlSessionTemplate同样如此。

这里我们简单提一下SqlSessionFactorySqlSessionTemplate的作用

1.1.mybatis中的SqlSessionFactory

故名思意,它是创建SqlSession的工厂

image-20221204171832751

这里Spring构建SqlSessionFactory,使用了SqlSessionFactoryBean#getObject

image-20221205061620302

它实现了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

image-20221204172653799

这里可以看到如果没有MapperFactoryBeanMapperScannerConfigurer这两个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操作
image-20221204173306273

2.2AutoConfiguredMapperScannerRegistrar

在没有MapperScannerConfigurer,mybatis自动装配会为我们注入它,它是一个ImportBeanDefinitionRegistrar

image-20221204173438149

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

image-20221204174053767

spring容器在启动的时候,会回调它的postProcessBeanDefinitionRegistry在这个方法里面会扫描所有的mapper接口,指定其class为MapperFactoryBean,从而在后续的实例化中,调用MapperFactoryBean#getObject生成mapper接口的动态代理对象

三丶mybatis扫描mapper接口,注册mapper的Beanfinition

1.MapperScannerConfigurer是如何被注册到spring容器中的

上文中,我们说到,如果我们没有使用@MapperScan或者@MapperScans注解标注在配置类上面,那么会默认添加一个MapperScannerConfigurer,进行mapper接口的扫描注册工作

image-20221204174455872

通常启动类都有这样的@MapperScan

image-20221204174516319

@MapperScan上面存在@Import,会导入一个MapperScannerRegistrar,这是一个ImportBeanDefinitionRegistrar会在这里注册MapperScannerConfigurer的bean定义信息

其实就是把@MapperScan注解上的配置,绑定到MapperScannerConfigurer的属性上,

@MapperScan注解,可以指定mapper在的包,mapper接口必须标注的注解,Mapper接口动态代理对象生成使用的MapperFactoryBean

2.MapperScannerConfigurer 如何进行扫描注册mapper的

image-20221204175221106

其实扫描注册的工作委托给了ClassPathMapperScanner,调用scan方法进行扫描注册

image-20221204175417187

它是一个ClassPathBeanDefinitionScanner的子类,ClassPathBeanDefinitionScanner就是负责包路径扫描,注册BeanDefinition的

image-20221204175608720

这里的扫描调用了ClassPathBeanDefinitionScanner的doScan方法,这个方法会根据包路径解析成Resouce对象,然后根据路径下的类包装成BeanDefinition(ScannedGenericBeanDefinition)

重点看下processBeanDefinitions

image-20221204180002627

这里最关键的是definition.setBeanClass(this.mapperFactoryBeanClass),即将mapper接口的BeanDefinition类型指定为MapperFactoryBean,这样在spring后续实例化mapper的时候就调用MapperFactoryBean#getObject方法进行实例化了

至此我们学习了SpringBoot是如何和mybatis进行结合的,下面总结成一图

image-20221205063340713

四丶Mapper bean的实例化

当我们一个Service需要注入一个mapper的时候,会从Spring容器中找对应的实例,这时候边会涉及到这个mapper的实例化,但是我们mapper明明是一个接口呀,如何实例化昵?

虽然我们mapper是一个接口,但是注入到service属性上的是这个接口的实现类,它是mybatis动态代理后生成的对象。

这个实例化的入口便是AbstractBeanFactory#getBean方法

1.获取mapper对应的BeanDefinition

这里获取的beanDefinition便是源自ClassPathMapperScanner 注册到容器中的

2.实例化MapperFactoryBean

我们上面说到过,实例化mapper需要调用MapperFactoryBean#getObject,那么首先需要实例化一个MapperFactoryBean

image-20221204225703939

这里实例化MapperFactoryBean边是使用的createBean方法,然后Spring会使用反射调用构造方法实例化出MapperFactoryBean(Spring还存在使用CGLIB生成子类然后实例化的方式),其中调用的是

image-20221204230443642

这个构造方法需要一个入参,表示Mapper接口类型,那么这个mapperInterface入参来自那么昵?ClassPathMapperScanner扫描完mapper接口,生成BeanDefinition后,还会在BeanDefinition中记录全限定类型,这个全限定类名将作为MapperFactoryBean的构造器入参

image-20221204231236586

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

image-20221205055735702

其中checkDaoConfig方法被MapperFactoryBean重写

image-20221205055947092

这里会调用configuration.addMapper解析xml和mybaits相关的注解,然后进行注册和接口进行绑定,但是这一步解析xml操作通常不会真正进行,因为在创建SqlSessionFactory的时候已经进行了

5.调用MapperFactory#getObject实例化出一个mapper

image-20221205063504160

实例化出一个Mapper接口的动态代理对象,调用的是SqlSessesionTemplate#getMapper

image-20221205064437028

那么到底mapper方法调用的时是如何操作数据库的昵?这一点我们后面继续说

至此我们知道了我们service注入的mapper其实是mybatis使用动态代理生成的对象,表面是一个什么方法实现都没有的接口,其实是动态代理"负重前行",下图展示了一个mapper被创造出来的全流程

image-20221205070231918

五丶Mybatis 和spring事务的结合

上面我们知道了xxMapper其实是一个jdk动态代理生成的对象 ,其InvocationHandlerMapperProxy

image-20221205070556722

当mapper被调用其接口中声明的方法的时候,会调用到InvocationHandler#invoke这时候MapperProxy就会大显身手

1.MapperProxy#invoke

image-20221205071325979

MapperProxy内部使用了一个Map缓存方法和对应的执行器(MapperMethodInvoker),这个map通常来自MapperProxyFactory的ConcurrentHashMap属性。而真正方法的调用又委托给了MapperMethod#execute,MapperMethod根据方法调用的类型(增删改查)调用MapperProxy中的属性SqlSession(spring环境下的sqlSession实现类是SqlSessionTemplate)`对应的方法

image-20221205072013759

2.SqlSessionTemplate 与mybatis spring事务

image-20221207193657612

SqlSessionTemplate实现了SqlSession接口,但是真正进行数据库操作的时候,都是委托给属性SqlSessionProxySqlSessionTemplate存在的意义在于"模板"——复用SqlSession,那么为什么需要复用,为何要复用?我们接着看下它的构造方法

image-20221207193835760

可以看到,其内部的sqlSessionProxy是一个动态代理类,我们看下SqlSessionInterceptor,它是一个InvocationHandler

image-20221207222821878

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

相关文章