Mybatis体系结构与工作原理
1、工作流程:
在MyBatis启动的时候,主要在解析配置文件,包括全局配置文件(Mybatis-config.xml)、映射器配置文件(Mapper.xml) Mapper包含了我们怎么控制MyBatis的行为和我们要对数据库下达的指令,也就是我们的动态SQL,及入参类型,返回值映射,程序会把这些信息解析成一个Configuration对象。
在程序调用数据库操作Dao接口的时候,它在应用程序和数据库之间建立一次连接,就是创建了SqlSession对象。SqlSession对象由会话工厂SqlSessionFactory创建,SqlSessionFactory包含所有的配置信息,在容器初始化的时候由SqlSessionFactoryBuilder创建。
在执行数据库操作的时候,主要是Executor对象执行操作,并将操作核心流程设定为映射输入的参数映射,StatementHandler处理适配器,执行数据库操作,映射输出的参数映射;
2,mybatis的jar包构成
梳理清楚了整个mybatis的执行流程之后,我们再来看看其jar包,了解其实如何实现代码的;
annotations:注解
binding:接口与statement的绑定关系,接口注册成的MapperProxy与最终通过MapperCommand执行SQL语句;
builder: 解析SqlMap文件,包括:classPath解析、根据路径加载文件按照xml分层解析文件、SqlMap解析、Statement解析
cache:缓存层
基本实现:缓存层的接口是Cache, 基本实现是PerpetualCache,一级缓存和二级缓存都使用这个实现,用HashMap保存缓存内容;
设计模式: 装饰器模式,通过装饰实现缓存功能的增强。
分 类:内部缓存分为功能性缓存类 和 淘汰策略类缓存(FifoCache,LruCahe、WeakCache、SoftCache);
缓存对象在什么时候被创建?什么时候被装饰?
1,一级缓存:默认开启使用,
作用域:sqlsession会话级别的,
创建:BaseExector中进行创建,初始化 LocalCache,
2,二级缓存:在全局配置中<setting>中 <cacheEnable> 开启, namespace中 <cache/>;
作用域:nameSpace级别,Mapper接口的级别,同一个接口的同一个方法相同参数就可以使用缓存
创建:在BaseExector的装饰者 - CachingExecutor中进行创建,他装饰在BaseExector外面,就能被提前执行了。为了实现跨会话调用缓存,而BaseExector创建在会话之后,因此要跨会话,必须在BaseExector之前创建。
使用:用户调用接口首先到二级缓存获取查询结果,然后再到BaseExector查询一级缓存是否能获取结果;
适用场景:同一个namespace *享,跨会话查询;少量的增删改操作(会清除缓存);其他mappering即namespace对这个namespace进行了操作,会对缓存进行清空。
使用第三方缓存:
全局配置中 <cache type:""> 修改缓存类型为第三方缓存;
cursor:游标
datasource 数据源,使用的工厂方法模式,DataSourceFactory下面有DbcpDataSourceFactory、JndiDataSourceFactory、SimpleDataSourceFactory;
executor:执行器,使用包装器模式 io:IO封装 javassist: jdbc lang logging:日志实现
mapping:Mapper.xml中对应的三大天王Statement、result、parameter
ognl parsing:解析 plugin:插件List存储 reflection:反射 scripting session:会话 transaction:事务管理 type:字符类型Handler
3、架构图
看完了jar包之后,我们再看看其架构图
接口层:
接口层的核心对象是SqlSession,他是上层应用和MyBatis打交道的桥梁。
SqlSession上定义了很多对数据库操作的方法,
接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。
核心处理层: 处理数据库操作
配置解析:主要解析配置文件为我们的java操作对象,包括配置,statement,parament,Result;
参数处理:按照配置解析的对象进行属性的映射,参数的映射和动态SQL的生成;
sql执行:执行SQL语句,三种基本实现;
结果映射:三种方式进行处理结果集,并映射成Java对象;
插件:插件也属于核心处理层,因为他的工作方式和拦截的对象都在核心层;
基础支持层:基础通用层
主要功能: 是由一些抽取出来的通用功能,用来支持核心处理层的功能。
包 括:数据源/连接池、缓存、日志、事务、反射等这些功能。
4,核心层代码深度解析
1、获取resource,读取配置文件
2、配置解析过程
1、解析了什么文件?
XmlConfigBuilder解析全局配置文件,根标签<configuration>,根标签里面是所有一级标签的解析(解析字标签及属性)然后把解析内容存放在Configuration对象对应的属性中;
XmlMapperBuilder解析Mapper配置文件;
XmlStatementBuilder解析 <select><update><insert><delete>标签中内容;
XmlMapperEntityBuilder 解析对象参数;
2、怎么解析的?产生了什么对象?结果存放在哪里?
XmlConfigBuilder解析一级标签:
properties:解析子标签和属性,然后设置值到Configuration中的属性Properties中;
settings:将其关键属性设置给Configuration,在不存在的时候设置默认值;在加载远程配置的时候需要其配置处理,因此先进行配置加载,后进行处理;
typeAliases: 类型别名,存放在TypeAliasRegistry中
plugins:插件解析,存放在InterceptorChain中
objectFactory:创建实体类对象
objectWrapperFactory:创建实体类包装对象
reflectorFactory:反射工厂
environment:产生事务工厂,数据源工厂,并通过数据源工厂产生一个数据源,
databaseIdProvider:支持对数据的场景;
typeHandlers:java类型和jdbc类型的相互转换,存放在TypeHandlerRegistry中;
mappers:mapper映射路径去加载Mapper; 四种映射路径配置方式:package、resource、url、class; 解析方式:①XmlMapperBuilder类创建调用parse()方法解析,对基本配置进行解析之后创建XmlStatementBuilder对增删改查sql进行解析,
②然后调用MapperBuilderAssistant#addMappedStatement()方法产生了一个MappedStatement, ③解析完成之后XmlMapperBuilder调用bindMapperFprNameSpace()将Mapper与命名空间绑定,将Mapper存放在MapperRegistry的属性knownMappers中,将命令空间接口和MapperProxyFactory工厂进行绑定;
3、会话创建过程:build方法创建SqlSession,然后openSession创建一个会话,会话中传入命名空间获取一个Mapper;
1,基本实现类:DefaultSqlSession创建,调用方法openSessionFormDataSource;
首先,获取environment对象,用事务工厂创建事务;有两种模式:JDBC和Manager两种方式,Manager为容器管理
然后,按照指定类型,创建Executor对象; 类型:BatchExecutor:支持Stament对象重用和批量语句执行,对比update方法 ReuseExecutor:支持Stament对象的重用,执行完之后会缓存起来,下次用; SimpleExecutor:普通执行;
设计模式:模板方法:BaseExecutor实现模板类,具体的增删改查都交给三大子类实现,
包装器模式:创建二级缓存,用CacheExecutor包装BaseExecutor【new CachingExecutor(executor)】;
责任链模式:interceptorChain.pluginAll(executor) 将插件遍历出来进行链式包装executor。
然后创建DefaultSqlSession,包含Executor、Configuration返回结果;
4,获得Mapper对象
1,为什么要引入Mapper对象?
解决以前调用Mapper传入接口号的硬编码问题;无法知道该接口是否存在,无法知道该接口被那些地方调用,调用了多少次。
2,Mapper对象是什么对象?
是由MapperProxyFactory使用new MapperProxy(sqlSession, mapperInterface, methodCache) 创建;内部用jdk动态代理实现,
【疑问?】Mybatis的动态代码没有接口实现类的实例?为何不需要实现类?
只要确保在接口对应的方法 能在Mapper中找到对应的id就可以了,不用一定要有实现类;调用任何都会跑到MapperProxy的invoke方法去,调用MapperMethod#execute() 获取DefaultSqlSession(); 查询的话根据返回结果类型:游标,null,嵌套对象,Map调用对应的查询回来方法;
3,为什么要从SqlSession里面去获取?
sqlSession代表一次会话,一次会话包含了整个sql执行的过程,且创建过程所需要的configurations在SqlSession中;
4,为什么传进去一个接口,然后还要用接口类型来接收?
传进去接口是为了在knownMappers中用key去查找到对应的Mapper工厂,用接口类型来接收其一是方便对接口进行包装,其二是回传的并不是具体的实现,具体的实现还需要其调用对应的invoke方法去实现具体的调用。
5、执行sql ,实现类DefaultSqlSession # selectList()
1,接口方法是怎么找到要执行的SQL的?
接口类型+方法名构成的字符串statement去获取的;
2,方法参数是怎么转换成Sql参数的?
StatementHandler,根据类型创建不同的StatementHandler, 默认创建PreparedStatementHandler{
实现基本属性赋值:configuration、executor、mappedStatement、rowBounds、boundSql、typeHandlerRegistry、objectFactory;
创建对象:parameterHandler、resultSetHandler;入参处理器,结果处理器;
}
设计模式:
拦截器,可以对Executor、 StatementHandler、parameterHandler、resultSetHandler实现拦截;实现拦截调用interceptorChain.pluginAll( handler) 实现;
3,结果集怎么转换成对象?
DefaultObjectFactory实现,
4,缓存的key产生机制,二级缓存在配置Mapper的时候根据配置创建二级缓存装饰BaseExecutor;
CachingExecutor#createCacheKey() mapper+id+翻页信息(开始偏移量+ 条数) +sql +参数信息
5,核心对象总结
核心对象详解文件参考:https://www.cnblogs.com/zsg88/category/1080098.html
Configuration:配置
DefaultSqlSession:会话
Executor:执行器
StatementHandler:jdbc的封装
ParameterHandler:入参数处理,进入sql
ResultSetHandler:结果处理
MapperProxy:实现invoke
MappedStatement:对 增删改查节点的封装
6,设计模式
工厂 SqlSessionFactory
单例 SqlSessionFactory Configuration
建造者 SqlSessionFactoryBuilder
装饰者 CachingExecutor batch simple resue; 缓存
代理 SqlSessionInterceptor MapperProxy Plugin 延迟加载实现-ProxyFactory
Log:ConnectionLogger 、StatemLogger实现对执行器的代理
PooledConnection
模板 : Executor BaseExecutor batch simple resue
适配器: log4j是具体的日志的实现, sl4j 是抽象的接口; 在应用中使用sl4j就可以轻易的切换;用具体的log4j很难切换,在需要自定义实现就适配实现
责任链 InterceptorChain
策略模式:Handler 的实现