一.应用层注入(当我们在使用mybatis的时候,我们在使用什么?)
当我们在使用mybatis+spring的时候,我们实际上只在配置里面注入了两个类:
1.
id
="sqlSessionFactory"
class
="org.mybatis.spring.SqlSessionFactoryBean"
>
2.
class
="org.mybatis.spring.mapper.MapperScannerConfigurer
”>
顾名思义这两个类一个是用来产生sqlSession的sessionFactoryBean ,Factorybean 是Spring 为我们提供的一种bean扩展,工厂模式想必大家都很熟悉,把sessionFactory本身作为一个Spring的bean放到spring里面使用,这就是FactoryBean。这里我们在xml手动注入了一个路径:
name
="configLocation"
value
="classpath:mybatis/mybatis-configuration.xml
”/>
和一个datasouce ,configlocation 就是我们mabatis配置文件的路径
还有一个是用来扫面mybatis配置文件的MapperScaner的Configurer ,这里面手动注入的只有一些配置项例如包名,SessionFeactoryBeanName ,真正的扫描工作由一个ClassPathMapperScanner 完成,在这个类里面我们手动配置了两个属性:
一个是dao层的包名,还一个就是之前说的sessionFactoryBean。
那么一下这两个类到底是什么?
先看
org.mybatis.spring.SqlSessionFactoryBean
先来看这个类的接口(由于java的单继承,当我们不能完全确认类与类之间的关系是一种is-a的关系的时候,往往是用接口来实现扩展,而继承则使用的比较谨慎,这一点在spring框架中表现的尤为明显)
1.FactoryBean
spring 给我们提供的一个扩展接口
用于管理各类BeanFactory 当我们在spring里面使用getBean(‘一个factoryBean的ID’)的时候,我们拿到的bean其实是factoryBean里面getObject()这个方法所返回的bean(factory),而不是factoryBean本身
2.InitiallizingBean
当spring遇到实现了这个接口的类,会在所有属性注入完成后调用一个用于初始化的方法:
afterPropertiesSet(),我们一般把bean的初始化写在这个方法里面
3.ApplicationListenner
监听spring的spring的刷新事件,用于在spring刷新的时候更新类的内部信息
再来看这个类的方法:
除去一堆属性的get set 方法,我们需要关注的方法只有这么几个
1.
afterPropertiesSet 对象的初始化方法:在确认了两个关键属性不为空之后,调用buildSqlSessionFactory 方法
2.
buildSqlSessionFactory
顾名思义,这个方法会建造一个SqlSessionFactory 而且很可能使用可建造者模式。这个方法有一百多行,总体而言做了这么几件事情,
2.1.配置一个
XMLConfigBuilder,这个builder里面有一个parse方法,里面就是解析mybatis配置文件的具体实现 ,parse方法返回一个
Configuration 对象,这里面包含了我们在配置的xml里面写的所有东西。具体解析过程会在之后讲到,
2.2.
XMLConfigBuilder 这个对象在构造函数里面会对 自己本身的
Configuration 对象属性,进行一部分初始化工作,例如validate,但不会进行具体解析,在
buildSqlSessionFactory 这个方法里面,我们拿到了这个初步初始化之后的
Configuration 对象,进行了进一步的初始化,例如我们在spring里面配置的 插件 ,alias ,还有objectFactory ,transactionFactory
2.3.最后,调用
XMLConfigBuilder 里面的parse方法,正式解析xml文件。
然后我们再看一下
MapperScannerConfigurer
还是先看接口:
这里除了InitializingBean 之外还有两个aware 和 一个processor接口:
aware接口:感知接口,applicationContextAware :感知这个Bean所处的Spring的Context(上下文)
beanNameAware :感知这个Bean在Spring容器中的beanName
BeanDefinitionRegistryProcessor:
这里说一下postprocessor : spring 给我们预留的processor接口允许我们在spring 处理初始化逻辑的时候,允许程序员处理一些与spring框架本身初始化相关的事情,而不是Bean本身的初始化。
例如BeanDefinitionRegistryProcessor,这个接口就允许我门在初始化Bean本身(afterpropertiesset)之后,对spring框架本身所维护的BeanDefinitionRegistry做一些修改,至于BeanDefinitiionRegistry是什么 ,再之后的内容中会有解释。
然后再来看看方法:
postProcessBeanDefinitionRegistry:整个类里面只有这一个重要的方法 他是 实现了BeanDefinitionRegistryProcessor 接口必须实现的方法:它使得我门可以修改spring所维护的一个
BeanDefinitionRegistry ,它是spring框架中非常重要的一个接口。
接口文档上是这么说的:
Interface for registries that hold bean definitions, for example RootBeanDefinitionand ChildBeanDefinition instances. Typically implemented by BeanFactories thatinternally work with the AbstractBeanDefinition hierarchy.
这个接口维护了一些个register(注册表?),这个注册表持有许多BeanDefinition(类的定义),例如根类与子类的定义,这个接口一般被抽象层级的BeanFactory所实现。
好像有点不知所云 ,我打个断点来看看:、
我们可以看到在实际上这个接口被DefaultListableBeanFactory实现,那这里的DefaultListableBeanFactory又是什么东西?
DefaultListableBeanFactory 是整个spring ioc的始祖 ,
当我们使用原生spring 我们会得到一个applicationContext,当我们想从Spring得到一个对象,我们会调用它的getBean 方法,那这个getBean是怎么实现的? 答案就是DefaultListableBeanFactory。
这个DefaultListableBeanFactory 就是我们所使用的所有的Spring Bean 的工厂类(也可以说是容器),它实现了
BeanDefinitionRegistry这个接口。
mybatis在
postProcessBeanDefinitionRegistry 方法里面拿到了
BeanDefinitionRegistry
之后往里面插入了一些BeanDefinition ,先是new 了一个
ClassPathMapperScanner 这个对象的作用是用来扫描base package(也就是我们doScan在
MapperScannerConfigurer配置的basepackage属性包路径下所有的类和接口(一般是各种DAO),然后解析成BeanDefinition,然后插入
BeanDefinitionRegistry 。
具体实现在
ClassPathMapperScanner doScan()方法里面实现。 具体实现会在之后讲到。
二.核心层(mybatis是怎样工作的)
1.binding and mapper 绑定与映射 (当我们启动应用时,mybatis干了些什么)
mybatis框架的绑定与映射主要是将我们写的dao层的方法与配置文件xml中的sql语句相互映射。首先,他们是怎样被加载到我们的应用里面去的呢?
1.1 xml配置文件的解析
之前在介绍
SqlSessionFactoryBean的时候就提到过
xmlConfigBuilder 里面的parse方法是用来解析我们写的xml配置文件的,实际上在parse方法里面所调用
XPathParser的把xml文件解析成一个
XNode,的然后将这个XNode传入
parseConfiguration 方法。 我们知道Xpath是w3c的一个标准,实际上在这个
XPathParser 里面,也是调用的org.w3c.dom,和org.xml.sax 两个开源包来解析xml文件,具体实现这里略过,只看大概逻辑,如下:
平铺易懂的代码风格,每一行解析了一个子模块,所有的方法都没有返回值,解析结果统一储存在了这个对象的configuration,typeAliasRegistry ,typehandlerREgistey三个属性里面,最重要的mapper(类,方法的映射信息)被储存在configuration,来看一下configuration 这个属性对象,这是一个非常复杂的大对象(几十个方法与属性)。暂时只介绍mapper相关的方法与属性
1.方法:
addMapper
(Class<
T
>
type
)
parser从配置文件中解析出来一个接口,将其录入configuration
2.方法:
T
getMapper
(Class<
T
> type
,
SqlSession sqlSession)
在spring 注入Bean的时候,需要通过这个方法得到一个实现了所需接口的Bean
3.属性:
MapperRegistry
mapperRegistry
configuration维护了一个
MapperRegistry属性,
addMapper 属性与
getMapper 都是通过这个属性实现,来看一下
MapperRegistry
两个属性:
Configuration config :Configration 与MapperRegistry 相互持有,在addMapper 的时候需要用到configration
Map<> knowmappers :map<接口,接口的工厂对象> 核心属性,维护了一个 接口 与 用于生产实现接口的 代理对象的 工厂对象。这里是一个典型的工厂模式
MapperProxyFactory如下
既然是工厂类,那么作用就只有一个,那就是产生对象,具体来讲就是产生实现了具体接口(比图XXXDAO)的代理对象。具体生产如下
首先会根据上层传过来的sqlSession生成一个
MapperProxy ,这个
MapperProxy 实现了
InvocationHandler ,
java.lang.
reflect 这个包下面有一个Proxy类与一个
InvocationHandler 接口,调用Proxy的
newProxyInstance 可以得到一个指定接口的代理类,接口里面的方法由
InvocationHandler 拦截,调用
InvocationHandler 里面的
invoke
,使用反射(invoke)的方式调用方法的具体实现,在
MapperProxy这里mybatis框架做了一层方法的缓存:
至此,一个实现了指定接口(DAO)的Instance 就被生成。
那么mybatis是怎样将这个Instance注入到我们所写的业务Bean里面去的呢?
回到之前说的
ClassPathMapperScanner ,我们看一下它的doScan方法
首先这个函数调用了super.doScan()
ClassPathMapperScanner 继承自
ClassPathBeanDefinitionScanner 这个类是Spring 里面默认的ClassPathScaner spring用它扫描目的路径(包)里面所有的带Component,Service,Controller注解的class文件,并解析成相应BeanDefinition ,这个类的doScan返回了一个Set,这就是扫描结果。
在
ClassPathMapperScanner 里面的doScan拿到扫描结果BeanDefinition之后,干了两件事情:
首先为BeanDefinition设置 了四个属性
addToConfig,
markerInterface,
sqlSessionTemplate,
sqlSessionFactory (之后会用到),然后修改BeanDefinition 的BeanClass,改为MapperFactoryBean,这才是将MapperProxy注入业务代码的关键部分,当Spring 注入一个Instance时,会根据BeanClass调用ClassLoader 将Class加载到系统,所以实际上我们注入一个DAO的时候,会注入一个MapperFactoryBean(然而实际上我们调用的是
MapperProxy),我们来看一下MapperfactoryBean
这个类继承自SqlSessionDaoSupport 同时实现了FactoryBean ,
FactoryBean之前说过,在spring里面将注入getObject里面所返回的对象,直接看getObject方法即可。
之前说过在 BeanDefinition里面设置了一个属性
sqlSessionTemplate ,这里在getSqlSession里面被拿出来了,然后调用了getMapper方法。
而getMapper方法是调用了configuration里面的getMapper方法,而这一个configuration就是在本节开头提到的configuration 属性,它最开始出现在SqlSessionFactory里面,而SqlSessionFactory是一个单列,他里面这个属性被很多对象引用。而这里所返回的Mapper,就是之前所说过的MapperProxyFactory 生产的MapperProxy。自此,当我们启动应用的时候,mybatis所做的初始化工作就完成了。
2.invoke and excute 反射与执行 (当我们的业务调用DAO层的时候,mybatis干了什么)
我们业务代码里面所注入的DAO层的Bean实际上是一个MapperProxy,它代理了所有的Dao层的方法,我们来看一下mapperProxy的invoke方法
这里可以看到先对上层传入的方法做了判断,如果DeclaringClass(声明这个方法的类)是Object ,就调用本类的方法,如果不是,就去Proxy里面维护的一个
methodCache 里面拿一个
MapperMethod 类,没有的的话就新建一个(总而言之就是一个缓存),然后调用
MapperMethod MapperMethod里面的excute方法。来看一下MapperMethod 类:这个类没有实现任何接口,父类是Object ,所以先看属性和构造方法
这里定义了两个final属性,在构造函数里面也主要是构造这两个final属性。先看SqlCommand这个类,它是类MapperMethod的内部类
顾名思义,sqlCommand是一个sql命令的封装,但我们的sql是写在xml配置文件里面的所以这个类的构造函数干了这么几件事:首先通过传入的Method找到sqlStatement的名字(接口名.方法名),去configration里面找对应方法的statement(所有mapper的xml配置文件里面的内容都被解析到了configration里面),然后处理Statement的继承关系,最后将Statement的id 和type记录在对象的属性里面,供以后调用Statement。
然后再看一下
MethodSignature ,这个类也是一个内部类
从构造函数看出,这里面储存了对于DAO接口所定义的方法的一些描述,例如返回值 类型 参数数量之类
最后看一下最重要的excute方法:
在sqlcommand里面初始化了一个command type,描述了
sql语句的类型,整理根据sql的类型(增删改查)和返回值调用了sqlSession里面的insert,delete,update,select 方法,这里的sqlSession是一个接口,mybatis实际上默认是用SqlSessionTemplate 实现了这个接口(在mapperProxyFactory里面),来看一下增删改查的具体实现类SqlSessionTemplate:
他有四个 final属性:
其中sqlSessionProxy非常重要,因为所有增删改查都是通过调用它实现的,以selectOne为例:
既然sqlSqssionTemplate本身实现了sqlSession 接口,为什么要加一层sqlSessionProxy 呢?来看sqlSessionProxy的拦截器里面:
答案是事务管理,在invoke里面将不同的dao层的方法路由到不同的sqlSession对象,方便事务的回滚,所以需要一个代理,这里调用了SqlSessionUtils.getSqlSession,里面根据上层传入的executorType与Spring自带的TransactionManager 返回正确的SqlSession。然后再调用session的增删改查。这里默认的sqlSession实现类是DefaultSqlSession(由DefaultSqlSessionFactory生成,这个工厂类就是我们在xml配置的
SqlSessionFactoryBean 生成的 ),来看看这个类
这个才是mybatis增删改查数据看的SqlSession的具体实现,以select为例:
参数解释:statement: 调用的包名+类名+方法名
parameter:DAO层的参数
roundBounds:选择边界
这个方法首先通过在configuration里面拿到statement对应的MappedStatement,MappedStatement 就是我们为每个dao的接口的方法写的一个xml文件下的内容(例如sql语句 返回类型的映射),在初始化应用的时候这些xml被解析,放入configuration ,然后把MappedStatement和DAO层参数传入executor(mybatis里面默认使用CachingExecutor,在sessionFactory里面设置),调用query方法执行。现在我们深入CachingExecutor的query方法:
既然名字叫CachingExecutor,那在查询的时候肯定有一层缓存,这个缓存是内存缓存,独立的存在于每个MappedStatement,在缓存不命中的情况会调用delegate属性(一个SimpleExecutor)的query方法:
我们可以看到这里又有一层内存级别cache ,这层cache独立存在于Executor(同一批次query共用此chache,查询完毕后马上清除)在不命中的情况下才会调用queryFromDataBase。
在queryFromDataBase里面,调用doQuery查询数据库,并且将结果存入localcache,SimpleExecutor重写了doQuery方法:
这将MappedStatement转换成一个
RoutingStatementHandler 然后调用了里面的query方法,这个Handler的主要作用是根据Statement的类型调用不同的StatementHandler代理:
这里的statement是PREPARED类型的。所以我们得到的是一个PreparedStatementhandler。
在doQuery里面,在调用handler执行Statement之前,还会prepareStatement
这里会调用PrepareStatementhandler根据Connection的类型对Statement进行预处理这里的Connection与我们所使用的DataSource相关,例如我们这里使用的是一个DistributedConnection ,那么这里预处理之后的Statement就是一个DistributedPreparedStatement。反之,如果这里用的是mysql,那就回返回一个mysql包下面的Statement。
在handler与statement都准备完毕之后,就会执行query了:
先将statement强转为preparedStatement ,然后调用statement里面的execute方法(与数据库的具体类型相关),最后处理结果集。与数据库类型相关的部分略过,这里看一下结果集的处理部分:DefaultResultSetHandler,实现了ResultHandler接口:
在这里我们关注handleResultsSets方法的实现。
构造函数如下:
可以看到这类可以拿到configuration (全局单例),executor (全局单例)和mappedStatement等等对象,
结果集处理逻辑如下:
首先拿到mappedStatement里面的result maps,那里面储存了我们在xml配置文件里面定义的Dao层方法的返回集的类名,遍历结果多个结果集,根据每一个结果集的resultmap将每一个结果集转化为xml配置的结果对象。单个结果集转换逻辑如下:
这里会先处理结果集映射的父类映射,然后调用handleRowValues 处理多行结果:
每一行结果都会分两种处理方式:关联处理和简单处理,关联处理表示本次查询拿到的结果集可能从多个表中读取,需要解析它们之间的关系并整合成一个完整对象,简单处理表示查询结果从一个表或者视图中得到,只需要进行基本类型(元数据)的映射。
这里我们看一下简单处理:
做了四件事情:1.新建一个resultContext
2.去除没用的行
3.遍历所有行,每行生成一个rowValue,
4.存储结果(联合查询:将结果与其父结果链接 非联合查询:直接返回结果 )
然后看一下每一行的结果是如何生成的:
首先根据resultMap 与resultset等,生成了一个空的resultObject,这个resultObject的类型就是Dao层接口所定义的返回值类型,
DefaultResultSetHandler 本身持有一个objectFactory,这个objectFactory可以根据ClassType调用类的构造函数产生新建对象,resultMap就存有result的ClassType。
得到空的resultObject之后,将其包装成一个metaObject,然后对MetaObject
进行属性值的映射:
这里遍历了resultMap(我们配置在xml文件里面的结果集映射关系),从resultSet中取出相应的值,并转化成对象。调用metaObject的setValue 将属性的值设置到结果对象内。
至此,一个结果对象装配完成,之后将返回上层业务代码进行处理
三.总结
mybatis初始化流程如下:
mybatis执行期间流程如下: