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

系列文章目录和关于我

一丶从一个问题开始——读已提交情况下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会为我们注入一个SqlSessionFactorySqlSessionTemplate同样如此。

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

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

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

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动态代理生成的对象 ,其InvocationHandlerMapperProxy

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

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

2.1 mybatis 和 spring结合后即使没有开启事务也能自动提交的原因

上图可以看到如果事务并非交给spring管理(调用mapper执行单条增删改查的数据库操作,会自动提交事务)在反射调用sqlsession方法后,会进行事务提交。

//上面无事务注解 下面这条语句会调用到sqlsession的动态代理对象,进行自动提交
public void test(){ xxxMapper.insertOne(xx);
}

笔者校招的时候,面试官问过这个问题,我尼玛扯到了mysql的自动提交

原生mybatis使用sqlsession执行数据库操作后,需要手动调用其commit方法。spring环境的mybatis会自动提交,便是由于sqlsessionTemplate复用的sqlsession,其实是DefaultSqlsession的代理类,在执行数据库操作后,发现事务没有被spring管理便进行自动提交

2.2使用mapper执行多个数据库修改操作,具备事务的原因

@Transactional
public void test(){
xxxmapper.insert1();
xxxmapper.insert2();
}

众所周知,上面这个方法spring容器中的bean执行是具备事务的,那么为啥具备事务昵?

你可能会回答,容器中的bean是被BeanPostProcesser在bean完成实例化,依赖注入,后会被BeanPostProcessor后置处理器,依次进行处理,生成代理对象,其中存在AnnotationAwareAspectJAutoProxyCreator(@Aspect,@Before等注解感知能力的BeanPostProcessor,会将@Aspect标记的bean中的方法解析成Adivor然后使用ProxyFactory生成其代理对象)或者说任何AbstractAdvisorAutoProxyCreator,它会将使用Advisor 并基于CGLIB,或者java接口动态代理生成代理对象,其中便有BeanFactoryTransactionAttributeSourceAdvisor(一个Advior,真正事务代理的逻辑在TransactionInterceptor(一个Advise,实现事务开启,事务提交,回滚等逻辑)]中,以及TransactionAttributeSource(用于解析事务注解,判断方法是否需要开启事务,TransactionAttributeSourcePointcut这个pointcut 便是使用它进行判断方法是否需要被事务代理)它实现解析事务注解判断是否需要进行动态代理,实现事务功能

但是这个问题归根结底还是没有说明,为什么mapper多次数据库修改操作,具备事务。

具备事务的前提是使用同一个连接,这样才能connection.commit提交事务,connnection.rollback,回滚事务,根本原理就在SqlSessionTemplate,对SqlSession的复用中

上图展示了mybatis在结合spring后,是如何让自己的sqlsession复用的,存于事务管理器中(基于ThreadLocal,存储事务信息,这也就是为啥多线程情况下事务效的原因)但是还是没有说明为啥复用了同一个connection

接下来我们看下使用SqlSessionFactory(mybatis自动配置类注入的DefaultSqlSessionFactory)开启一个sqlsession的逻辑

首先获取TransactionFactory事务工厂,这里使用的是SpringManagedTransactionFactory

它返回的事务是SpringManagedTransaction,然后创建一个Executor,然后封装成一个DefaultSqlSession返回,这里我们重点看下SpringManagedTransaction

这里Connection的获取便是从事务同步管理器的ThreadLocal中获取,如果没有connection一般是第一次数据库操作,那么这里会dataSource.getConnection()开启一个连接,然后交由事务同步管理器处理,后续便会复用此连接。

2.3 spring管理mybatis事务的时候,事务何时提交

上面我们说到数据库操作都交由DefaultSqlSession处理,DefaultSqlSession是一个门面,其提交事务,最终还是调用到了SpringManagedTransactioncommit方法

这里可以看到SpringManagedTransaction#commit只有连接没有交给spring管理,并且连接并非自动提交才会生效,基本上调用这里的提交不会产生任何效果。

上面我们说到SqlSessionTemplate委托动态代理后的SqlSession执行操作的时候,会从事务同步管理器中获取SqlSession,如果没有那么new一个然后注册到事务同步管理器中

事务提交的奥秘就在registerSessionHolder

这里的SqlSessionSynchronization是一个TransactionSynchronization对象,TransactionSynchronization接口提供了多个方法在事务不同的时期会在代理对象@Transactional标注的方法中进行回调

其中beforeCommit方法会在@Transactional标注的代理对象其业务逻辑执行完成后,如果需要提交事务,会被回调到,这时候就会调用SqlSession的commit方法进行提交事务

提交事务会继续委托给SpringManagedTransaction,可是其commit方法只会在事务不被spring管理的时候进行提交,如果事务被spring管理,@Transactional注解标注的代理对象方法执行后会调用PlatformTransactionManager#commit,这里会调用到DataSourceTransactionManager(如果是分布式事务那么是其他的实现类,基于数据源的事务都是调用此类)它会调用connection.commit 提交事务

2.4不加事务注解的mapper进行数据库操作,事务何时提交

sqlSessionTemplate 中的SqlSessionInterceptor,在创建出SqlSession执行完数据库操作的时候,发现事务没有被Spring管理,此时便会立即提交事务

getSqlsession无法从事务同步管理器中复用SqlSession,每次都是new出一个SqlSession 因为当前方法无事务注解,事务同步管理器不会处于活跃状态。提交事务会调用到SpringManagedTransaction其commit方法中判断得到事务没有被spring管理,便会调用connection.commit提交事务

2.5不加事务注解使用mapper执行多条数据库修改操作,会没有事务的原因

public void test(){
xxxmapper.insert1();
xxxmapper.insert2();
}

2.4中我们可以看到,insert1insert2的执行,其实每次都会new出一个新的sqlsession,每一个sqlsession对应一个SpringManagedTransaction,每一次执行结束后都会立马提交事务,所有不具备事务。所有那怕test方法最后抛出异常,事务也会提交。

六丶Mybatis操作数据库

上面我们研究了mybatis事务和spring事务的结合,并没有关注mybatis是如何进行数据库操作的,下面我们来看下mybatis是怎么拿到xml中的一条sql,把我们入参中的对象映射到sql中的占位符,执行sql然后将结果集解析为mapper方法的出参类型对象的。

前面几节的知识中提到,DefaultSqlSession是mybatis操作数据库的门面,增删改查都是交由它来实现

其中所有的查询操作都是调用select或者selectList方法,所有的新增,更新,删除都是调用update(这些都是数据库变更操作)方法,我们以查询操作为例

可以看到查询的操作最终委托给了Executor对象

1.Executor

1.1.Executor接口

  • 该接口提供了改和查的基本功能(数据库的删除插入本质也是更新)
  • 提交和回滚
  • 缓存相关方法
  • 批处理刷新
  • 执行器关闭
  • 延迟加载

1.2.BaseExecutor

​ 对Executor中的接口中的大部分方法进行了通用的实现,并且可以通过配置文件,或者手动指定执行器类型来让mybatis使用具体执行器实现(这里说的实现只有BatchExcutor,SimpleExecutor,ReuseExcutor),还提供了三个抽象方法(如下)让子类实现

  • doUpdate
  • doFlushStatements
  • doQuery

1.3.SimpleExecutor

简单执行器,是 MyBatis 中默认使用的执行器,对BaseExecutor中的方法进行了简单的实现,(根据配置获取连接,根据连接获取Statement,执行sql,结果集映射)每执行一次 update 或 select,就开启一个 Statement 对象,用完就直接关闭 Statement 对象

1.4.BatchExecutor

主要应对批量更新,插入,删除,一次向数据库发送多个SQL语句从而减少通信开销,从而提高性能。(对查找不生效)

批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用来提交它们,一次执行完成与数据库之间的交互。需要注意的是:JDBC中的批处理只支持 insert、update 、delete 等类型的SQL语句,不支持select类型的SQL语句。

1.5.ReuseExecutor

ReuseExecutor 不同于 SimpleExecutor 的地方在于 ReuseExecutor 维护了 Statement 缓存

ReuseExecutor顾名思义就是重复使用执行,其定义了一个Map<String, Statement>,将执行的sql作为key,将执行的Statement作为value保存,这样执行相同的sql时就可以使用已经存在的Statement,就不需要新创建了,从而避免SimpleExecutor这样多次进行参数拼接生成statement以提高性能

1.6.CachingExecutor

CachingExecutor没有继承BaseExecutor,CachingExecutor 不具备 Executor 执行器功能,CachingExecutor 是一个装饰器, Mybatis 采用装饰者模式对 Executor 执行器提供了功能增强。CachingExecutor装饰器能够使得被装饰的Executor 具备二级缓存功能

下图是Configuration创建Executor的流程,如果全局配置指定了cachEnable,那么会使用CachingExcutor进行装饰,并且mybatis插件可以作用于Excutor,

1.7 Executor执行数据库操作流程

1.7.1 CachingExcutor装饰器模式实现二级缓存

其装饰的作用就是让被装饰的Executor具备二级缓存的能力,在执行查询,更改等操作的时候会维护二级缓存,由于二级缓存并不常用(因为我们基本上都是微服务多实例,一个实例更新了二级缓存,如何同步到其他实例,我们需要自己实现cache,这有带来一致性等问题,一般是不开启二级缓存的)我们不继续深究二级缓存的原理

1.7.2 BaseExcutor模板方法设计模式

BaseExcutor定义了基本的流程,对于子类具备差异的地方,留给子类自己去实现,从而达到高内聚的目的。以查询为例,一级缓存的刷新由BaseExecutor在合适的时机调用,首先从一级缓存中获取,如果缓存中存在,那么不会进行数据库查询操作,反之调用queryFromDatabase查询数据库,queryFromDatabase会调用到doQuery方法,这个方法由子类自己实现

至此我们可以解答下 一丶从一个问题开始——读已提交情况下mybatis一级缓存造成的问题,出现的原因便是一级缓存缓存了上一次的查询结果,由于我们执行的是同一个查询,mapperStatement(mapper方法全限定)一致,入参也样一致,也没有内存分页的内容,参数映射等内容也一致,便会命中缓存,所以读已提交的隔离级别,被mybatis 破坏

但是如果我们不加事务直接,便不会如此,因为不加事务直接,每一次查询操作都是new出的sqlsession,都会调用到不同的Executor,一级缓存是和Eexcutor中的一个属性(本质是一个map)这样一级缓存便是不同的对象,便不会命中缓存。

1.7.3 SimpleExecutor 如何查询数据库
这里我们没有研究ReuseExecutor如何复用,其实使用map(key是执行查询的sql,value是statement)达到复用的目的
也没有研究BatchExecutor,本质是执行更改操作的时候调用的是statement#addBatch,批量执行sql语句,
二者使用的都很少,将不做过多赘述了

可以看到SimpleExcutor执行查询委托给了StatementHandler,它会用StatmentHandler创建statement,然后执行查询

我们总结下至此的执行流程,如下图

接下来我们探究下StatmentHandler是如何进行参数映射,使用Statment执行数据操作,并处理返回结果集的

2.StatmentHandler操作数据库

2.1.StatemenHanlder接口

定义了StatementHandler的基本功能

  • 准备语句 子类可以实现返回不同的Statement子类
  • 参数映射
  • 更新操作
  • 查询操作

2.2BaseStatementHandler

模板方法设计模式,提取公共的操作到父类,子类具备差异的地方使用抽象方法,交由子类实现

2.2RoutingStatementHandler

主要是适配多个StatmentHandler的实现,有点装饰器适配器的意思

后续具体方法的实现都是调用delegate对应的方法,相当于RoutingStatementHandler 只是做了一个根据MappedStatement中的StatementType配置创建不同的StatmentHandler

2.3PrepareStatementHandler

预处理Statement的handler,处理带参数允许的SQL, 对应JDBC的PreparedStatement(预编译处理)

2.4 SimpleStatementHandler

最简单的StatementHandler,处理不带参数运行的SQL,对应JDBC的Statement

2.5 CallableStatementHandler

存储过程的Statement的handler,处理存储过程SQL,对应JDBC的CallableStatement(存储过程处理)

下图是mybatis创建一个statementHandler,默认是RoutingStatementHandler,正在操作数据库的一般是PrepareStatmentHandler,并且mybatis插件会发挥作用

2.6 PrepareStatementHandler 如何创建一个Statement,并设置参数,执行查询的

2.6.1 prepare

最终初始化一个statement是由子类PrepareStatementHandler调用connection.prepareStatement实现

2.6.2 参数映射

可以看到参数映射的工作,交给了ParameterHandler(唯一的实现类是DefaultParameterHandler)具体设置参数的流程如下

//设置参数
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//1.获取sql语句的参数,ParameterMapping里面包含参数的名称类型等详细信息,还包括类型处理器
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//2.遍历依次处理
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//3.OUT类型参数不处理
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//4.获取参数名称
String propertyName = parameterMapping.getProperty();
//5.如果propertyName是动态参数,就会从动态参数中取值。(当使用<foreach>的时候,MyBatis会自动生成额外的动态参数)
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//6.如果参数是null,不管属性名是什么,都会返回null。
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//7.判断类型处理器是否有参数类型,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。
value = parameterObject;
} else {
//8.这种情况下是复杂对象或者Map类型,通过反射方便的取值。通过MetaObject操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//9.获取对应的数据库类型
JdbcType jdbcType = parameterMapping.getJdbcType();
//空类型
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//10.对PreparedStatement的占位符设置值(类型处理器可以给PreparedStatement设值)
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

可以看到最终使用TypeHandler设置参数,会调用到prepareStatement#setxxx方法设置参数

2.6.3 查询数据库,并转换结果集

查询数据库的操作委托给了ResultSetHandler的实现类DefaultResultSetHandler

  • 处理多结果集

存储过程存在多结果集的情况,

  • 处理一行结果集

不存在嵌套子查询的时候,使用handleRowValuesForSimpleResultMap

这里出现一个类DefaultResultContext,实现了ResultContext,这是结果上下文,主要的职责是控制处理结果行的停止,配合rowBounds实现内存分页,后面的storeObject就是将一行对应的对象存在list(似乎对map这种出惨有特殊处理,对于嵌套子查询也有特殊处理)

这里的自动映射应该是处理,没有指定resultMap 凭借对象属性和数据库列名进行映射的情况 ,后面applyPropertyMappings 处理指定resultMap 中column和 property的情况,对于指定了TypeHandler的列,会使用TypeHandler进行设置(调用TypeHandler#getResult),自动映射的类使用MetaObject#setValue处理(反射设置属性)

七丶mybatis插件实现原理

1.拦截器接口

public interface Interceptor {

    //拦截        Invocation :当前被拦截对象 参数 和被拦截方法
Object intercept(Invocation invocation) throws Throwable; //动态代理
default Object plugin(Object target) {
return Plugin.wrap(target, this);
} default void setProperties(Properties properties) {
// NOP 可以在这里给拦截器赋值一些属性
} }

2.Plugin.wrap(target, this)方法如何实现拦截

  • Plugin 实现了InvocationHandler——基于JDK动态代理

  • 读取注解信息,获取当前拦截器要拦截什么类的什么方法

注意获取方法的方式

Method method = sig.type().getMethod(sig.method(), sig.args());

getMethod方法是没有办法获取到私有方法的,所有无法拦截一个私有方法

  • 获取被代理类的接口

  • 动态代理对象生成

3.动态代理生成的对象是怎么被使用的

如上我们知道了mybatis 是怎么支持插件的,根据拦截器上的信息生成动态代理对象,动态代理对象在执行方法的时候会进入拦截器的intercept拦截方法,那么动态代理的生成的对象在哪里被使用到昵

  • Configuration 类 也就是mybatis的大管家,在new一些mybatis四大对象的时候会使用到插件

    也就是说mybatis 只支持拦截ParmeterHandler,ResultSetHandler,StatementHandler,Excutor这四种对象

    在mybatis执行中的使用的四大对象其实是被动态代理后的对象,从而调用到插件功能

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

  1. SpringBoot源码学习系列之启动原理简介

    本博客通过debug方式简单跟一下Springboot application启动的源码,Springboot的启动源码是比较复杂的,本博客只是简单梳理一下源码,浅析其原理 为了方便跟源码,先找个Ap ...

  2. SpringBoot源码分析(一)@SpringBootApplication解析

    @SpringBootApplication解析 三层注解 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(exclu ...

  3. springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore

    @SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的 ...

  4. Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析

    概述 在SpringMVC的使用时,往往会用到@RequestBody和@ResponseBody两个注解,尤其是处理ajax请求必然要使用@ResponseBody注解.这两个注解对应着Contro ...

  5. 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 SpringBoot是如何实现自动配置的?--SpringBoot源码(四) 温故而知新,我们来简单回顾一下上 ...

  6. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  7. 【spring-boot 源码解析】spring-boot 依赖管理

    关键词:spring-boot 依赖管理.spring-boot-dependencies.spring-boot-parent 问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程 ...

  8. SpringBoot内置的各种Starter是怎样构建的?--SpringBoot源码(六)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 温故而知新 本篇接 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五) 温 ...

  9. SpringBoot源码学习系列之异常处理自动配置

    SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...

  10. SpringBoot源码学习系列之嵌入式Servlet容器

    目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...

随机推荐

  1. 使用Recoding Rules优化性能

    通过PromQL可以实时对Prometheus中采集到的样本数据进行查询,聚合以及其它各种运算操作.而在某些PromQL较为复杂且计算量较大时,直接使用PromQL可能会导致Prometheus响应超 ...

  2. C#/VB.NET 读取条码类型及条码在图片中的坐标位置

    我们在创建条形码时,如果以图片的方式将创建好的条码保存到指定文件夹路径,可以在程序中直接加载图片使用:已生成的条码图片,需要通过读取图片中的条码信息,如条码类型.条码绘制区域在图片中的四个顶点坐标位置 ...

  3. androidmanifest.xml 反编译

    androidmanifest.xml 反编译 去除更新只修改androidmanifest.xml内容 解压apk文件后得到这个文件androidmanifest.xml windwos安装java ...

  4. sql面试50题------(11-20)

    文章目录 11.查询至少有一门课与学号为'01'的学生所学课程相同的学生的学号和姓名 12.查询和'01'号同学所学课程完全相同的其他同学的学号 13.查询两门及其以上不及格课程的同学的学号,姓名及其 ...

  5. 解决在vue中设置的height: 100%没有效果

    在新的页面设置height无效果的时候.需要改动App这个文件的heigth 解决办法.给app这个盒子设置高度.默认情况下为0 设置高度100%时,div的高度会等同于其父元素的高度.而上面中id为 ...

  6. NLP之基于Seq2Seq的单词翻译

    Seq2Seq 目录 Seq2Seq 1.理论 1.1 基本概念 1.2 模型结构 1.2.1 Encoder 1.2.2 Decoder 1.3 特殊字符 2.实验 2.1 实验步骤 2.2 算法模 ...

  7. OpenAPI 接口幂等实现

    OpenAPI 接口幂等实现 1.幂等性是啥? 进行一次接口调用与进行多次相同的接口调用都能得到与预期相符的结果. 通俗的讲,创建资源或更新资源的操作在多次调用后只生效一次. 2.什么情况会需要保证幂 ...

  8. antd 批量上传文件逻辑

    基本步骤 通过 antd 框架的 Upload 控件,采用手动上传的方式,先选择需要上传的文件(控制文件数量以及大小),再根据所选的文件列表,循环上传,期间通过 Spin 控件提示上传中. 效果展示 ...

  9. vue2和vue3组合使用教程地址

    https://cn.vuejs.org/guide/essentials/watchers.html#eager-watchers

  10. 三、Python语法介绍

    三.Python语言介绍 3.1.了解Python语言 Python 是1989 年荷兰人 Guido van Rossum (简称 Guido)在圣诞节期间为了打发时间,发明的一门面向对象的解释性编 ...