本人博客地址:烤包子
文章地址:Mybatis 源码类功能介绍
该篇文章和后面发布的Mybatis源码执行流程、Mybatis拦截器源码,缓存源码等,以及下面的源码工程配合使用效果更好。
后面会将Mybatis相关博客同步掘金,欢迎各位掘友交流。
引用:通读源码阅读指导书 - Mybatis源码详解 作者:易哥本人对源码的标注,每一个包的package-info都做了说明,以及测试工程都放在了工程:
Mybatis 源码类功能介绍
首先说明,为什么会有这篇文章的存在,阅读源码指导书可以说是博主开启源码阅读之类的启蒙,虽然以这种对每个包里面的功能划分和断点测试,单元测试对类进行学习需要大量的时间精力花费,但是比起我们以前看源码时候只是关注源码的执行流程,其实就是某一个源码功能的业务流程,有着很大的提升。
就以Mybatis为例,我们都知道他是作为一个ORM框架,我们只需要简单的配置 Mapper映射就可以只专注于写SQL业务,而不用关注Mybastis本身如何解析 XML 标签,配置信息,以及和 JDBC之间的交互,同时也做到后端语言(如:Java)基础类型,对象类型和数据字段类型的映射。
那他是如何实现的呢,如果我们只关注的Mybatis执行代码的主流程,这些细节性的考虑实现我们是学不到的;同时在一些代码设计场景上,用到了一些设计模式也是我们可以借鉴的。说到设计模式,我们其实不能只去学习它本身,而是有着应用场景的支撑,才有现在我们看到的设计模式的出现。
Mybatis也一样,他本身就是为了解决我们程序员在开发过程中遇到的痛点(如果没有ORM框架,那我们写SQL的同时就要关注数据库类型和语言类型的适配,JDBC链接数据库问题,事务处理等各种问题),所以,抛开应用场景谈框架都是扯犊子,我们应该通过阅读源码,或者在阅读源码的过程中知道框架主要为了解决什么问题,以及在框架某些功能设计中解决了什么问题,怎么解决的,同时,学以致用到我们自己的项目中,这才是我们学习源码本身的意义。
好了,废话说完!
本篇文章主要将mybatis源码20个包,按功能进行划分,并对包中的类功能进行分析。
按照功能将包划分为:
-
基础功能包:
- exceptions
- reflection
- annotations
- lang
- type
- io
- logging
- parsing
-
配置解析包:
- binding
- builder
- mapping
- scripting
- datasource
-
核心操作包:
- jdbc
- cache
- transaction
- cursor
- executor
- session
- plugin
一、基础功能包
1.1 exceptions
rust
1. 该包顾名思义,异常处理包;下面的类层层继承,层层包装
-> Throwable
-> Exception
-> RuntimeException
-> IbatisException
-> PersistenceException
-> TooManyResultsException
2. 我们从PersistenceException下面的子类可以看到,有许多包都里面有各自的对于特定异常的处理处理子类
像:
基础包:
-> LogException
-> ReflectionException
-> TypeException
-> ParsingException
配置包:
-> BindingException
-> BuildingException
-> ScriptingException
-> DataSourceException
核心操作包:
-> SqlSessionException
-> CacheException
-> ExecutorException
-> TransactionException
-> ResultMapException
-> PluginException
3. 每个类中内容都很简单,4个构造方法,往上抛就完事了。
1.2 reflection
1、 wrapper: (包装对象)
dart
1.ObjectWrapper: 它是对象或者对象集合的包装器,是一个接口。
该接口中的一些方法,就是该包的主要功能了。
ege:
get/set一个属性的值;
获取所有 有getter/setter方法的属性列表;
获得制定属性的set/get方法的类型;
判断某一个属性是否有get/set方法。
2.BaseWrapper: 作为ObjectWrapper的一个是实现类,它只是一个抽象类,并没有对ObjectWrapper里面的方法进行是实现;
但是,它里面却有一个至关重要的属性 MetaObject metaObject(下面有对该类的介绍)
3.(CollectionWrapper,MapWrapper,BeanWrapper)这个三个呢,是一组,分别用来包装Collection、Map、Bean
这三个就实现了BaseWrapper,虽然实现的是BaseWrapper,但是这三个主要是还是实现 BaseWrapper继承的ObjectWrapper里面的接口方法
4.ObjectWrapperFactory、DefaultObjectWrapperFactory:
细枝末节,无关紧要。
2、 MetaObject/MetaClass/SystemMetaObject:(元对象/元类/系统元对象)
scss
MetaObject:
里面具有的属性,像:原始对象(Object),对象包装器(ObjectWrapper),
对象工厂(ObjectFactory),对象包装器工厂(ObjectWrapperFactory),反射工厂(ReflectorFactory)
这样,上面所属的这些属性都被封装在了MetaObject中,
只要用到属性的这些信息,我们就可以直接对某一个对象定义一个MetaObject来使用上述的这些工具类。
MetaClass:
它针对类的进一步封装,其内部集成了类可能使用到的反射器和反射工厂。
SystemMetaObject:
只是定义了一些默认工厂。
3、反射:
arduino
Reflector(核心类),[ReflectorFactory接口,DefaultReflectorFactory实现类]
1.Reflector:
此类表示一组缓存的类定义信息,允许在属性名和getter/setter方法之间轻松映射。
并且通过该类中定义的一些属性,也就是一些属性缓存,便于后面的反射工厂使用。
如:Ⅰ、有get方法的属性数组,Ⅱ、有set方法的属性数组;
Ⅲ、setter方法列表,Ⅳ、getter方法列表, 都作为map缓存;
Ⅴ、setter方法输入类型,Ⅵ、getter方法输入类型,也是作为map存储; and so on.
★最重要的是,对一个类进行上述信息的缓存,是直接通过该对象的一个构造方法完成的。
public Reflector(Class<?> clazz) {...}
2.反射工厂
反射工厂的作用:所有利用到反射的对象,都会在findForClass方法中对该对象初始化Reflector对象,然后缓存到该reflectorMap中
4、 参数名称解析器:
less
ParamNameUtil/ParamNameResolver/TypeParameterResolver
1.ParamNameUtil: (细枝末节,无序关心。)
工具类:通过方法,构造方法获取参数名
2.ParamNameResolver:
解析方法的参数列表,并缓存到ParamNameResolver的SortedMap<Integer, String> names属性中
ege:
TestMapper selectTwoParam(@Param("id") Integer id, String name);
names:{
{0,"id"},
{1,"arg1"}
}
可以看到,有@Param注解和无注解在map中的属性名的区别。
3.TypeParameterResolver: (上面的base.genericparadigm就是对该类的测试使用)
这个类是这三个类中最重要的类;帮助Mybatis推断出属性、返回值、输入参数中泛型的具体类型.
最主要的三个方法:
1. resolveFieldType:解析属性的泛型
2. resolveReturnType:解析方法返回值的泛型
3. resolveParamTypes:解析方法输入参数的泛型
1.3 annotations、lang
注解类,没啥说的,只要对java元注解和自定义有一定了解,该包没啥好理解的。
1.4 type
sql
该type包的主要作用是:
我们知道Java和SQL之间的数据类型存在差异,此包就是用于解决这个问题的。
像:Java的String要对应SQL中的char,varchar等这些。
★ 核心类:TypeHandlerRegistry,通过研究它,就可以知道该包的作用了。对于上面所说的问题也就得到了解决
1、类型处理器
yaml
类型处理器:1个接口,1个基础实现类,一个辅助类,43个实现类。
★关于这个里面的所有类型,可以多试试 测试包(org.apache.ibatis.type)里面的测试案例★
1.TypeHandler: 类型处理器接口
2.BaseTypeHandler: 类型处理器的基础实现
继承了TypeReference,并且实现了BaseTypeHandler抽象类
3.TypeReference: 类型参考器
4.*TypeHandler: 43个类型处理器器
这43个处理器,一些是直接实现的TypeHandler接口,一些是继承了BaseTypeHandler
2、类型注册表
python
类型注册表:3个
这三个类型注册表的存在,使Mybatis不仅可以根据类型找到其对应的类型处理器,而且还可以根据类型别名寻找对应的类型处理器。
SimpleTypeRegistry:
基本类型注册表,内部使用Set维护了所有Java基本数据类型的集合;
TypeAliasRegistry:
类型别名注册表,内部使用HashMap维护了所有类型的别名和类型的映射关系;
ege:
[int,Integer.class],[integer, Integer.class];
[float[],Float[].class];
[_int,int.class],[_boolean,boolean.class]; 这些等等。
★TypeHandlerRegistry:(这个是核心)
类型处理器注册表,内部维护了所有类型与对应类型处理器的映射关系。上面的43类型处理器就是注册在这个类的map中了;
详细的map可以进入该类看其注解;
在所有的类型处理器都已经归类到map中之后,我们就可以通过该类中的方法,利用JDBC类型和JAVA类型知道对应可以处理该类型的类型处理器了;
可以以:getTypeHandler(Type type, JdbcType jdbcType)方法测试一下。
3、其他:
sql
注解类3个:
@Alias : 给类起个别名,打上该注解,别名和类型的映射关系遍存入TypeAliasRegistry
@MappedJdbcTypes : 被映射的jdbc类型;
使用自己的处理器来处理某些JDBC类型,只需创建BaseTypeHandler的子类,然后在上面加上该注解,声明它要处理的JDBC类型即可;
@MappedTypes : 被映射的Java类型;
用自己的处理器处理Java类型;只需创建BaseTypeHandler的子类,然后在上面打上该注解,声明它要处理的Java类型即可。
异常类1个:
TypeException:表示与类型处理相关的异常。
工具类1个:
ByteArrayUtils:提供数组转化的工具方法。
用于字符 byte[],Byte[]之间的转换。
枚举类1个:
JdbcType:在Enum中定义了所有的JDBC类型,类型来源于java.sql.Types。
像:bit,double,float,time,date,char,varchar,null,nclob,clob,blob等等
1.5 io
-
该包的作用:负责完成 MyBatis中与输入/输出相关的操作;
- 1、对xml文件
- 2、对class文件
1、磁盘文件系统介绍:
markdown
在操作磁盘文件时,软件程序不需要和实体的文件系统打交道,只需要和 VFS沟通即可。
__________________________________________
| 上层软件 |
|_________________________________________|
| VFS |
|_________________________________________|
| FAT | VFAT | NFS | NTFS | ... |
| ________________________________________|
2、VFS实现类/DefaultVFS/JBoss6VFS:
yaml
VFS实现类/DefaultVFS/JBoss6VFS:
1.VFS:
作为一个 abstract类,维护了内置的DefaultVFS、JBoss6VFS,还有用户自定义的。
2.DefaultVFS/JBoss6VFS:
编写了对文件加载方法。
3、ClassLoaderWrapper/Resources/ResolverUtil:
css
ClassLoaderWrapper/Resources/ResolverUtil:
1.ClassLoaderWrapper(类加载器的包装器)
① 里面定义了5中类加载器:(详细的还是进入该类看注释)
· classLoader 作为参数传入的类加载器,可能为 null;
· defaultClassLoader 系统默认的类加载器,如未设置则为 null;
· Thread.currentThread().getContextClassLoader() 当前线程的线程上下文中的类加载器;
· getClass().getClassLoader() 当前对象的类加载器;
· systemClassLoader 系统类加载器,在 ClassLoaderWrapper的构造方法中设置。
② classForName方法,通过(要加载类的全称限定名,类加载器)获取要加载的类
③ 剩下的也是加载 Class类的方式
2.Resources
该类又对 ClassLoaderWrapper 类进行了一些封装,用于获取Class文件
3.ResolverUtil
是一个工具类,主要功能是完成类的筛选。(详细的还是进入该类看注释)
这些筛选条件可以是:
· 类是否是某个接口或类的子类; IsA
IsA类中的matches方法可以判断目标类是否实现了某个接口或者继承了某各类;
· 类是否具有某个注解。 AnnotatedWith
AnnotatedWith类中的 matches方法可以判断目标类是否具有某个注解。
1.6 logging
1、日志级别:
javascript
日志级别:
· Fatal:致命等级的日志,指发生了严重的会导致应用程序退出的事件;
· Error:错误等级的日志,指发生了错误,但是不影响系统运行;
· Warn:警告等级的日志,指发生了异常,可能是潜在的错误;
· Info:信息等级的日志,指一些在粗粒度级别上需要强调的应用程序运行信息;
· Debug:调试等级的日志,指一些细粒度的对于程序调试有帮助的信息;
· Trace:跟踪等级的日志,指一些包含程序运行详细过程的信息。
2、Log接口
vbscript
Log接口:(下面11个实现类)
① NoLoggingImpl:不打印日志,内部没有任何的操作逻辑。
② StdOutImpl:对于Error级别的日志调用System.err.println方法进行打印,而对于其他级别的日志则调用System.out.println方法进行打印。
③ Slf4jLocationAwareLoggerImpl/Slf4jLoggerImp:是Slf4jImpl的装饰器
④ Log4j2AbstractLoggerImpl/Log4j2LoggerImpl:是Log4j2Impl的装饰器
⑤ Jdk14LoggingImpl
⑥ JakartaCommonsLoggingImpl:
JakartaCommonsLoggingImpl 是一个典型的对象适配器。
它的内部持有一个"org.apache.commons.logging.Log"对象,然后所有方法都将操作委托给了该对象。
(具体看该类里面的注释)
⑦ Log4jImpl
LogFactory:
就是制造Log实现类的工厂;
最终,该工厂会给出一个可用的Log实现类,由它来完成MyBatis的日志打印工作;
就是调用getLog()方法获取。
3、jdbc包
scss
jdbc包:
1. 该包的作用:
由于JDBC有自己的日志,Mybatis有自己的日志,那么二者日志分开对于我们调试和发现问题比较麻烦;
所以,jdbc子包基于代理模式,让MyBatis能够将JDBC的操作日志打印出来,用于解决上述问题。
2. BaseJdbcLogger:
作为基类提供了一些子类会用到的基本功能;
3. 4个子类(代理类)
ConnectionLogger(Connection)
PreparedStatementLogger(PreparedStatement)
ResultSetLogger(ResultSet)
StatementLogger(Statement)
这四个类作为 BaseJdbcLogger的子类,★实现了InvocationHandler,括号里面的作为代理类,这四个类作为相应的代理类。
这四个类作为相应的日志代理类大同小异,可通过看ConnectionLogger里面的注释,做到对另外3个类的一通百通。
★记好:这几个日志类,我们知道上面的connection连接数据库,statement数据库语句处理,resultSet结果集,
当这些JDBC操作进行的时候,执行的是这里的代理Logger类,会在执行上述操作的同时打印日志。
1.7 parsing
makefile
XPathParser:
整个XPathParser类本质就是对"javax.xml.xpath.XPath"的封装和调用,可以把 XPathParser类看作javax.xml.xpath.XPath类的包装类。
XNode:
可以将 parsing 包中的XNode类看作org.w3c.dom.Node类的包装类。
org.w3c.dom.Node类是用来表示DOM中节点的类,而XNode类只是在org.w3c.dom.Node类的基础上提取和补充了几个属性。
可是如何才能让这些信息在 XML 文件的解析中发挥作用呢?
MyBatis配置文件中 properties节点会在解析配置文件的最开始就被解析,并在解析后续节点时发挥作用。
二、配置解析包
2.1 binding
该包主要用于处理Java方法与SQL语句之间绑定关系;就是Java写的mapper接口中的方法与mapper.xml中SQL语句的绑定。
binging包主要连个功能:
- 1、维护映射接口中抽象方法与数据库操作节点之间的关联关系;
- 2、为映射接口中的抽象方法接入对应的数据库操作。
scss
先说一下:MapperMethod
这个类将数据库一次一次的操作和Java方法绑定起来;(也就是一个映射接口中的方法对应一个MapperMethod对象)里面两个属性,一个方法;
属性:
SqlCommand:记录SQL所绑定的映射接口方法名以及类型
ege:
{
"name":"org.apache.ibatis.binding.BoundBlogMapper.selectBlogUsingConstructorWithResultMap",
"type":"SELECT"
}
MethodSignature: 内部的属性详细描述了一个方法的信息
方法:
execute(): 调用JDBC执行具体的SQL操作了
MapperProxy:(MapperMethod的代理类)
作为MapperMethod的代理类,当有映射接口方法被调用时,就会来到该类的invoke方法执行代理方法,
并在invoke方法中会调用到 MapperMethod的execute()方法执行sql操作。
MapperProxyFactory:(代理工厂)
为映射接口(属性:mapperInterface)中的方法通过newInstance()方法生成代理对象(mapperProxy)
MapperRegistry:
knownMappers属性将映射接口与代理(MapperProxyFactory)关联起来;
ege:
TestMapperDao mapper = sqlSession.getMapper(TestMapperDao.class);
最终就是来到 该类中的getMapper()找 对应映射接口的代理工厂同时生成代理对象实例返回
★这样一来:
TestMapperDao mapper = sqlSession.getMapper(TestMapperDao.class); //这里获取的其实就是上面说的代理工厂生成的代理类
mapper.selectByPrimaryKey(1); //通过代理类(MapperProxy)执行该方法对象的SQL方法。
2.2 builder
首先该包的主要功能是:
- 1、解析 XML配置文件和映射文件,这部分功能在 xml子包中;
- 2、解析注解形式的Mapper声明,这部分功能在 annotation子包中。
1、BaseBuilder
java
BaseBuilder
作为所有建造者的基类,虽然被声明成一个抽象类,但是本身不含有任何的抽象方法,因此它的子类无须实现它的任何方法。
BaseBuilder类更像一个工具类,为继承它的建造者类提供了众多实用的工具方法。
★★★但是 BaseBuilder中的所有子类的目的就是构建BaseBuilder中的三个属性。★★★
下面是继承了BaseBuilder的子类:
MapperBuilderAssistant 辅助类
SqlSourceBuilder 、ParameterMappingTokenHandler
XMLConfigBuilder
XMLMapperBuilder
XMLStatementBuilder
XMLScriptBuilder (在org.apache.ibatis.scripting.xmltags中)
2、MapperBuilderAssistant
makefile
MapperBuilderAssistant:
作为BaseBuilder的辅助类,用于命名空间、缓存共享、结果映射等,最终这些设置将解析生成不同的类;
也提供了辅助方法,如:Mapper 命名空间的设置、缓存的创建、鉴别器的创建等.
3、SqlSourceBuilder、StaticSqlSource
sql
(1)SqlSourceBuilder:
只能通过 parse 方法生产出StaticSqlSource这一种对象。
ege:
originalSql:
SELECT * FROM BLOG
WHERE ID in ( one = #{__frch_item_0}
AND two = #{__frch_item_1} AND three = #{__frch_item_2} )
则下面经过 String sql = parser.parse(originalSql);
sql替换为: SELECT * FROM BLOG WHERE ID in ( one = ? AND two = ? AND three = ? )
★ 然后生成一个 StaticSqlSource对象
(2)StaticSqlSource
SqlSource的4个子类中其中一个;为了生成一个BoundSql对象。
4、CacheRefResolver、ResultMapResolver
python
CacheRefResolver、ResultMapResolver
(1)CacheRefResolver缓存引用解析器
<mapper namespace="com.yogurt.example.mapper.TestMapperDao">
<cache-ref namespace="com.yogurt.example.mapper.UserMapperDao></cache-ref>
</mapper>
如上,前者会使用后者的缓存;CacheRefResolver类用来处理多个命名空间共享缓存的问题。
两个属性:
assistant是解析器;
cacheRefNamespace是被解析对象。
(2)ResultMapResolver
MyBatis的resultMap标签支持继承;并且 resultMap继承关系的解析 由ResultMapResolver类来完成。
ege:
<resultMap id="userMap" type="User" autoMapping="false">
<id property="id" column="id" javaType="Integer"/>
<result property="name" column="name"/>
</resultMap>
<resultMap id="girlUserMap" type="Girl" extends="userMap">
<result property="email" column="email"/>
</resultMap>
所以,这里girlUserMap的解析由ResultMapResolver完成
5、ParameterExpression (参数表达式解析器)
arduino
ParameterExpression: (参数表达式解析器)
可以到 org.apache.ibatis.builder.ParameterExpressionTest 测试案例中去测试;还不太懂实际场景,先往后看。
ParameterExpression 类继承了 HashMap,内部能以键值对的形式保存最后的解析结果。
ege:
expression="(id.toString()),attr1=val1, attr2=val2,attr3=val3"
解析结果:
map{
"expression":"id.toString()",
"attr2":"val2",
"attr1":"val1",
"attr3":"val3"
}
6、XML文件解析
xml
XML文件解析 (包:org.apache.ibatis.builder.xml)
解析两个类型的:1.配置文件,2.映射文件
(1)配置文件:
① XMLConfigBuilder
解析配置文件(configuration)
(2)映射文件:
① XMLMapperBuilder
映射文件(***Mapper.xml)的解析
② XMLStatementBuilder
用于解析 映射文件(xxxMapper.xml)的中 select、update、delete、insert 节点
③ XMLIncludeTransformer
include节点的解析是由 XMLIncludeTransformer负责的,它能将 SQL语句中的include节点替换为被引用的 SQL片段。
(3)XMLMapperEntityResolver(该类在XML的第二行,配置文件、映射文件都会涉及校验)
该类中的resolveEntity方法通过字符串匹配找出了本地的 DTD文档并返回,
因此MyBatis可以在无网络的环境下正常地校验XML文件。
ege: (resolveEntity方法去找本地的DTD文件)
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
7、annotation解析
scss
annotation解析 (包:org.apache.ibatis.builder.annotation)
解析映射接口文件中的带有 @Select... @SelectProvider... 注解的方法
(1)MapperAnnotationBuilder
注解方法映射解析的触发 MapperAnnotationBuilder#parse();
上述方法只在:org.apache.ibatis.binding.MapperRegistry#addMapper(java.lang.Class) 里面进行了调用。
里面对注解进行了区分:
① 直接注解映射 @Select/@Delete/@Update/@Insert
② 间接注解映射 @SelectProvider/@InsertProvider/@UpdateProvider/@DeleteProvider
(2)ProviderContext、ProviderMethodResolver
两个解析 间接接口映射的辅助类。
① ProviderContext
存储的是 间接接口映射的方法属性信息
② ProviderMethodResolver
该类的作用如下
ege :
@SelectProvider(type = AnnotationMapperProvider.class, method = "queryUserById")
List<TestMapper> queryById(Integer id);
该类中的default方法是为了从type的类中找到method匹配的方法.
(3)ProviderSqlSource
① 间接注解映射的解析类:
就是对映射接口中的方法打了 @SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider 注解的解析
② SqlSource的子类,能够根据*Provider的信息初始化得到;
③ 调用入口唯一,在MapperAnnotationBuilder#getSqlSourceFromAnnotations()方法中
2.3 mapping
mapping包主mapping包主要完成以下功能:
- SQL语句处理功能;
- 输出结果处理功能;
- 输入参数处理功能;
- 多数据库种类处理功能
1、MappedStatement、SqlSource、BoundSql
sql
MappedStatement、SqlSource、BoundSql:
(1) MappedStatement (解析实体类)
该对象完整的表述下面整个 insert节点内容。
<insert id="insert" parameterType="com.yogurt.example.mapper.UserEntity">
INSERT INTO test_mapper(name,age,address)
VALUES(#{name},#{age},#{address})
</insert>
(2) SqlSource (解析实体接口)
它对应了 MappedStatement中的 SQL语句:
INSERT INTO test_mapper(name,age,address)
VALUES(#{name},#{age},#{address});
该接口下面有4个实现类:
① DynamicSqlSource:
动态 SQL语句。所谓动态 SQL是指含有动态 SQL节点(如"if"节点)或者含有"${}"占位符的语句。
② RawSqlSource:
原生 SQL语句。指非动态语句,语句中可能含"#{}"占位符,但不含有动态SQL节点,也不含有"${}"占位符。
③ StaticSqlSource:
静态语句。语句中可能含有"?",可以直接提交给数据库执行。
④ ProviderSqlSource:
上面的几种都是通过XML文件获取的SQ 语句,而ProviderSqlSource是通过注解映射的形式获取的SQL语句
而 DynamicSqlSource 和 RawSqlSource 都会被处理成 StaticSqlSource,
然后再通过StaticSqlSource的 getBoundSql方法得到 SqlSource对象。DynamicSqlSource和 RawSqlSource都在 scripting包中。
(3) BoundSql
BoundSql是参数绑定完成后的 SQL语句.
BoundSql是 SQL语句中一个重要的中间产物,它既存储了转化结束的 SQL信息,又包含了实参信息和一些附加的环境信息。
2、ResultMap、ResultMapping、Discriminator
scss
ResultMap、ResultMapping、Discriminator;
ParameterMap、ParameterMapping
这5个可以从这个方法里面找到入口 (org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement()从这个方法分析)
ResultMap、ResultMapping、Discriminator (这三个主要对应的输出结果)
可以看到这些对象的构建都在MapperBuilderAssistant这个辅助类中:
org.apache.ibatis.builder.MapperBuilderAssistant#addResultMap()
org.apache.ibatis.builder.MapperBuilderAssistant#buildResultMapping()
org.apache.ibatis.builder.MapperBuilderAssistant#buildDiscriminator()
(1) ResultMapping (对应的是resultMap属性,如果是实体,就是实体里面一个一个的属性)
(2) ResultMap (ResultMapping的一个集合,外加一些辅助信息)
(3) Discriminator (鉴别器节点)
3、ParameterMap、ParameterMapping
scss
ParameterMap、ParameterMapping (对应输入结果)
可以看到这些对象的构建都在MapperBuilderAssistant这个辅助类中:
org.apache.ibatis.builder.MapperBuilderAssistant#addParameterMap()
org.apache.ibatis.builder.MapperBuilderAssistant#buildParameterMapping()
(1) ParameterMapping (与ResultMapping同样)
(2) ParameterMap (与ResultMap一样)
4、Environment、CacheBuilder
yaml
mapping包中还有两个重要的类:
Environment:
它对应了配置文件中的environments节点
CacheBuilder:(org.apache.ibatis.cache包)
缓存建造者,它负责完成缓存对象的创建
5、该包中的一些枚举
sql
该包中的一些枚举:
StatementType:statement, prepared, callable (SQL语句种类,指是否为预编译的语句、是否为存储过程等。)
SqlCommandType:unknown, insert, update, delete, select, flush
FetchType:(延迟加载设置) lazy, eager, default
ParameterMode:(in, out, inout) 参数类型,指输入参数、输出参数等
ResultFlag:返回结果中属性的特殊标志,表示是否为 id属性、是否为构造器属性;(id, constructor)
ResultSetType:结果集支持的访问方式
2.4 scripting
该包的作用用于解析SQL语句是的一些动态标签,ege: foreach、where、if...
1、LanguageDriver、XMLLanguageDriver、RawLanguageDriver
scss
LanguageDriver、XMLLanguageDriver、RawLanguageDriver
(1) LanguageDriver:(接口) 脚本语言解释器
在接口上注解的SQL语句,就是由它进行解析的;
ege :
@Select("select * from user where id = #{id}")
User queryUserById(Integer id);
(2) XMLLanguageDriver
LanguageDriver的实现类,完成SqlSource对象的创建;这里创建的SqlSource只有两种类型 DynamicSqlSource或者RawSqlSource
(3) RawLanguageDriver
继承了 XMLLanguageDriver , 在重写的方法中,只是 校验输入的SqlSource是否为RawSqlSource,如果不是抛出异常
2、Ognl表达式的解析所涉及的类
Ognl表达式的解析所涉及的类:
OgnlClassResolver、OgnlMemberAccess、、OgnlCache、ExpressionEvaluator
① OgnlClassResolver:
OGNL在工作时可以使用 MyBatis中的 Resources类来完成类的读取。
② OgnlMemberAccess:
OgnlMemberAccess类就实现了 MemberAccess接口,并基于反射提供了修改对象属性可访问性的功能。
③ OgnlCache:
利用parseExpression方法对表达式进行了预先解析,并且将表达式解析的结果放入 expressionCache 属性中缓存了起来。
这样避免重复的解析,降低效率。
④ ExpressionEvaluator:
该类对上述3个类进行了封装,提供给解析节点使用。
3、各种节点实体类
sql
(1) XMLScriptBuilder:该类的目的是为了处理:SQL节点下的动态标签.
(2) NodeHandler是XMLScriptBuilder里面的内部类;
下面都是 NodeHandler里面的接口,也是XMLScriptBuilder里面的内部类,分别处理对应的标签:
BindHandler bind
TrimHandler trim
WhereHandler where
SetHandler set
ForEachHandler foreach
IfHandler if
OtherwiseHandler otherwise
ChooseHandler choose
(3) DynamicContext
该类作为动态SQL解析的上下文:
一方面,在进行 SQL节点树的解析时,★需要不断保存已经解析完成的 SQL片段;
另一方面,在进行SQL节点树的解析时也需要一些参数和环境信息作为解析的依据。
(4) SqlNode (接口)
子类实现:
ChooseSqlNode
ForEachSqlNode
IfSqlNode
MixedSqlNode
StaticTextSqlNode
TextSqlNode
VarDeclSqlNode
TrimSqlNode
SetSqlNode
WhereSqlNode
这里的所有子类就是 相应的动态节点的解析 如: <if></if> <where></where> 等,它们都是SqlNode的子类
★★★★★
在(1)(2)中的handleNode()方法中对相应节点解析之后,会将节点内容封装到 (4)对象的子类中;(4)中的子类的实现SqlNode接口
的apply()会完成 对当前节点的真正解析,并拼装到SQL语句上。
在SqlSource的子类实现 DynamicSqlSource、RawSqlSource中调用所说的apply()方法,并且拼装的结果放在了DynamicContext
上线文中;为完成后续 BoundSql对象的转化做为中间结果。(这里说的中间结果是DynamicSqlSource或者RawSqlSource)
apply()对其转化为了 BoundSql.
2.5 datasource
读取配置文件中的 数据库连接信息,生成DataSource
scss
DataSourceFactory (数据源工厂接口)
下面的3个子类工厂:
① JndiDataSourceFactory:
JNDI(Java Naming and Directory Interface)是 Java命名和目录接口,它能够为 Java应用程序提供命名和
目录访问的接口,我们可以将其理解为一个命名规范。
从本质上讲,JndiDataSourceFactory 不是在生产数据源,而只是负责查找数据源。
② UnpooledDataSourceFactory:(非池化数据源工厂)
这里的话,就是一次只能创建一个 connection连接;
并且,在该工厂的构造方法中,直接通过 new UnpooledDataSource()创建 数据源。
③ PooledDataSourceFactory:(池化数据源工厂)
首先,这个类里面的所有方法,属性参数都是继承了 UnpooledDataSourceFactory这个工厂中的;
数据源的创建也是 在该工厂的构造方法中,直接通过 new PooledDataSource()创建 的。
三、核心操作包
3.1 jdbc
该包是一个独立的包,可以有 数据库操作语句的执行能力和脚本运行能力。
1、AbstractSQL、SQL
sql
AbstractSQL、SQL
(1) 作用:用于解析 下面这种注解方法的SQL语句
@SelectProvider(type = AnnotationMapperProvider.class, method = "queryUserById")
List<TestMapper> queryById(Integer id);
public class AnnotationMapperProvider {
public String queryUserById() {
return new SQL()
.SELECT("*")
.FROM("test_mapper")
.WHERE("id = #{id}")
.toString();
}
}
(2) AbstractSQL
里面的大写方法,都是用于拼接SQL语句用的;如 SELECT 、 SET 、 UPDATE ...
(不过SQL子类继承了这些方法在,在我们正常使用的过程中,都是用的例子中 new SQL().***("").***("")这种方式)
步骤一:
用户使用类似 .SELECT("*") 、 .FROM("test_mapper") 、 .WHERE("id = #{id}")
这些片段被保存在 AbstractSQL 中SQLStatement内部类的 ArrayList中。
步骤二:
当最后 .toString() 操作时,就触发了SQL片段的拼接工作。在 SQLStatement内部类中按照一定规则拼接成完整的 SQL语句。
例子中的 .***() 顺序与最终的SQL拼接顺序无关,因为 .***()的信息都放在了SQLStatement中相应的list集合中;
当调用 .toString()方法之后,会调用sql().sql(sb),然后根据当前的操作类型,进入相应的方法(相当于一个模板,开始
集在合中找信息进行拼接SQL)
2、SqlRunner、ScriptRunner
sql
SqlRunner、ScriptRunner
(1) SqlRunner
SqlRunner类是 MyBatis提供的可以直接执行 SQL语句的工具类。
SqlRunner类能接受 SQL语句和参数,然后执行数据库操作。
不过,SqlRunner并不能完成对象和 SQL参数的映射、SQL结果和对象的映射等复杂的操作。
(2) ScriptRunner
ScriptRunner是 MyBatis提供的直接执行 SQL脚本的工具类,这使得开发者可以直接将整个脚本文件提交给 MyBatis 执行。
ScriptRunner处理的是 SQL脚本,不涉及变量赋值问题,相比 SqlRunner而言更为简单。
(3) 测试类:com.yogurt.example.jdbc.JdbcSqlRunnerTest
位置:mybatis-learning-demo/src/test/java/com/yogurt/example/jdbc/JdbcSqlRunnerTest.java
该测试类用于测试: SqlRunner , ScriptRunner
3.2 cache
Mybastis一、二级缓存就是这这里实现的。
1、cache包结构与Cache接口
python
cache包结构与Cache接口:
1. Cache:
Cache接口是实现类和装饰器类的共同接口.
2. PerpetualCache: (是Cache的实现类)
里面的id属性:标识:使用映射文件的 namespace值作为缓存的 id
cache属性:map结果,采用键值对的形式来存储数据。
3. 各种装饰器:(Cache的装饰器类)
(1) 同步装饰器:为缓存增加同步功能,如 SynchronizedCache类
(2) 日志装饰器:为缓存增加日志功能,如 LoggingCache类
(3) 清理装饰器:为缓存中的数据增加清理功能,如 FifoCache、LruCache、WeakCache、SoftCache。
(4) 阻塞装饰器:为缓存增加阻塞功能,如 BlockingCache类
(5) 定时清理装饰器:为缓存增加定时刷新功能,如 ScheduledCache
(6) 序列化装饰器:为缓存增加序列化功能,如 SerializedCache
(7) 事务装饰器:用于支持事务操作的装饰器,如 TransactionalCache
4. CacheKey: (缓存键)
想一下数据放在缓存,缓存键的命中率问题;这就需要我们构造缓存键,用于在从缓存中获取数据时,防止碰撞,同时,键要高效比较和高效生成。
缓存键的生成:
如方法:org.apache.ibatis.executor.BaseExecutor#createCacheKey(org.apache.ibatis.mapping.MappedStatement,
java.lang.Object,
org.apache.ibatis.session.RowBounds,
org.apache.ibatis.mapping.BoundSql)
5. TransactionalCacheManager (事务缓存管理器)
一个事务中可能会涉及多个缓存,TransactionalCacheManager 就是用来管理一个事务中的多个缓存的,
其中的 transactionalCaches属性中保存了(多个缓存和对应的经过缓存装饰器装饰后)的缓存。
2、一级、二级缓存
ini
上面已经把 Cache包的作用介绍完了,但是我们发现,这里只是告诉我们 cache的设计,至于cache的创建,一级缓存、二级缓存并没有介绍。
1. 真正创建缓存的地方在:
org.apache.ibatis.mapping.CacheBuilder 类中;
CacheBuilder利用建造者模式构建 Cache对象
2. org.apache.ibatis.executor包中:
一级缓存:BaseExecutor
(1) 一级缓存的作用范围: SESSION与 STATEMENT,分别对应了一次会话和一条语句;
(2) 配置文件在 settings节点中设置一级缓存的作用范围:
<setting name="localCacheScope" value="SESSION"></setting>
(3) 映射文件中可以设置在数据库操作之前清空一、二级缓存:
<select id="queryByID" resultType="User" flushCache="false">
select * from user where id = #{id}
</select>
二级缓存:CachingExecutor
(1) 二级缓存的作用范围是一个命名空间(即一个映射文件),而且可以实现多个命名空间共享一个缓存;
(2) 配置文件在 settings节点中设置:
<setting name="cacheEnabled" value="true"></setting>
(3) 在映射文件中通过 cache标签来开启并配置本命名空间的缓存:
<cache type="PERPETUAL" eviction="FIFO" flushInterval="10000" size="1024"
readOnly="true" blocking="true" />
也可以利用 <cache-ref/>标签引用另外一个映射文件的缓存:
<cache-ref namespace="com.yogurt.example.mapper.TestMapperDao.xml"></cache-ref>
3.3 transaction
markdown
1. 该包内包含两个子包:
jdbc子包中包含基于JDBC进行事务管理的类;
managed子包中包含基于容器进行事务管理的类。
2. TransactionFactory: (所有事务工厂的接口)
用于生产事务实例;
子类实现:
JdbcTransactionFactory
ManagedTransactionFactory 容器管理的
3. Transaction: (所有事务的接口)
子类实现:
JdbcTransaction 由JDBC进行事务管理
ManagedTransaction 交给Spring容器进行管理事务
3.4 cursor
游标:迭代器,对结果集进行处理。
3.5 executor
一、该包中需要的背景知识
scss
一、该包中需要的背景知识
(1). 基于cglib的动态代理
① demo位置:com.yogurt.example.executor.cglibproxy ;
② cglib使用字节码处理框架 ASM来转换字节码并生成被代理类的子类,然后这个子类就可以作为代理类展开工作;
③ cglib是通过给被代理类创建一个子类,从而实现在不改变被代理类的情况下创建代理类的。
因此它也有一定的局限性:无法为 final类创建代理,因为 final类没有子类。
(2). javassist框架的使用
① demo位置:com.yogurt.example.executor.javassist;
② 它也是一个开源的用来创建、修改 Java字节码的类库,能实现类的创建、方法的修改、继承关系的设置等一系列的操作。
③ javassist框架是以"无中生有"的方式创建一个类,并给类设置属性和方法。
(3). 序列化与反序列化
① demo位置: mybatis-learning-demo/src/test/java/com/yogurt/example/executor/serializable
② 两种:
Externalizable接口:
序列化过程:writeReplace(),writeExternal()
反序列化过程: readExternal(),readResolve()
Serializable接口:
序列化过程: writeReplace()
反序列化过程:readResolve()
③ 继承 Serializable 接口实现序列化和反序列化,目标类除了继承Serializable接口外不需要任何其他的操作,
整个序列化和反序列化的过程由 Java内部的机制完成。
而继承 Externalizable接口实现序列化和反序列化则支持 自定义 序列化和反序列化的方法。
(4). ThreadLocal
demo位置:mybatis-learning-demo/src/test/java/com/yogurt/example/executor/threadlocal
(5). 存储过程
demo位置:mybatis-learning-demo/src/test/java/com/yogurt/example/executor/procedure
(6). jdbc源码包中
Statement :
① Statement 接口中定义了一些抽象方法能用来执行静态 SQL 语句并返回结果,
通常返回的结果是一个结果集ResultSet(resultset包中的ResultSetWrapper就是ResultSet的装饰类)。
② 用在设置简单SQL语句时。
PreparedStatement:
① Statement的子接口;
② 预编译语句用这个。
CallableStatement:
① PreparedStatement的子接口;
② 存储过程语句用这个。
二、主键自增功能 (keygen包)
scss
(1) KeyGenerator接口,三个子类Jdbc3KeyGenerator,NoKeyGenerator,SelectKeyGenerator。
(2) 使用方式:
① NoKeyGenerator (不提供任何主键自增功能)
② Jdbc3KeyGenerator (它存在的意义是提供自增主键的回写功能。)
1. 在标签上加上 useGeneratedKeys="true" keyProperty="id"
2. Jdbc3KeyGenerator类的工作是在数据库主键自增结束后,将自增出来的主键读取出来并赋给 Java对象。
这些工作都是在数据插入完成后进行的,即在 processAfter()中进行。而 processBefore()中不需要进行任何操作。
3. Jdbc3KeyGenerator类其实并没有真正地生成自增主键,而只是将数据库自增出的主键值回写到了Java对象中。
因此,面对不支持主键自增功能的数据库时,Jdbc3KeyGenerator类将无能为力.
③ SelectKeyGenerator (用在不支持增值主键的数据库,通过调用特定SQL获取主键值)
1. 在insert的标签内,内置标签:
<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
2.
① 插入前执行:
如果数据库没有设置或者不支持主键自增:
则完整的对象会被完整地插入数据库中。(这个是 SelectKeyGenerator类最常用的使用场景)
如果数据库设置了主键自增:
则刚才特定 SQL语句生成的自增属性值会被数据库自身的自增值覆盖掉。(这时就会产生不一致)
② 插入后执行:
如果数据库不支持主键自增:
则之前被插入数据库中的对象的自增属性是没有被赋值的,而 Java对象的自增属性却被赋值了,这会导致不一致。
如果数据库设置了主键自增:
则数据库自增生成的值和 SQL语句执行产生的值可能不一样;
不过我们一般通过设置特定的 SQL语句来保证两者一致!
③ processBefore和processAfter这两个方法都直接调用了 processGeneratedKeys方法,
所以processGeneratedKeys方法的功能就是执行一段 SQL语句后获取一个值,然后将该值赋给 Java对象的自增属性。
三、懒加载功能 (loader包)
xml
(1) 在配置文件中配置启用懒加载功能:
<settings>
<!-- 启用全局懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 激进懒加载设置false,即懒加载时,每个属性都按需加载-->
<setting name="aggressiveLazyLoading value="false"/>
</settings>
(2) aggressiveLazyLoading 是激进懒加载设置
当aggressiveLazyLoading设置为 true时,对对象任一属性的读或写操作都会触发该对象所有懒加载属性的加载;
当aggressiveLazyLoading设置为 false时,对对象某一懒加载属性的读操作会触发该属性的加载。
(3) 代理工厂:ProxyFactory;两个实现类:CglibProxyFactoryJavassistProxyFactory
重点看两个实现类中的:
① 创建代理对象的方法: createProxy()
② 最终执行被代理的方法: invoke()
(4)结果的懒加载:
① ResultLoader:
它是一个结果加载器类,它负责完成数据的加载工作。
因为懒加载只涉及查询,而不需要支持增、删、改的工作,因此它只有一个查询方法 selectList()来进行数据的查询。
② ResultLoaderMap:
被代理对象可能会有多个属性可以被懒加载,这些尚未完成加载的属性是在ResultLoaderMap 类的实例中存储的。
(5)懒加载功能对序列化和反序列化的支持:
① 序列化:CglibProxyFactory、JavassistProxyFactory都有一个内部类:EnhancedResultObjectProxyImpl实现序列化。
② 反序列化:CglibProxyFactory、JavassistProxyFactory都有一个内部类:EnhancedDeserializationProxyImpl实现反序列化。
③ 不论是序列化,还是反序列化内部类中的createProxy方法,最终都会调动自身所在类中的createProxy()方法,该方法中的一个重要
操作是:校验被代理类中是否含有writeReplace方法。如果被代理类没有该方法,则会让代理类继承WriteReplaceInterface从而获得
一个 writeReplace()方法。
这样就使得 在被代理类中植入了 writeReplace()方法后,在被代理对象被序列化时,则会调用该方法。这里createProxy()方法就
是 懒加载功能 对于被代理类序列化和反序列化功能的支持得到的解决。
四、MyBatis的语句处理功能
bash
statement包 (MyBatis对多语句类型的支持 语句处理功能)
(1)、
①. ${}:使用这种符号的变量将会以字符串的形式直接插到 SQL片段中。
②. #{}:使用这种符号的变量将会以预编译的形式赋值到 SQL片段中。
(2)、MyBatis中支持三种语句类型,不同语句类型支持的变量符号不同。MyBatis中的三种语句类型如下。
①. STATEMENT:这种语句类型中,只会对SQL片段进行简单的字符串拼接。因此,只支持使用"${}"定义变量。
②. PREPARED:这种语句类型中,会先对SQL片段进行字符串拼接,然后对SQL片段进行赋值。
因此,支持使用"${}","#{}"这两种形式定义变量。
③. CALLABLE:这种语句类型用来实现执行过程的调用,会先对SQL片段进行字符串拼接,
然后对SQL片段进行赋值。因此,支持使用"${}","#{}"这两种形式定义变量。
(3)、statement包中的类:
StatementHandler:
①. 语句处理器接口,里面定义了SQL的增删改查处理方法;
②. 两个实现类:
Ⅰ. RoutingStatementHandler:
该类是一个代理类,它能够根据传入的MappedStatement对象的具体类型选中一个具体的被代理对象,
然后将所有实际操作都委托给被代理对象;
可以委托的对象:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler
分别对应:
STATEMENT:SimpleStatementHandler,这种语句类型只会对SQL片段进行简单的字符串拼接,只支持${};
PREPARED:PreparedStatementHandler,这种语句类型会先对SQL片段进行字符串拼接,
然后对SQL片段进行赋值,支持#{},${};
CALLABLE:CallableStatementHandler,这种语句类型用来实现执行过程的调用,会先对SQL片段进行字符串拼接,
然后对SQL片段进行赋值。支持#{},${}
Ⅱ. BaseStatementHandler:
BaseStatementHandler 作为三个实现类的父类,提供了实现类的公共方法;
真正的实现是在其三个子类中:
SimpleStatementHandler类、PreparedStatementHandler类和CallableStatementHandler类是三个真正的
Statement处理器,分别处理Statement对象、PreparedStatement对象和CallableStatement对象。
不过SimpleStatementHandler类、PreparedStatementHandler类和 CallableStatementHandler类经过转发
处理,最终还是依靠java.sql包下的Statement接口及其子接口提供的功能完成具体参数处理操作的。
五、parameter包 (参数处理功能)
scss
该包中只有一个接口:
ParameterHandler接口有一个默认的实现类DefaultParameterHandler,DefaultParameterHandler在 scripting包的 defaults子包中。
在 DefaultParameterHandler中的 setParameters(java.sql.PreparedStatement)方法会为语句设置参数;就是依次取出每个参数的值,然
后根据参数类型调用PreparedStatement中的赋值方法完成赋值。
六、结果处理功能
scss
demo:
在 mybatis-learning-demo/src/test/java/com/yogurt/example/executor/resultset/ResultSetTest.java 中
打断点,可以更好的理解。
(1) 结果处理功能需要完成的作用:
① 处理结果映射中的嵌套映射等逻辑;
② 根据映射关系,生成结果对象;
③ 根据数据库查询记录对结果对象的属性进行赋值;
④ 将结果对象汇总为 List、Map、Cursor等形式。
(2) result包 (该包仅仅是完成了 (1.4) 的功能--->将结果对象汇总为 List、Map、Cursor等形式)
1. 首先session包中的两个接口:
org.apache.ibatis.session.ResultContext 结果、结果集、可以组成多个结果的结果集
org.apache.ibatis.session.ResultHandler 结果处理器
2. 在result包中:
① DefaultResultContext类是 ResultContext的实现类;
作用:存储了一个结果对象,对应着数据库中的一条记录。
② DefaultResultHandler类和DefaultMapResultHandler类是 ResultHandler的实现类。
并且ResultHandler接口下面这几个实现类的作用:
· DefaultResultHandler类 负责将多个 ResultContext聚合为一个 List返回;
· DefaultMapResultHandler类 负责将多个 ResultContext聚合为一个 Map返回;
· DefaultCursor 类中的 ObjectWrapperResultHandler 内部类负责将多个 ResultContext聚合为一个 Cursor返回。
(在 cursor包中(是个游标))
(3) resultset (该包会完成 (1.1)、(1.2)、(1.3) 描述的功能)
1. ResultSetWrapper是结果封装类,ResultSetHandler和 DefaultResultSetHandler分别是结果集处理器的接口和实现类。
2. ResultSetWrapper结果封装类:
ResultSetWrapper类是对 java.sql.ResultSet的进一步封装,这里用到了装饰器模式。
ResultSetWrapper类在 java.sql.ResultSet接口的基础上扩展出了更多的功能,这些
功能包括:获取所有列名的列表、获取所有列的类型的列表、获取某列的 JDBC类型、获取某列对应
的类型处理器等。
2. ResultSetHandler:
① 里面的三个抽象方法分别:
· <E> List<E> handleResultSets(Statement stmt):将 Statement的执行结果处理为 List。
· <E> Cursor<E> handleCursorResultSets(Statement stmt):将Statement的执行结果处理为 Map。
· void handleOutputParameters(CallableStatement cs):处理存储过程的输出结果。
② 实现类是:DefaultResultSetHandler
3. DefaultResultSetHandler:
真正实现了对结果的处理;★可以跟着 demo的测试用例对里面的方法进行断点跟踪比较理解每个方法功能。
七、执行器
scss
(1) 从Executor接口开始:
在该接口中,定义了的抽象方法:可以完成数据的增、删、改、查,以及事务处理等操作。
而事实上,MyBatis的所有数据库操作也确实是通过调用这些方法实现的。
(2) Executor接口的两个实现类:
①. CachingExecutor
实现缓存功能,在cache包中已经详细介绍了。
②. BaseExecutor
BaseExecutor 是一个抽象类,并用到了模板模式。它实现了其子类的一些共有的基础功能,而将与子类直接相关的操作交给子类处理。
可以重点看一下该类里面方法的注释。
BaseExecutor作为一个抽象类,下面的几个子类:
(Ⅰ). SimpleExecutor
一个最为简单的执行器
(Ⅱ). ReuseExecutor
支持Statement对象复用的执行器
(Ⅲ). BatchExecutor
支持批量执行功能的执行器
(Ⅳ). ClosedExecutor
ClosedExecutor类存在的目的就是通过 isClosed()方法返回 true来表明自己是一个关闭的类,
以保证让任何遇到 ClosedExecutor 对象的操作都会重新创建一个新的有实际功能的Executor.
ClosedExecutor:一个仅能表征自身已经关闭的执行器,没有其他实际功能。
八、错误上下文 (ErrorContext)
scss
(1) ErrorContext 类是一个错误上下文,它能够提前将一些背景信息保存下来。这样在真正发生错误时,便能将这些背景信息提供出来,进而给我们的错误排查带来便利。
★(2) ThreadLocal的应用场景就在这里,将自身存储进ThreadLocal,从而进行线程间的隔离;
3.6 session
这个包是真正的面向用户(面向开发者的接口),即对外的接口。当我们编写一个查询语句的时候,用到的就是这边包里面的类。
java
ege:
// 获取配置文件
InputStream input = Resources.getResourceAsStream("SqlSessionConf
// 利用配置文件构建 SqlSessionFactory工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
// 通过sessionFactory工厂创建 SqlSession对象
SqlSession sqlSession = sessionFactory.openSession();
TestMapperDao mapper = sqlSession.getMapper(TestMapperDao.class);
System.out.println(mapper.selectByPrimaryKey(1));
一、SqlSession的生成链
1、从SqlSession开始分析;SqlSession是接口,下面有两个实现类:DefaultSqlSession,SqlSessionManager;
2、SqlSessionFactory是SqlSession的创建工厂,该工厂有两个实现类:DefaultSqlSessionFactory,SqlSessionManager;
3、而SqlSessionFactory的创建又是由 SqlSessionFactoryBuilder建造者建造的!!!
这个 SqlSessionFactoryBuilder类中,我们可以发现它最终创建的工厂是:DefaultSqlSessionFactory
4、这样其实大致逻辑我们就知道了:
利用 SqlSessionFactoryBuilder类建造出 DefaultSqlSessionFactory工厂,而DefaultSqlSessionFactory工厂就是生产出来SqlSession这个产品。
5、而 SqlSessionManager的功能也是类似于SqlSessionFactoryBuilder作为建造者,但是它不仅仅是一个建造者,它这个类,既 implement SqlSessionFactory,又implement SqlSession;
SqlSessionFactory是工厂,SqlSession的工厂的产品;SqlSessionManager 这种既实现工厂接口又实现工厂产品接口的类是很少见的。
这样SqlSessionManager将工厂和产品整合到一起后,提供了下面两点功能:
1、SqlSessionManager总能给出一个产品(从线程 ThreadLocal取出或者新建)并使用该产品完成相关的操作,
外部使用者不需要了解细节,因此省略了调用工厂生产产品的过程。
2、提供了产品复用的功能。工厂生产出的产品可以放入线程 ThreadLocal 保存(需要显式调用 startManagedSession 方法),
从而实现产品的复用。这样既保证了线程安全又提升了效率。
很多场景下,用户使用的是工厂生产出来的产品,而不关心产品是即时生产的还是之前生产后缓存的。
在这种情况下,可以参考 SqlSessionManager的设计,来提供一种更为高效的给出产品的方式
二、Configuration
javascript
配置文件的根节点就是 configuration 节点,因此该节点内保存了所有的配置信息。
configuration节点的信息经过解析后都存入了 Configuration对象中,因此Configuration对象中就包含了 MyBatis运行的所有配置信息。
主要内容分为以下几个部分:
1、大量的配置项,和与 <configuration> 标签中的配置对应;
2、创建类型别名注册机,并向内注册了大量的类型别名;
3、创建了大量Map,包括存储映射语句的Map,存储缓存的Map等,这些Map使用的是一种不允许覆盖的严格Map;
4、给出了大量的处理器的创建方法,包括参数处理器、语句处理器、结果处理器、执行器。
注意这里并没有真正创建,只是给出了方法。
三、分页 RowBounds
scss
RowBounds类用来表示查询结果分页设置,即表明查询结果的起始位置和条数限制。
如何使用,我们可以去看 在DefaultResultSetHandler 类的skipRows()方法中如何发挥作用,分页原理直接列在这里了:
这种分页是通过内存分页实现的,也就是说 MyBatis会向数据库查出所有的数据,然后在内存中略过一些数据后再开始读取。
虽然最终返回的是部分数据,但是向数据库请求的却是全部数据,因此这并不是一种高效的分页方式。
四、一些枚举类
AutoMappingBehavior:
表示当启动自动映射时要如何对属性进行映射。可选项有不映射、仅单层映射和全部映射。
AutoMappingUnknownColumnBehavior:
表示自动映射中遇到一些未知的字段该如何处理。可选项有不处理、输出报警日志和抛出异常。
ExecutorType:
这三个执行器的选择是在 MyBatis的配置文件中进行的,
这三个枚举对应了 org.apache.ibatis.executor 包中三个执行器的选择。
这三个执行器主要基于StatementHandler完成创建 Statement对象、绑定参数等工作。
TransactionIsolationLevel:
表示事务隔离级别。可选项有无隔离、读已提交、读未提交、可重复读和串行化。
五、ResultContext、ResultHandler
ResultContext:
表示结果上下文,其中存放了数据库操作的一个结果(对应数据库中的一条记录)。
ResultHandler:
表示结果处理器,数据库操作结果会由它处理。因此说,ResultHandler会负责处理 ResultContext。
这两个接口的具体实现是在 executor.result包中!!!
3.7 plugin
一、预备知识
csharp
①、 在 org.apache.ibatis.plugin.base.chainresponsibilitypatthern包中学习了 责任链模式;
应用场景是在 plugin包中, InterceptorChain类中,对于多个拦截器形成一个拦截器链。
②、 在 org.apache.ibatis.plugin.base.interceptor包中学习了 Mybatis的自定义拦截器。
应用场景,首先是自定义Mybatis的拦截器,基于注解 @Intercepts、@Signature 注解实现。
二、MyBatis插件
scss
①、Interceptor接口,自定拦截器需要实现该类,里面有三个方法;注解 @Intercepts、@Signature;
可以通过(1.1)中的demo对拦截器功能进行了解。
②、InterceptorChain:
拦截器链,里面是用一个list存放所有的拦截器的。
③、Plugin:
该类其实是 目标对象(被拦截的类)的代理类;当被代理对象中方法被触发时会进入该Plugin类中的invoke方法。
通过分析该类的属性和方法,Plugin类完成了类层级和方法层级这两个层级的过滤工作:
1、 如果目标对象所属的类被拦截器声明拦截,则 Plugin用自身实例作为代理对象替换目标对象。
2、 如果目标对象被调用的方法被拦截器声明拦截,则Plugin将该方法交给拦截器处理。否则 Plugin将该方法交给目标对象处理。
正因为 Plugin类完成了大量的工作,拦截器自身所需要做的工作就非常简单了,主要分为两项:使用 Intercepts 注解和 Signature
注解声明自身要拦截的类型与方法;通过intercept方法处理被拦截的方法。