最近花了两三天把我以前看过的《MyBatis技术内幕》又看了一遍,感觉这次受益匪浅,整本书从基础到上层讲解,对于各种xml等基础知识点也扩展了一堆,又在必要时设计模式神插入,就认真的做了一下笔记。
不过最近才开始做源码笔记,不太清楚怎么写
输入参数类型
可以是Map、List 等集合类型,也可以是基本数据类型和 POJO 类型
Mybatis的一级、二级缓存
一级缓存与session绑定
二级缓存与Namespace绑定
所有缓存在对应域进行增删改后 都会被清空
openSession
创建执行器(三种:普通,重用,批量)
代理执行,不拦截Object中的方法
session.getMapper创建代理
拦截执行代理
最终还是根据方法签名调用到session.selectOne
查询前会使用缓存
执行流程
#{}和${}的区别
在配置文件中${}用于引用属性文件
在sql中引用变量#{}会使用?占位符,${}会直接拼接
MyBatis默认使用RowBounds进行分页,非物理分页,会查询所有再分页
属性名与列名不同映射
对于属性名的大小写会直接忽略,若还是不同,可以开启驼峰命名法,若还是不同,可以使用resultMap手动映射,或sql查询起别名
Mybatis分层
xml文档解析
DOM需要全部读取进内存 SAX通过流式回调处理,需要程序员自己维护层级逻辑 StAX采用拉模式,程序通过调用解析器进行解析
XPathParser封装了XML解析逻辑
通过流文件构建文档
用于解析各种数据类型(都是通过先解析为String再转化过去)
用于处理有占位符的文件
简单使用
存储xml节点类,会在创建时解析属性与内容(就是内部循环解析)
反射工具封装
Method#isBridge() 判断方法是否为虚拟机自动生成的
用于返回方法的唯一标识
防止子类重写父类的Getter方法造成一个属性多个Get方法,只保留一个(Set类似)
Invoker封装常见方法的调用(基本都是简单调用Method#invoke)
ReflectorFactory提供对Reflector的缓存与创建
唯一实现类,非常简单
Type所有类型的父接口
MyBatis内部使用TypeParameterResolver提供的静态方法对Type处理
对象工厂
第一个create使用无参构造器,第二个使用有参构造器
DefaultObjectFactory是其唯一实现类
解析对应接口要返回的实际类型,create方法实现就是上面说的非常简单
属性解析工具类
负责解析name[0].age[1].id这种有" [ ] . "存在的属性
负责方法名到属性名转换的工具类
PropertyCopier负责对对象属性递归拷贝
封装对象的元数据信息,默认使用静态方法构建
ObjectWrapper负责对对象的封装,使用ObjectWrapperFactory创建
BaseWrapper主要实现了集合的操作
对对象数据再封装
可以使用TypeHandler对自定义类型进行映射
BaseTypeHandler实现TypeHandler做了非常简单的封装(跟直接用原来的几乎没有区别)
用于管理TypeHandler(还默认注册了大量TypeHandler)
根据注解,或接口信息确定注册的TypeHandler类型
根据转换类型获取TypeHandler
MyBatis类型转换使用Map缓存查找式,Spring类型转换使用责任链轮询式
类型别名注册,默认已经注册了大量别名
别名默认为简单类名,可以使用注解指定
日志模块只是对其他日志实现进行简单的封装
懒加载原理
使用代理,在真正需要数据时,再调用代理获取数据
用于格式化打印sql日志
主要记录的要跟踪打印的方法,sql执行层数(用于格式化sql)
通过代理对Sql日志打印
监听连接创建Statement等的方法返回对应的代理对象
代理对象对感兴趣的方法返回代理结果集
最终在ResultSetLogger代理结果中输出格式化sql,结果,前面
Tomcat的自定义类加载器
Tomcat为每个部署的应用创建一个唯一的WebAppClassLoader,它负责加载应用自己的jar包与class文件,使不同Web应用相互隔离,热部署时,丢弃原来的ClassLoader创建新的,父类都是CommonClassLoader可以共享其中的类库
ResolverUtil通过Test寻找合适的类
Test主要实现类,根据类型判断(IsA),根据注解判断(AnnotatedWith)
VFS用于遍历路径资源,优先添加用户自定义的
默认实现有JBoss6VFS与DefaultVFS
数据源工厂主要有池化与非池化
setProperties用于设置属性,getDataSource用于获取数据源(上图UnpooledDataSourceFactory的实现,池化的只是返回对象不同)
非池化数据源,每次都创建新连接(先初始化驱动,再获取连接,最后配置连接的自动提交,隔离级别等属性)
池化数据库返回的是一个代理,记录各种时间戳完成长时间空闲关闭等,在调用关闭时会被拦截放入连接池,并非关闭
判断是否有效,最终会执行一条PING语句
对活跃连接,空闲连接全部回滚关闭(空闲连接与这类似)
事务Transaction的两个实现类
JdbcTransaction只是对原来连接方法的简单封装,后者commit与rollback都是空实现依赖容器实现
MapperRegistry用于注册接口映射
获取Mapper代理
MapperProxyFactory主要创建代理
还有对应的方法接口信息
MapperProxy处理具体代理逻辑
会放行默认Object方法,对方法调用信息进行缓存
Mapper接口与xml配置文件的桥梁
SqlCommand记录Sql语句与其类型,MethodSignature记录接口方法信息,执行时根据sql类型选择执行
ParamNameResolver负责解析方法参数
参数解析会过滤掉RowBounds与ResultHandler,会先尝试获取注解,再获取方法名还是为null直接使用map大小
根据具体参数值与解析的参数信息,封装命名参数对象,方便后面使用,注意0,1,2等会被注册为param1等
MapperMethod核心方法execute
INSERT,UPDATE,DELETE都类似,都是先转换参数再调用对应的insert,delete等最后包装返回
SELECT稍微麻烦需要,根据返回值封装返回对象
rowCountResult根据影响行数与返回结果包装返回null,int,long,boolean
对与SELECT若参数中有ResultHandler,会优先调用它处理返回结果,其中会判断是否有RowBounds参数
SELECT返回值转换为其他类型也都要RowBounds参数是否存在的判断
上面的方法最终都是调用SqlSession的数据库操作方法
缓存使用装饰器
Cache有很多实现类,但只有PerpetualCache实现了逻辑(简单的Map缓存),其他都是装饰器(构造器要求必须传入包装Cache)
MyBatis缓存依据多个因素使用CacheKey控制
解析使用建造器模式
核心属性依次为:全局配置(最重要,后者都来源于它),类型别名注册器,类型处理注册器
XMLConfigBuilder上面的子类负责解析逻辑
解析各种节点属性
解析properties配置文件
MyBatis真是纠结症,遇到不确定的不能选个默认规则,直接报错
处理设置为属性类
注册别名
在registerAlias中会自动处理注解
注册类型处理器(与上面类似)
处理插件
把对应的配置转为Properties设置进拦截器,最后添加拦截器进配置文件
拦截器底层使用拦截器链管理
与拦截器处理极其类似
解析开发环境信息
MyBatis惯用套路
把对应配置直接转为Properties,然后实例化对象,直接把Properties丢给对象,让对象自己处理
解析提供的数据库Id
MyBatis默认不支持自动转换sql适应不同数据库,可用为sql指定数据库Id显示切换不同数据库语句
获取数据库Id最终使用连接的元数据信息获取的
解析Mapper
先根据package选择,再根据resource,url,class选择且必须唯一(没有默认优先级)
具体解析Mapper文件
解析Mapper的各种节点
解析二级缓存标签
可见对标签有大量的默认值定义
builderAssistant负责根据信息创建缓存对象
newCacheDecoratorInstance体现了缓存的装饰器模式
判断缓存是否有对应属性的set方法有转换类型进行调用
解析命名空间引用缓存
根据命名空间共用缓存
解析ResultMap
解析单列属性
使用建造者模式对根据属性建造对象
对映射的继承进行分析
解析映射构造器
鉴别器的处理
涉及到对嵌套结果映射的处理
解析sql片段
一条映射信息
其中SqlSource表示一条sql语句信息
映射通过XMLStatementBuilder#parseStatementNode进行解析
处理include节点
先找对应的sql片段,在获取内部属性用于填充sql片段参数,最后替换sql标签,其中包含递归解析sql中可能还有include
对应selectKey依旧是先获取信息再使用建造者
selectKey用于自定义查询数据库获取当前主键值,sql类型只能是select
对应sql语句的解析根据是否动态创建不同的
parseDynamicTags
边解析,边判断是否为动态sql,若文本对象存在${}或有节点对象都是动态sql
通过nodeHandlerMap获取对应的标签处理器,若获取为空会直接报错
DynamicCheckerTokenParser为对应的TextHandler但是不处理任何文本替换逻辑,只是记录是否发生过${}的文本替换
对应不同的标签处理都是先获取信息创建对应的节点对象加入集合
这是foreach标签处理过程
处理命名空间
会把命名空间加入配置,防止重复解析,并添加Mapper接口映射
添加接口Mapper时会构建MapperAnnotationBuilder使用parse处理接口上的注解
对注解的解析与xml类似不过没有共用
重新解析失败的对象集合
xml解析是从上往下的,上面的对象可能引用下面的对象造成解析失败暂存起来,最后再次解析
挨个重新解析,解析一个移除一个,若再次出错,直接忽略
Mybatis使用OgnlCache对Ongl封装使其支持缓存
只是简单的Map缓存
DynamicContext负责拼接sql语句
ContextMap继承HashMap并对get进行重写
增加了更多的取值途径(类似Stuts2包装的request)
SqlNode负责调用DynamicContext的拼接方法
例如if的apply方法,返回布尔值表示是否解析该标签内部标签
SqlSourceBuilder处理SqlNode拼接完毕的sql
主要处理占位符(GenericTokenParser),最终都是要处理为静态sql(StaticSqlSource)
文本处理器为ParameterMappingTokenHandler
对于#{}直接获取对应的参数,并替换为?
处理#{name,javaType=string}等占位符信息
BoundSql主要记录sql及其参数信息
DynamicSqlSource#getBoundSql
会调用sqlNode的apply方法拼接sql
RawSqlSource
RawSqlSource是只包含#{}或普通的sql语句,直接对#{}替换为?并保留对应的参数信息即可
ResultSetHandler的唯一实现类DefaultResultSetHandler
ResultSetHandler用于处理返回的结果集,DefaultResultSetHandler根据xml配置处理返回结果集
遍历处理每行结果
处理单行数据按有没有父mapping或是否用户提供了resultHandler来处理
有嵌套结果集的处理
简单结果集的处理
默认对应数据偏移的处理
根据是否需要停止与查询条数判断是否需要停止
获取对应的ResultMap
根据结果映射封装结果
根据数据创建对象,若有嵌套查询且为懒加载使用代理对象
创建对象,分4中情况
1,结果集只有1列且有对应的TypeHandler转换类型为对应的Java类型
2,有构造器信息使用构造器
3,有默认构造器,使用objectFactory用默认构造器创建对象
4,根据构造器签名寻找合适的构造器
获取对应的构造器参数,使用构造器创建
根据构造器签名获取对应的构造器
先获取所有构造器依次判断
获取对应的构造器参数
用于存储处理每一行的结果
记录映射过与没有映射过的列名
自动映射行为
先判断ResultMap是否配置,否则根据是否嵌套映射与全局配置做判断
对没有映射的列进行自动映射(有缓存机制)
获取数据结果3种情况:1,嵌套查询结果 2,多结果集处理 3,普通结果集的处理
存储结果
若存在父映射,放入父映射,否则直接放入resultContext
处理集合属性
若没有获取到Java类型会通过属性对应的Setter方法获取,判断是否是集合,若是由objectFactory创建处理
延迟加载主要看fetchType或配置文件的全局配置
因为JavaBean大多数没有实现任何接口,懒加载使用CGLIB或Javassist进行代理
ResultLoader用于保存一次延迟加载所需的信息
延迟加载主方法
ResultLoaderMap存储懒加载的属性
load加载指定属性,loadAll加载所有属性
pair.load();最终创建ResultLoader加载结果
代理对应的拦截器(这时CGLIB的,Javassist与此类似)
lazyLoader用于存储懒加载属性,aggressive触发属性加载时是全部加载还是只加载部分属性(全局配置文件中配置)
lazyLoadTriggerMethods触发延时加载的方法列表
Jdbc3KeyGenerator用于自动生成主键
在数据库操作后进行处理
把返回的结果(id)封装,最终还要给对象赋值(例如INSERT)
SelectKeyGenerator用于sql语句生成主键
在处理前或后执行sql语句
执行sql语句获取主键
StatementHandler串联处理过程的类
prepare获取对应的Statement对象,parameterize进行参数填充
RoutingStatementHandler
自己不实现功能,根据传入类型创建对应的实现,功能实现都靠创建的实现类
BaseStatementHandler对sql执行的一些公共数据进行定义
执行查询
执行,增删改等
DefaultParameterHandler对PreparedStatement设置参数
StatementHandler初始化参数就是调用ParameterHandler
PreparedStatementHandler
获取PreparedStatement
Mybatis有哪些Executor执行器
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:使用先查询map,没有创建,使用完毕放回map 实现复用
BatchExecutor:将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理
设置:可以在配置文件的settings标签设置(默认设置),也可以在openSession(枚举)设置(覆盖设置)
BaseExecutor实现了大部分方法,关键部分留下模板方法让子类实现
查询时会使用缓存
增删改时会清除一级缓存
查询时会先在缓存中放占位符,查找完毕再设置具体值
提交方法,会清除缓存,执行缓存的sql语句,必要的话提交事务
ReuseExecutor会通过sql与Statement管理对Statement复用
BatchExecutor每个Statement都包含多个sql语句
相同的sql放到同一个Statement里
DefaultSqlSessionFactory
根据数据库配置文件获取
根据传入数据库连接获取
就这两种方式
SqlSessionManager
既可以用于获取SqlSession,也可直接当SqlSession操作
获取SqlSession源于传入的SqlSessionFactory,SqlSession操作源于sqlSessionProxy拦截器中实际使用每个线程绑定的SqlSession
简述Mybatis的插件运行原理,以及如何编写一个插件
原理对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口事件进行拦截
实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置编写的插件
注解解释:可以使用多个Signature拦截多个方法,type拦截那个对象,method拦截那个方法,args用于区分方法重载
根据配置创建执行器
通过CachingExecutor实现二级缓存(装饰器)
通过pluginAll使用插件对执行器层层代理
在拦截器中拦截方法的执行实现插件
与Spring对接的ClassPathMapperScanner
主要是把符合条件接口的BeanDefinition设置类信息为MapperFactoryBean