依赖结构图
mybatis-spring-boot-starter
- spring-boot-starter
- spring-boot-starter-jdbc
- spring-boot-starter
- HikariCP
- slf4j
- spring-jdbc
- sping-beans
- spring-core
- spring-tx
- spring-beans
- spring-core
- mybatis-spring-boot-autoconfigure
- spring-boot-autoconfigure
- mybatis
- mybatis-spring
各模块概要说明
- 接口层:提供给外部使用的接口 API,接口层接收到调用请求后调用数据处理层完成具体的数据处理,这一层在IBatis时还会被代码手动调用,但是到Mybatis以后直接通过接口和动态代理实现了自动调用。
- 核心处理层:负责具体的实现逻辑,解析配置,Mapper接口和xml绑定, SQL解析、执行和结果映射处理等。
- 基础支撑层:不包含实际框架业务逻辑的功能模块,为上层的数据处理层提供最基础的支撑,包括xml数据解析、数据源控制、事务控制、缓存处理、反射功能,类型转换、日志控制、资源加载等功能模块。
接口层
SqlSession
- 该接口包含selectOne,selectMap,selectCursor,selectList,select,insert,update,delete等数据库CRUD相关方法以及事务处理(commit,rollback)以及Mybatis自身相关方法(getConfiguration,getMapper,getConnection)MyBatis常用API等方法。
- MyBatis提供了两个SqlSession接口的实现,其中最常用的是DefaultSqlSession实现
SqlSessionFactory
负责创建SqlSession对象,其中只包含多个openSession方法的重载,可以通过其参数指定事务的隔离级别、底层使用Executor的类型以及是否自动提交事务等方面的配置
DefaultSqlSessionFactory
是一个具体工厂类,实现了SqlSessionFactory接口
SqlSessionManager
同时实现了SqlSession 接口和SqlSessionFactory接口,同时提供了SqlSessionFactory 创建 SqlSession对象以及SqlSession操纵数据库的功能
SqlSessionManager与DefaultSqlSessionFactory的主要不同点
SqlSessionManager提供了两种模式
- 第一种模式: 与DefaultSqlSesionFactory的行为相同,同一线程每次通过SqlSessionManager对象访问数据库时,都会创建新的DefaultSession对象完成数据库操作;
- 第二种模式: 是SqlSessionManager 通过localSqlSession这个ThreadLocal变量,记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一线程多次创建SqlSession对象带来的性能损失
基础支持层
反射模块
对 Java 原生反射进行了良好封装,添加了缓存优化,提供了更加简洁易用的 API
Reflector
Reflector是MyBatis中反射模块的基础,每个Reflector对象都会对应一个类,在Reflector中缓存了反射操作需要使用的类的元信息。Reflector中各个字段的含义如下
private final Class<?> type;//对应的Class类型
private final String[] readablePropertyNames;//可读(有getter方法的)属性的名称集合
private final String[] writeablePropertyNames;//可写(有setter方法的)属性的名称集合
//记录属性相应的 setter方法,key是属性名称,value是GetFieldInvoker对象
private final Map<String, Invoker> setMethods = new HashMap<>();
//属性相应的getter方法集合,key是属性名称,value也是SetFieldInvoker对象
private final Map<String, Invoker> getMethods = new HashMap<>();
//记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
//记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
private final Map<String, Class<?>> getTypes = new HashMap<>();
private Constructor<?>defaultConstructor;//记录了默认构造方法
//记录了所有属性名称的集合
private Map<String,String>caseInsensitivePropertyMap=new HashMap<String,String>();
ReflectorFactory
在Reflector的构造方法中会解析指定的Class对象,并填充上述集合
ReflectorFactory接口主要实现了对Reflector对象的创建和缓存,默认实现DefaultReflectorFactory
TypeParameterResolver
TypeParameterResolver 通过 resolveFieldType、resolveReturnType、resolveParamTypes方法分别解析字段类型、方法返回值类型和方法参数列表中各个参数的类型(Class,ParameterizedType,Type Variable,GenericArray Type,WildcardType)
ObjectFactory
DefaultObjectFactory是MyBatis提供的ObjectFactory接口的唯一实现,它是一个反射工厂,其create方法通过调用instantiateClass方法实现。
DefaultObjectFactory.instantiateClass方法会根据传入的参数列表选择合适的构造函数实例化对象
Property 属性工具类
PropertyTokenizer、PropertyNamer 和PropertyCopier “orders[O].items[0].name”这种由“.”和“[]”组成的表达式是由PropertyTokenizer进行解析
PropertyNamer提供了帮助完成方法名到属性名的转换的静态方法,以及多种检测操作(methodToProperty,isProperty,isGetter,isSetter)
PropertyCopier是一个属性拷贝的工具类,其核心方法是copyBeanProperties0方法,主要实现相同类型的两个对象之间的属性值拷贝
MetaClass
MetaClass 通过Reflector和Property Tokenizer组合使用,实现对复杂的属性表达式的解析,并能获取指定属性描述信息
MetaClass的构造函数中会为指定的Class创建相应的Reflector对象
MetaClass中的findProperty方法,通过调用MetaClas.buildProperty方法通过PropertyTokenizer解析复杂的属性表达式
MetaClass.hasGeter和hasSetter方法负责判断属性表达式所表示的属性是否有对应的属性
ObjectWrapper
ObjectWrapperFactory负责创建ObjectWrapper对象
ObjectWrapper接口是对对象的包装,抽象了对象的属性信息(MetaObject的构造方法会根据传入的原始对象的类型以及ObjectFactory工厂的实现,创建相应的ObjectWrapper对象),它定义了一系列查询对象属性信息的方法,以及更新属性的方法
类型转化模块
实现别名机制(为简化配置文件),实现 JDBC 类型与 Java 类型间的转换
MyBatis中所有的类型转换器都继承TypeHandler接口,在TypeHandler 接口中定义了setParameter方法负责将数据由JdbcTlype 类型转换成Java类型;getResult方法及其重载负责将数据由Java类型转换成JdbcType类型
TypeHandlerRegistry
在MyBatis初始化过程中会为所有已知的TypeHandler创建对象,并实现并通过TypeHandlerRegistry.register方法注册到TypeHandlerRegistry中,由TypelHandlerRegistry负责管理这些TypeHandler对象
TypeAliasRegistry
MyBatis 通过TypeAliasRegistry类完成别名注册和管理的功能
TypeAliasRegistry.registerAlias方法完成注册别名
日志模块
确定日志接口,通过适配器模式集成如Log4j、Log4j2、slf4j等第三方日志框架
MyBatis为了集成和复用这些第三方日志组件,在其日志模块中提供了多种Adapter,将这些第三方日志组件对外的接口适配成了org.apache.ibatis.logging.Log 接口,这样MyBatis内部就可以统一通过org.apache.ibatis.logging.Log接口调用第三方日志组件的功能
- MyBatis统一提供了trace、debug、warn、error四个日志级别
- LogFactory类通过配置生成相应的日志对象
BaseJdbcLogger通过JDK动态代理的方式,将JDBC操作通过指定的日志框架打印出来
资源加载模块
对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能
ClassLoaderWrapper
在IO包中提供的ClassLoaderWrapper是一个ClassLoader的包装器,包含了多个ClassLoader对象,通过调整多个类加载器的使用顺序,ClassLoaderWrapper 可以确保返回给系统使用的是正确的类加载器
- ClassLoaderWrapper会按照指定的顺序依次检测其中封装的ClassLoader对象,并从中选取第一个可用的ClassLoader完成相关功能
- ClassLoaderWrapper 的主要功能可以分为三类:
分别是getResourceAsURL方法、classForName方法、getResourceAsStream方法,这三类方法最终都会调用参数为String和ClassLoader[]的重载
ResolverUtil
根据指定的条件查找指定包下的类,主要有ResolverUtil.find方法
VFS
VFS表示虚拟文件系统(Vitual File System),它用来查找指定路径下的资源。VFS是一个抽象类,MyBatis中提供了JBoss6VFS和DefaultVFS两个VFS的实现
- VFS.getlnstance方法会创建VFS对象,并初始化instance字段
- VFS.isValid方法负责检测当前VFS对象在当前环境下是否有效,VFS.list(URL,String)方法负责查找指定的资源名称列表
解析器模块
封装XPath提供解析xml功能;处理动态SQL语句中的占位符
XPathParser
包含evalString,evalBoolean,evalShort,evalInteger,evalLong,evalFloat,evalDouble,evalNodes,evalNode,evaluate,createDocument等方法
XPathParser中各个字段的含义和功能
private Document document;//Document对象通过createDocument方法得到
private boolean validation;//是否开启验证
private EntityResolver entityResolver;//用于加载本地DTD文件,具体实现为XMLMapperEntityResolver类
private Properties variables;//mybatis-config.xml 中<propteries>标签定义的键值对集合
private XPath xpath;//XPath对象
解析配置时,XPathParser.evalString方法,其中会调用PropertyParser.parse方法处理节点中相应的默认值(冒号后面的默认配置),底层实际是通过GenericTokenParser.parse方法获取的(通用的字占位符解析器即${}),更底层是TokenHandler接口的实现类Variable TokenHandler
通过XPathParser.evalNode方法返回XNode类型对象数据,可以通过parseAttributes方法和parseBody方法解析节点的属性和节点值
数据源模块
提供数据源实现,并提供与第三方数据源集成的接口
- MyBatis 提供了两个javax.sql.DataSource接口实现,分别是PooledDataSource和UnpooledDataSource。
- Mybatis 使用不同的DataSourceFactory接口实现创建不同类型的DataSource
UnpooledDataSource
- UnpooledDataSource.getConnection方法获取数据库连接时都会创建一个新连接(通过UnpooledDataSource.doGetConnection方法获取数据库连接)
- UnpooledDataSource.initializeDriver方法主要负责数据库驱动的初始化
- UnpooledDataSource.configureConnection方法主要完成数据库连接的一系列配置
PooledDataSource
实现了简易数据库连接池的功能,PooledDataSource创建新数据库连接的功能是依赖其中封装的UnpooledDataSource对象实现
PooledDataSource.getConnection方法首先会调用PooledDataSource.popConnection方法获取PooledConnection对象,然后通过PooledConnection.getProxyConnection方法获取数据库连接的代理对象
当调用连接的代理对象的close方法时,并未关闭真正的数据连接,而是调用PooledDataSource.pushConnection方法将PooledConnection 对象归还给连接池,供之后重用
PooledDataSource.pushConnection方法和popConnection方法中都调用了PooledConnection.isValid方法来检测PooledConnection的有效性
事务管理
事务接口和简单实现,于Spring结合使用时通过Spring管理事务
- Transaction 接口有JdbcTransaction、ManagedTransaction两个实现,其对象分别由Jdbc TransactionFactory 和Managed TransactionFactory负责创建。
Jdbc Transaction
依赖于JDBC Connection控制事务的提交和回滚
protected Connection connection;//事务对应的数据库连接
protected DataSource dataSource;//数据库连接所属的DataSource
protected TransactionIsolationLevel level;//事务隔离级别
protected boolean autocommmit;//是否自动提交
在JdbcTransaction的构造函数中会初始化除connection字段之外的其他三个字段,而connection 字段会延迟初始化,它会在调用getConnection方法时通过 dataSource.getConnection方法初始化,并且同时设置autoCommit 和事务隔离级别。JdbcTransaction的commit方法和rollback方法都会调用
Connection对应方法实现
ManagedTransaction
ManagedTransaction的实现更加简单,它同样依赖其中的dataSource字段获取连接,但其commit、rollback方法都是空实现,事务的提交和回滚都是依靠容器管理的。
ManagedTransaction 中通过closeConnection字段的值控制数据库连接的关闭行为。
缓存模块
Mybatis两级缓存依赖于基础支持层中的缓存模块实现
Cache 接口 Cache 接口的实现类有多个,但大部分都是装饰器,只有PerpetualCache提供了Cache接口的基本实现
public interface Cache{
String getId();//该缓存对象的id
//向缓存中添加数据,一般情况下,key是Cachekey,value是查询结果
void putObject(Object key,Object value);
object getobject(object key);//根据指定的key,在缓存中查找对应的结果对象object removeobject(Object key);//删除key对应的缓存项
void clear();//清空缓存
int getsize();//缓存项的个数,该方法不会被MyBatis核心代码使用,所以可提供空实现
//获取读写锁,该方法不会被MyBatis核心代码使用,所以可提供空实现
ReadWriteLock getReadwriteLock();
}
PerpetualCache
实现比较简单,底层使用HashMap 记录缓存项LruCache通过LinkedHashMap实现,new LinkedHashMap
<Object, Object>
(size,.75F, true)
CacheKey
private int multiplier;//参与计算hashcode,默认值是37
private int hashcode;//CacheKey对象的hashcode,初始值是17
private long checksum;//校验和
private List<Object> updateList;//由该集合中的所有对象共同决定两个CacheKey是否相同
private int count;//updateList集合的个数
下面四个部分构成的CacheKey对象,也就是说这四部分都会记录到该CacheKey对象的updateList集合中
- MappedStatement的id
- 指定查询结果集的范围,也就是RowBounds.offset和RowBounds.limit
- 查询所使用的SQL语句,也就是boundSql.getSql0方法返回的SQL语句,其中可能包含“?”占位符
- 用户传递给上述SQL语句的实际参数值
Bing模块
实现自定义的Mapper接口与映射配置文件关联的逻辑
MapperRegistry
是Mapper接口及其对应的代理对象工厂的注册中心
Configuration.mapperRegistry字段记录当前使用的MapperRegistry对象
在MyBatis初始化过程中会读取映射配置文件以及Mapper接口中的注解信息,并调用MapperRegistry.addMapper方法填充MapperRegistry.knownMappers集合,该集合的key是Mapper 接口对应的Class对象,value为MapperProxyFactory工厂对象,可以为Mapper接口创建代理对象
MapperRegistry中字段的含义和功能
//Configuration对象,MyBatis全局唯一的配置对象,其中包含了所有配置信息
private final Configuration config;
//记录了Mapper接口与对应MapperProxyFactory之间的关系private final Map<Class<?>,MapperProxyFactory<?>>knownMappers=
new HashMap<Class<?>,MapperProxyFactory<?>>();
在需要执行某SQL语句时,会先调用MapperRegistry.getMapper方法获取实现了Mapper接口的代理对象,例如session.getMapper(BlogMapper.class)方法得到的实际上是MyBatis通过JDK动态代理为BlogMapper接口生成的代理对象。
MapperProxyFactory
主要负责创建代理对象
- MapperProxyFactory.newInstance方法实现了mapperInterface接口的代理对象的功能
- MapperProxy.invoke方法是代理对象执行的主要逻辑
MapperMethod
MapperMethod中封装了Mapper接口中对应方法的信息,以及对应SQL语句的信息
MapperMethod 中各个字段的信息
private final SqlCommand command;//记录了SQL语句的名称和类型
private final MethodSignature method;//Mapper接口中对应方法的相关信息
SqlCommand
SqlCommand是MapperMethod中定义的内部类,它使用name字段记录了SQL语句的名称,使用type字段(SqlCommandType类型)记录了SQL语句的类型。SqlCommandType是枚举类型,有效取值为UNKNOWN、INSERT、UPDATE、DELETE、SELECT、FLUSH。
ParamNameResolver
- 在MethodSignature中,会使用ParamNameResolver处理Mapper接口中定义的方法的参数列表。ParamNameResolver 使用name 字段(SortedMap
<Integer,String>
类型)记录了参数在参数列表中的位置索引与参数名称之间的对应关系,其中key表示参数在参数列表中的索引位置,value 表示参数名称,参数名称可以通过@Param注解指定,如果没有指定@Param注解,则使用参数索引作为其名称。如果参数列表中包含RowBounds类型或ResultHandler类型的参数,则这两种类型的参数并不会被记录到name集合中 - ParamNameResolver的hasParamAnnotation 字段(boolean类型)记录对应方法的参数列表中是否使用了@Param 注解。
MethodSignature
MethodSignature也是MapperMethod中定义的内部类,其中封装了Mapper接口中定义的方法的相关信息
MapperMethod方法
MapperMethod中最核心的方法是execute方法,它会根据SQL语句的类型调用SqlSession对应的方法完成数据库操作
MapperMethod.executeWithResultHandler
MapperMethod.executeForMany
MapperMethod.executeForMap
MapperMethod.executeForCursor
核心处理层
在核心处理层中实现了包括MyBatis的初始化以及完成一次数据库操作的涉及的全部核心处理流程。
配置解析
- 初始化过程,加载 Mybatis-config.xml 配置文件、映射配置文件、Mapper 接口中的注解信息,解析后会形成相应的对象保存到 Configuration 对象中(定义的
<resultMap>
节点(即ResultSet的映射规则)会被解析成ResultMap对象;示例中定义的<result>
节点(即属性映射)会被解析成ResultMapping对象。之后,利用该Configuration对象创建 SqlSessionFactory对象。- 待MyBatis初始化之后,开发人员可以通过初始化得到SqlSessionFactory创建SqlSession 对象并完成数据库操作)
BaseBuilder
//configuration是MyBatis初始化过程的核心对象,MyBatis中几乎全部的配置信息会保存到
//configuration 对象中。Configuration对象是在MyBatis初始化过程中创建且是全局唯一的,
//也有人称它是一个“A11-In-one”配置对象
protected final Configuration configuration;
//在mybatis-config.xm1配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在该
//TypeAliasRegistry对象中,在第2章中已经介绍过其原理,不再重复描述protected final TypeAliasRegistry typeAliasRegistry;
//在mybatis-config.xm1配置文件中可以使用<typeHandlers>标签添加自定义TypeHandler器,完
//成指定数据库类型与Java类型的转换,这些TypelHandler都会记录在TypeHandlerRegistry中
protected final TypeHandlerRegistry typeHandlerRegistry;
SqlSessionFactoryBuilder.build方法会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件,而XMLConfigBuilder 继承自BaseBuilder抽象类
XMLConfigBuilder
主要负责解析mybatis-config.xml配置文件
XMLConfigBuilderpropertiesElement
方法会解析 mybatis-config.xml配置文件中的<properties>
节点并形成java.util.Properties对象,之后将该Properties对象设置到XPathParser和Configuration的variables字段中XMLConfig Builder.settingsAsProperties
方法负责解析<settings>
节点,在<settings>
节点下的配置是MyBatis全局性的配置XMLConfig Builder.typeAliasesElement
方法负责解析<typeAliases>
节点及其子节点,并通过TypeAliasRegistry 完成别名的注册XMLConfigBuilder.typeHandlerElement
方法负责解析<typeHandlers>
节点,并通过TypeHandlerRegistry 对象完成TypeHandler的注册XMLConfigBuilder.pluginElement
方法负责解析<plugins>
节点中定义的插件,并完成实例化和配置操作XMLConfigBuilder.objectFactoryElement
方法负责解析并实例化<objectFactory>
节点指定的ObjectFactory实现类,之后将自定义的ObjectFactory 对象记录到Configuration.objectFactory字段中XMLConfigBuilder.environmentsElement
方法负责解析<environments>
的相关配置,它会根据XMLConfigBuilder.environment 字段值确定要使用的<environmen>
配置,之后创建对应的TransactionFactory和DataSource对象,并封装进Environment对象中XMLConfigBuilder.databaseldProviderElement
方法负责解析<databaseldProvider>
节点,并创建指定的DatabaseldProvider对象。DatabaseldProvider会返回 databaseld值,MyBatis会根据databaseld选择合适的SQL进行执行XMLConfigBuilder.mapperElement
方法负责解析<mappers>
节点,它会创建XMLMapperBuilder对象加载映射文件,如果映射配置文件存在相应的Mapper接口,也会加载相应的Mapper 接口,解析其中的注解并完成向MapperRegistry的注册
XMLMapperBuilder
负责解析映射配置文件
XMLMapperBuilder.cacheElement
方法主要负责解析<cache>
节点,通过MapperBuilderAssistant.useNewCache方法创建Cache对象,并添加到Configuration.caches集合中保存
CacheBuilder
CacheBuilder是Cache的建造者,CacheBuilder.setCacheProperties方法会根据<cache>
节点下配置的<property>
信息,初始化Cache对象
CacheBuilder.setStandardDecorators方法会根据CacheBuilder中各个字段的值,为cache对象添加对应的装饰器
XMLMapperBuilder.cacheRefElement
方法负责解析<cache-ref>
节点,CacheRefResolver.resolveCacheRef方法会调用MapperBuilderAssistant.useCacheRef方法,在MapperBuilderAssistant.useCacheRef方法中会通过namespace查找被引用的Cache对象
ResultMap
每个<resultMap>
节点都会被解析成一个ResultMap对象,其中每个节点所定义的映射关系,则使用ResultMapping对象表示
- 在XMLMapperBuilder中通过resultMapElements0方法解析映射配置文件中的全部
<resultMap>
节点,该方法会循环调用resultMapElement方法处理每个<resultMap>
节点 - XMLMapperBuilder.processConstructorElement方法解析
<constructor>
节点 <association>
节点在XMLMapperBuilder.buildResultMappingFromContext方法中完成解析<discriminator>
节点XMLMapperBuilder.processDiscriminatorElement方法完成解析XMLMapperBuilder.sqlElement
方法负责解析映射配置文件中定义的全部<sql>
节点
得到ResultMapping对象集合之后,会调用ResultMapResolver.resolve0方法,该方法会调用MapperBuilderAssistant.addResultMap方法创建ResultMap对象,并将ResultMap对象添加到Configuration.resultMaps集合中保存
XMLMapperBuilder.bindMapperForNamespace,完成了映射配置文件与对应Mapper接口的绑定
解释失败的会根据抛出异常的节点不同,MyBatis会创建不同的Resolver对象,并添加到Configuration的不同incomplete集合中
Configuration.incompleteMethods,incompleteResultMaps,incompleteCacheRefs等通过configurationElement)方法完了一次映射配置文件的解析后,还会调用parsePendingResultMaps0方法、parsePendingChacheRefs方法、parsePendingStatements方法三个parsePending方法处理Configuration中对应的三个incomplete集合
SQL 解析
MyBatis实现动态SQL语句的功能,提供了多种动态SQL语句对应的节点,例如,
<where>
节点、<if>
节点、<foreach>
节点等。通过这些节点的组合使用,开发人员可以写出几乎满足所有需求的动态SQL语句。
- XMLStatementBuilder负责解析SQL节点具体内容
- SqlSource接口表示映射文件或注解中定义的SQL语句
- MappedStatement表示映射配置文件中定义的SQL节点
- 通映射配置文件中定义的SQL节点会被解析成MappedStatement对象,其中的SQL语句会被解析成SqlSource对象,SQL语句中定义的动态SQL节点、文本节点等,则由SqlNode接口的相应实现表示
- 在解析SQL节点之前,首先通过XMLInclude Transformer解析SQL语句中的
<include>
节点,该过程会将<include>
节点替换成<sql>
节点中定义的SQL片段,并将其中的“${xxx}”占位符替换成真实的参数,该解析过程是在XMLInclude Transformer.applyIncludes0方法中实现的: XMLStatementBuilder.processSelectKeyNodes
方法负责解析SQL节点中的<selectKey>
子节点- 在
XMLLanguageDriver.createSqlSource
方法中会创建XMLScriptBuilder 对象并调用XMLScriptBuilder.parseScriptNode
方法创建SqlSource对象
NodeHandler
NodeHandler 接口实现类会对不同的动态SQL标签进行解析,生成对应的SqlNode对象
SqlNode
接口有多个实现类,每个实现类对应一个动态SQL节点
SqlSourceBuilder
- 在经过SqlNode.apply方法的解析之后,SQL语句会被传递到SqlSourceBuilder中进行进一步的解析
- SqlSourceBuilder主要完成了两方面的操作:
- 一方面是解析SQL语句中的“#{}”占位符中定义的属性,格式类似于{#item,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
- 另一方面是将SQL语句中的“#{}”占位符替换成“?”占位符。
- SqlSourceBuilder会将SQL语句以及parameterMappings集合封装成StaticSqlSource对象。StaticSqlSource.getBoundSql创建并返回了BoundSql 对象,该BoundSql对象也就是DynamicSqlSource返回的BoundSql对象。
DynamicSqlSource
- 无论是StaticSqlSource、DynamicSqlSource还是RawSqlSource,最终都会统一生成BoundSql对象,其中封装了完整的SQL语句(可能包含“?”占位符)、参数映射关系(parameterMappings集合)以及用户传入的参数(additionalParameters集合)
- DynamicSqlSource负责处理动态SQL语句,RawSqlSource负责处理静态SQL语句,两者解析SQL语句的时机也不一样,前者的解析时机是在实际执行SQL语句之前,而后者则是在MyBatis初始化时完成SQL语句的解析。
参数映射
ParameterMapping
- TokenHandler.handleToken方法的实现会调用buildParameterMapping方法解析参数属性,并将解析得到的ParameterMapping对象添加到parameterMappings集合中
- 在ParameterHandler接口中只定义了一个setParameters方法,该方法主要负责调用PreparedStatement.set*()方法为SQL 语句绑定实参
- 在DefaultParameterHandler.setParameters方法中会遍历BoundSql.parameterMappings集合中记录的ParameterMapping对象,并根据其中记录的参数名称查找相应实参,然后与SQL语句绑定
SQL执行
SQL语句的执行涉及多个组件,包括Executor、StatementHandler、ParameterHandler和ResultSetHandler
- Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler 完成。
- StatementHandler 首先通过ParameterHandler 完成SQL语句的实参绑定,然后通过java.sql.Statement对象执行SQL语句并得到结果集,最后通过ResultSetHandler 完成结果集的映射,得到结果对象并返回。
KeyGenerator
默认情况下,insert语句并不会返回自动生成的主键,而是返回插入记录的条数。如果业务逻辑需要获取插入记录时产生的自增主键,则可以使用Mybatis 提供的KeyGenerator接口
public interface KeyGenerator{
//在执行insert之前执行,设置属性order="BEFORE"
void processBefore(Executor executor,MappedStatement ms,Statement stmt,Object parameter);
//在执行insert之后执行,设置属性order="AFTER"
void processAfter(Executor executor,MappedStatement ms,Statement stmt,object parameter);
}
MyBatis 提供了三个KeyGenerator接口的实现
Jdbc3KeyGenerator
Jdbc3KeyGenerator.processBefore方法是空实现,只实现了processAfter方法,该方法会调用Jdbc3KeyGenerator.processBatch方法将SQL语句执行后生成的主键记录到用户传递的实参中
SelectkeyGenerator
SelectkeyGenerator 中的processBefore方法和processAfter方法的实现都是调用processGeneratedKeys方法processGeneratedKeys方法会执行
<selectKey>
节点中配置的SQL语句,获取insert语句中用到的主键并映射成对象,然后按照配置,将主键对象中对应的属性设置到用户参数中
StatementHandler
- StatementHandler接口Executor 接口实现的基础
- StatementHandler 接口中的功能很多,例如创建 Statement对象,为SQL语句绑定实参,执行select、insert、update、delete等多种类型的SQL语句,批量执行SQL语句,将结果集映射成结果对象
public interface StatementHandler{
//从连接中获取一个Statement
Statement prepare(Connection connection,Integer transactionTimeout)throws SQLException;
//绑定 statement执行时所需的实参
void parameterize(Statement statement).throws SQLException;
//批量执行SQL语句
void batch(Statement statement)throws SQLException;
//执行update/insert/delete语句
int update(Statement statement)throws SQLException;
//执行select语句
<E>List<E>query(Statement statement,ResultHandler resultHandler)throws SQLException;
<E>Cursor<E>queryCursor(Statement statement)throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();//获取其中封装的ParameterHandler
RoutingStatementHandler
RoutingStatementHandler 会根据MappedStatement中指定的statementlType字段,创建对应的StatementHandler 接口实现。BaseStatementHandler 是一个实现了StatementHandler接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法
SimpleStatementHandler
SimpleStatementHandler继承了BaseStatementHandler抽象类,它底层使用java.sql.Statement对象来完成数据库的相关操作,所以SQL语句中不能存在占位符,相应的,SimpleStatementHandler.parameterize方法是空实现
- SimpleStatementHandler.instantiateStatement方法直接通过JDBC Connection创建 Statement对象
- SimpleStatementHandler 中的query,queryCursor,batch方法,都是直接调用Statement对象的相应方法
- SimpleStatementHandler.update方法负责执行insert、update或delete等类型的SQL语句,并且会根据配置的KeyGenerator获取数据库生成的主键
PreparedStatementHandler
PreparedStatementHandler 底层依赖于java.sql.PreparedStatement对象来完成数据库的相关操作,在SimpleStatementHandler.parameterize方法中,会调用ParameterHandler.setParameters方法完成SQL语句的参数绑定
- PreparedStatementHandler.instantiateStatement方法直接调用JDBCConnection的- prepareStatement方法创建PreparedStatement对象
CallableStatementHandler
CallableStatementHandler 底层依赖于java.sql.CallableStatement 调用指定的存储过程,其parameterize方法会调用ParameterHandler.setParameters方法完成SQL语句的参数绑定,并指定输出参数的索引位置和JDBC类型
Executor
Executor中定义了数据库操作的基本方法,在实际应用中经常涉及的SqlSession接口的功能,都是基于Executor接口实现的。
主要负责维护一、二级缓存,并提供事务管理相关操作,它会将数据库相关操作委托给 StatementHandler 完成StatementHandler 通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过 java.sql.Statement 执行 SQL 并得到结果集,最后通过 ResultSetHandler 完成结果集映射交返回对
Executor接口中定义的方法如下
public interface Executor{
//执行update、insert、delete三种类型的sQL语句
int update(MappedStatement ms,Object parameter)throws SQLException;
//执行select类型的SQL语句,返回值分为结果对象列表或游标对象
<E>List<E>query(MappedStatement ms,Object parameter,RowBounds rowBounds,ResultHandler resultHandler,CacheKey cacheKey,BoundSql boundSql)throws SQLException;
<E>List<E>query(Mappedstatement ms,Object parameter,RowBounds rowBounds,ResultHandler resultHandler)throws SQLException;
<E>Cursor<E>queryCursor(MappedStatement ms,Object parameter,RowBounds rowBounds)throws SQLException;List<BatchResult>flushStatements()throws SQLException;//批量执行SQL 语句
void commit(boolean required)throws SQLException;//提交事务
void rollback(boolean required)throws SQLException;//回滚事务
//创建缓存中用到的CacheKey对象
Cachekey createCacheKey(MappedStatement ms,object parameterobject,RowBounds rowBounds,BoundSql boundSql);
boolean isCached(MappedStatement ms,Cachekey key);//根据CacheKey对象查找缓存
void clearLocalCache();//清空一级缓存
//延迟加载一级缓存中的数据,DeferredLoad的相关内容后面会详细介绍
void deferLoad(MappedStatement ms,Metaobject resultobject,String property,CacheKey key,Class<?> targetType);
Transaction getTransaction();//获取事务对象
void close(boolean forceRollback);//关闭Executor 对象
boolean isClosed();//检测Executor是否已关闭
}
BaseExecutor
BaseExecutor是一个实现了Executor接口的抽象类,它实现了Executor接口的大部分方法,其中就使用了模板方法模式BaseExecutor中主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别是:doUpdate、doQuery、doQueryCursor、doFlushStatement方法,其余的功能在BaseExecutor中实现(一级缓存等固定不变的操作都封装在BaseExecutor中)
SimpleExecutor
SimpleExecutor继承了BaseExecutor抽象类,它是最简单的Executor接口实现,实现4个基本方法。
ReuseExecutor
ReuseExecutor 提供了Statement 重用的功能,ReuseExecutor 中通过statementMap字段(HashMap
<String,Statement>
类型)缓存使用过的Statement对象,key是SQL语句,value是SQL对应的Statement对象
BatchExecutor
BatchExecutor实现了批处理多条SQL语句的功能,底层通过调用Statement.addBatch方法添加SQL语句,调用Statement.executeBatch方法批量执行其中记录的SQL语句,并使用返回的int数组
缓存
二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存。若二级缓存未命中,再去查询一级缓存。与一级缓存不同,二级缓存和具体的命名空间绑定,一级缓存则是和 SqlSession 绑定。
一级缓存
- 一级缓存的生命周期与SqlSession相同,其实也就与SqlSession中封装的Executor对象的生命周期相同。当调用Executor对象的close方法时,该Executor对象对应的一级缓存就变得不可用。一级缓存中对象的存活时间受很多方面的影响,例如,在调用Executorupdate)方法时,也会先清空一级缓存。其他影响一级缓存中数据的行为,我们在分析BaseExecutor的具体实现时会详细介绍。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。
- 一级缓存的CacheKey、对象由MappedStatement的id、对应的offset和limit、SQL语句(包含“?”占位符)、用户传递的实参以及Environment的id这五部分构成
二级缓存
- CachingExecutor 是一个Executor接口的装饰器,它为Executor对象增加了二级缓存的相关功能,TransactionalCache和TransactionalCacheManager是CachingExecutor依赖的两个组件。其中,TransactionalCache 继承了Cache接口,主要用于保存在某个SqlSession的某个事务中需要向某个二级缓存中添加的缓存数据。
- TransactionalCacheManager 用于管理 CachingExecutor使用的二级缓存对象,其中只定义了一个transactionalCaches 字段(HashMap
<Cache,TransactionalCache>
类型),它的key是对应的CachingExecutor 使用的二级缓存对象,value是相应的TransactionalCache对象,在该TransactionalCache中封装了对应的二级缓存对象,也就是这里的key - SynchronizedCache 这个装饰器,从而保证二级缓存的线程安全。
结果集映射
ResultSetHandler
在StatementHandler接口在执行完指定的select 语句之后,会将查询得到的结果集交给ResultSetHandler 完成映射处理ResultSetHandler 除了负责映射select 语句查询得到的结果集,还会处理存储过程执行后的输出参数
- DefaultResultSetHandler是MyBatis提供的ResultSetHandler接口的唯一实现
通过select 语句查询数据库得到的结果集由DefaultResultSetHandler.handleResultSets方法进行处理,该方法不仅可以处理Statement、PreparedStatement产生的结果集,还可以处理CallableStatement 调用存储过程产生的多结果集
ResultSetWrapper
ResultSetWrapper中记录了ResultSet中的一些元数据(记每列的列名,对应的Java类型,对应的JdbcType类型,对应的TypeHandler对象),并且提供了一系列操作ResultSet的辅助方法(getMappedColumnNames方法)
- DefaultResultSetHandler.handleResultSet方法完成对单个ResultSet的映射
- handleRowValuesForNestedResultMap方法完成ResultMap中存在嵌套映射
- DefaultResultSetHandler.resolveDiscriminatedResultMap方法会根据ResultMap对象中记录的Discriminator以及参与映射的列值
- DefaultResultSetHandler.createResultObject方法负责创建数据库记录映射得到的结果对象,映射操作是在getPropertyMappingValue方法中完成
延迟加载
MyBatis中与延迟加载相关的类有ResultLoader、ResultLoaderMap、ProxyFactory接口及实现类
- ResultLoader 主要负责保存一次延迟加载操作所需的全部信息,ResultLoader的核心是loadResult方法,该方法会通过Executor执行ResultLoader中记录的SQL语句并返回相应的延迟加载对象。
- ResultLoaderMap用loadMap字段(HashMap
类型)保存对象中延迟加载属性及其对应的ResultLoader对象之间的关系 - ProxyFactory 接口以及两个实现类,CglibProxyFactory 使用cglib方式创建代理对象,JavassitProxyFactory 使用Javassit方式创建代理。
总体运行逻辑
- 解析配置文件,根据配置初始化Mybatis运行环境(全局配置,数据连接池,插件,类型处理,别名处理等),解析映射文件(包含resulmap和sql的xml),将XML解析的结果和Mapper接口绑定
- 当代码中通过注入的Mapper对象调用相应的方法时,通过生成的动态代理对象调用实际定义的数据库操作逻辑代码(设计参数映射以及缓存优化),最后完成映射的逻辑返回结果
插件扩展
Mybatis插件
我们可以通过添加用户自定义插件的方式对MyBatis进行扩展
MyBatis允许在已映射语句执行过程中的某一点进行拦截调用,默认情况下,MyBatis允许拦截器拦截Executor的方法、ParameterHandler的方法、ResultSetHandler的方法以及StatementHandler的方法:
- Executor(update、query、flushStatements、commit、rollback、getTransaction、close、isClosed)
- ParameterHandler(getParameterObject、setParameters)
- ResultSetHandler(handleResultSets、handleCursorResultSets、handleOutputParameters)
- StatementHandler(prepare、parameterize、batch、update、query)
MyBatis 插件可以用来实现拦截器接口Interceptor(org.apache.ibatis.
plugin.Interceptor),在实现类中对拦截对象和方法进行处理。
先来看拦截器接口,了解该接口的每一个方法的作用和用法。Interceptor接口代码如下
public interface Interceptor{
Object intercept(Invocation invocation)throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
拦截器的配置方法。在mybatis-config.xml中,一般情况下,拦截器的配置如下
<plugins>
<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
<property name="propl"value="valuel"/><property name="prop2"value="value2"/>
</plugin>
</plugins>
- 当配置多个拦截器时,MyBatis会遍历所有拦截器,按顺序执行拦截器的plugin方法,被拦截的对象就会被层层代理。
- 在执行拦截对象的方法时,会一层层地调用拦截器,拦截器通过invocation.proceed调用下一层的方法,直到真正的方法被执行。
- 方法执行的结果会从最里面开始向外一层层返回,所以如果存在按顺序配置的A、B、C三个签名相同的拦截器,MyBaits会按照C>B>A>target.proceed()>A>B>C的顺序执行。如果A、B、C签名不同,就会按照MyBatis拦截对象的逻辑执行。
以拦截ResultSetHandler接口的handleResultSets方法为例,配置签名如下
@signature 注解包含以下三个属性
- type:设置拦截的接口,可选值是前面提到的4个接口。
- method:设置拦截接口中的方法名,可选值是前面4个接口对应的方法,需要和接口匹配。
- args:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法。
QIntercepts({
@Signature(
type=ResultSetHandler.class,method="handleResultSets",args={Statement.class})
})public class ResultSetInterceptor implements Interceptor(){
@Override//执行拦截逻辑的方法
public Object intercept(Invocation invocation) throws Throwable {
//通过invocation.getArgs()可以得到当前执行方法的参数
//第一个args[0]是MappedStatement对象,第二个args[1]是参数对象parameterObject。
final Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
Object parameter = args[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
return invocation.proceed();
}
@Override//决定是否触发intercept()方法
public Object plugin(Object target) {
//只拦截Executor对象,减少目标被代理的次数
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override//根据配置初始化Interceptor对象
public void setProperties(Properties properties) {
}
}