目录
0本文主要涉及
1各模块概要说明
1接口层
2基础支持层
反射模块:
类型转换模块
日志模块
资源加载模块
解析器模块
数据源模块
事务管理
缓存模块
Binding 模块
3核心处理层
配置解析
SQL 解析
参数映射
SQL执行
结果集映射
总体运行逻辑
4插件扩展
Mybatis源码学习总结
接口层:提供给外部使用的接口 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 setMethods = new HashMap<>();
//属性相应的getter方法集合,key是属性名称,value也是SetFieldInvoker对象
private final Map getMethods = new HashMap<>();
//记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
private final Map> setTypes = new HashMap<>();
//记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
private final Map> getTypes = new HashMap<>();
private Constructor<?>defaultConstructor;//记录了默认构造方法
//记录了所有属性名称的集合
private MapcaseInsensitivePropertyMap=new HashMap();
在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操作通过指定的日志框架打印出来
对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能
在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 中标签定义的键值对集合
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.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的实现更加简单,它同样依赖其中的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
private int multiplier;//参与计算hashcode,默认值是37
private int hashcode;//CacheKey对象的hashcode,初始值是17
private long checksum;//校验和
private List
下面四个部分构成的CacheKey对象,也就是说这四部分都会记录到该CacheKey对象的updateList集合中:
·MappedStatement的id。
·指定查询结果集的范围,也就是RowBounds.offset和RowBounds.limit。·查询所使用的SQL语句,也就是boundSql.getSql0方法返回的SQL语句,其中可能包含“?”占位符。
·用户传递给上述SQL语句的实际参数值。
实现自定义的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,MapperProxyFactory>knownMappers=
new HashMap,MapperProxyFactory>();
在需要执行某SQL语句时,会先调用MapperRegistry.getMapper方法获取实现了Mapper接口的代理对象,例如session.getMapper(BlogMapper.class)方法得到的实际上是MyBatis通过JDK动态代理为BlogMapper接口生成的代理对象。
MapperProxyFactory 主要负责创建代理对象
MapperProxyFactory.newInstance方法实现了mapperInterface接口的代理对象的功能
MapperProxy.invoke方法是代理对象执行的主要逻辑
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
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 对象中(定义的
待MyBatis初始化之后,开发人员可以通过初始化得到SqlSessionFactory创建SqlSession 对象并完成数据库操作)
//configuration是MyBatis初始化过程的核心对象,MyBatis中几乎全部的配置信息会保存到
//configuration 对象中。Configuration对象是在MyBatis初始化过程中创建且是全局唯一的,
//也有人称它是一个“A11-In-one”配置对象
protected final Configuration configuration;
//在mybatis-config.xm1配置文件中可以使用标签定义别名,这些定义的别名都会记录在该
//TypeAliasRegistry对象中,在第2章中已经介绍过其原理,不再重复描述protected final TypeAliasRegistry typeAliasRegistry;
//在mybatis-config.xm1配置文件中可以使用标签添加自定义TypeHandler器,完
//成指定数据库类型与Java类型的转换,这些TypelHandler都会记录在TypeHandlerRegistry中
protected final TypeHandlerRegistry typeHandlerRegistry;
SqlSessionFactoryBuilder.build方法会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件,而XMLConfigBuilder 继承自BaseBuilder抽象类
XMLConfigBuilder 主要负责解析mybatis-config.xml配置文件
XMLConfigBuilderpropertiesElement方法会解析 mybatis-config.xml配置文件中的
XMLConfig Builder.settingsAsProperties方法负责解析
XMLConfig Builder.typeAliasesElement方法负责解析
XMLConfigBuilder.typeHandlerElement方法负责解析
XMLConfigBuilder.pluginElement方法负责解析
XMLConfigBuilder.objectFactoryElement方法负责解析并实例化
XMLConfigBuilder.environmentsElement方法负责解析
XMLConfigBuilder.databaseldProviderElement方法负责解析
XMLConfigBuilder.mapperElement方法负责解析
XMLMapperBuilder负责解析映射配置文件
XMLMapperBuilder.cacheElement方法主要负责解析
CacheBuilder是Cache的建造者,CacheBuilder.setCacheProperties方法会根据
CacheBuilder.setStandardDecorators方法会根据CacheBuilder中各个字段的值,为cache对象添加对应的装饰器
XMLMapperBuilder.cacheRefElement方法负责解析
ResultMap
每个
在XMLMapperBuilder中通过resultMapElements0方法解析映射配置文件中的全部
XMLMapperBuilder.processConstructorElement方法解析
XMLMapperBuilder.sqlElement方法负责解析映射配置文件中定义的全部
得到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*集合。
MyBatis实现动态SQL语句的功能,提供了多种动态SQL语句对应的节点,例如,
XMLStatementBuilder负责解析SQL节点具体内容
SqlSource接口表示映射文件或注解中定义的SQL语句
MappedStatement表示映射配置文件中定义的SQL节点
通映射配置文件中定义的SQL节点会被解析成MappedStatement对象,其中的SQL语句会被解析成SqlSource对象,SQL语句中定义的动态SQL节点、文本节点等,则由SqlNode接口的相应实现表示
在解析SQL节点之前,首先通过XMLInclude Transformer解析SQL语句中的
XMLStatementBuilder.processSelectKeyNodes方法负责解析SQL节点中的
在XMLLanguageDriver.createSqlSource方法中会创建XMLScriptBuilder 对象并调用XMLScriptBuilder.parseScriptNode方法创建SqlSource对象
NodeHandler
NodeHandler 接口实现类会对不同的动态SQL标签进行解析,生成对应的SqlNode对象
SqlNode 接口有多个实现类,每个实现类对应一个动态SQL节点
在经过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语句的执行涉及多个组件,包括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方法会执行
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语句
Listquery(Statement statement,ResultHandler resultHandler)throws SQLException;
CursorqueryCursor(Statement statement)throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();//获取其中封装的ParameterHandler
}
RoutingStatementHandler 会根据MappedStatement中指定的statementlType字段,创建对应的StatementHandler 接口实现。BaseStatementHandler 是一个实现了StatementHandler接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法
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 底层依赖于java.sql.PreparedStatement对象来完成数据库的相关操作,在SimpleStatementHandler.parameterize方法中,会调用ParameterHandler.setParameters方法完成SQL语句的参数绑定
PreparedStatementHandler.instantiateStatement方法直接调用JDBCConnection的prepareStatement方法创建PreparedStatement对象
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语句,返回值分为结果对象列表或游标对象
Listquery(MappedStatement ms,Object parameter,RowBounds rowBounds,ResultHandler resultHandler,CacheKey cacheKey,BoundSql boundSql)throws SQLException;
Listquery(Mappedstatement ms,Object parameter,RowBounds rowBounds,ResultHandler resultHandler)throws SQLException;
CursorqueryCursor(MappedStatement ms,Object parameter,RowBounds rowBounds)throws SQLException;ListflushStatements()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是一个实现了Executor接口的抽象类,它实现了Executor接口的大部分方法,其中就使用了模板方法模式BaseExecutor中主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别是:doUpdate、doQuery、doQueryCursor、doFlushStatement方法,其余的功能在BaseExecutor中实现(一级缓存等固定不变的操作都封装在BaseExecutor中)
SimpleExecutor继承了BaseExecutor抽象类,它是最简单的Executor接口实现,实现4个基本方法。
ReuseExecutor 提供了Statement 重用的功能,ReuseExecutor 中通过statementMap字段(HashMap
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
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
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中,一般情况下,拦截器的配置如下
当配置多个拦截器时,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) {
}
}