依赖结构图

  1. mybatis-spring-boot-starter
  2. - spring-boot-starter
  3. - spring-boot-starter-jdbc
  4. - spring-boot-starter
  5. - HikariCP
  6. - slf4j
  7. - spring-jdbc
  8. - sping-beans
  9. - spring-core
  10. - spring-tx
  11. - spring-beans
  12. - spring-core
  13. - mybatis-spring-boot-autoconfigure
  14. - spring-boot-autoconfigure
  15. - mybatis
  16. - mybatis-spring

各模块概要说明

MyBatis源码结构 - 图1

  • 接口层:提供给外部使用的接口 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实现

MyBatis源码结构 - 图2

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中各个字段的含义如下

  1. private final Class<?> type;//对应的Class类型
  2. private final String[] readablePropertyNames;//可读(有getter方法的)属性的名称集合
  3. private final String[] writeablePropertyNames;//可写(有setter方法的)属性的名称集合
  4. //记录属性相应的 setter方法,key是属性名称,value是GetFieldInvoker对象
  5. private final Map<String, Invoker> setMethods = new HashMap<>();
  6. //属性相应的getter方法集合,key是属性名称,value也是SetFieldInvoker对象
  7. private final Map<String, Invoker> getMethods = new HashMap<>();
  8. //记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
  9. private final Map<String, Class<?>> setTypes = new HashMap<>();
  10. //记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
  11. private final Map<String, Class<?>> getTypes = new HashMap<>();
  12. private Constructor<?>defaultConstructor//记录了默认构造方法
  13. //记录了所有属性名称的集合
  14. private Map<StringString>caseInsensitivePropertyMap=new HashMap<StringString>();

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源码结构 - 图3

MyBatis中所有的类型转换器都继承TypeHandler接口,在TypeHandler 接口中定义了setParameter方法负责将数据由JdbcTlype 类型转换成Java类型;getResult方法及其重载负责将数据由Java类型转换成JdbcType类型

MyBatis源码结构 - 图4

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类通过配置生成相应的日志对象

MyBatis源码结构 - 图5

BaseJdbcLogger通过JDK动态代理的方式,将JDBC操作通过指定的日志框架打印出来

MyBatis源码结构 - 图6

资源加载模块

对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能

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等方法

MyBatis源码结构 - 图7

XPathParser中各个字段的含义和功能

  1. private Document document//Document对象通过createDocument方法得到
  2. private boolean validation//是否开启验证
  3. private EntityResolver entityResolver//用于加载本地DTD文件,具体实现为XMLMapperEntityResolver类
  4. private Properties variables//mybatis-config.xml 中<propteries>标签定义的键值对集合
  5. 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

MyBatis源码结构 - 图8

UnpooledDataSource

  • UnpooledDataSource.getConnection方法获取数据库连接时都会创建一个新连接(通过UnpooledDataSource.doGetConnection方法获取数据库连接)
  • UnpooledDataSource.initializeDriver方法主要负责数据库驱动的初始化
  • UnpooledDataSource.configureConnection方法主要完成数据库连接的一系列配置

PooledDataSource

实现了简易数据库连接池的功能,PooledDataSource创建新数据库连接的功能是依赖其中封装的UnpooledDataSource对象实现

PooledDataSource.getConnection方法首先会调用PooledDataSource.popConnection方法获取PooledConnection对象,然后通过PooledConnection.getProxyConnection方法获取数据库连接的代理对象

MyBatis源码结构 - 图9

当调用连接的代理对象的close方法时,并未关闭真正的数据连接,而是调用PooledDataSource.pushConnection方法将PooledConnection 对象归还给连接池,供之后重用

MyBatis源码结构 - 图10

PooledDataSource.pushConnection方法和popConnection方法中都调用了PooledConnection.isValid方法来检测PooledConnection的有效性

事务管理

事务接口和简单实现,于Spring结合使用时通过Spring管理事务

  • Transaction 接口有JdbcTransaction、ManagedTransaction两个实现,其对象分别由Jdbc TransactionFactory 和Managed TransactionFactory负责创建。

Jdbc Transaction

依赖于JDBC Connection控制事务的提交和回滚

  1. protected Connection connection//事务对应的数据库连接
  2. protected DataSource dataSource//数据库连接所属的DataSource
  3. protected TransactionIsolationLevel level//事务隔离级别
  4. 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接口的基本实现

  1. public interface Cache{
  2. String getId();//该缓存对象的id
  3. //向缓存中添加数据,一般情况下,key是Cachekey,value是查询结果
  4. void putObjectObject keyObject value);
  5. object getobjectobject key);//根据指定的key,在缓存中查找对应的结果对象object removeobject(Object key);//删除key对应的缓存项
  6. void clear();//清空缓存
  7. int getsize();//缓存项的个数,该方法不会被MyBatis核心代码使用,所以可提供空实现
  8. //获取读写锁,该方法不会被MyBatis核心代码使用,所以可提供空实现
  9. ReadWriteLock getReadwriteLock();
  10. }

MyBatis源码结构 - 图11

PerpetualCache

实现比较简单,底层使用HashMap 记录缓存项LruCache通过LinkedHashMap实现,new LinkedHashMap<Object, Object>(size,.75F, true)

CacheKey

  1. private int multiplier//参与计算hashcode,默认值是37
  2. private int hashcode//CacheKey对象的hashcode,初始值是17
  3. private long checksum//校验和
  4. private List<Object> updateList//由该集合中的所有对象共同决定两个CacheKey是否相同
  5. 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中字段的含义和功能

  1. //Configuration对象,MyBatis全局唯一的配置对象,其中包含了所有配置信息
  2. private final Configuration config;
  3. //记录了Mapper接口与对应MapperProxyFactory之间的关系private final Map<Class<?>,MapperProxyFactory<?>>knownMappers=
  4. 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 中各个字段的信息

  1. private final SqlCommand command//记录了SQL语句的名称和类型
  2. 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

MyBatis源码结构 - 图12

  1. //configuration是MyBatis初始化过程的核心对象,MyBatis中几乎全部的配置信息会保存到
  2. //configuration 对象中。Configuration对象是在MyBatis初始化过程中创建且是全局唯一的,
  3. //也有人称它是一个“A11-In-one”配置对象
  4. protected final Configuration configuration
  5. //在mybatis-config.xm1配置文件中可以使用<typeAliases>标签定义别名,这些定义的别名都会记录在该
  6. //TypeAliasRegistry对象中,在第2章中已经介绍过其原理,不再重复描述protected final TypeAliasRegistry typeAliasRegistry;
  7. //在mybatis-config.xm1配置文件中可以使用<typeHandlers>标签添加自定义TypeHandler器,完
  8. //成指定数据库类型与Java类型的转换,这些TypelHandler都会记录在TypeHandlerRegistry中
  9. protected final TypeHandlerRegistry typeHandlerRegistry

SqlSessionFactoryBuilder.build方法会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件,而XMLConfigBuilder 继承自BaseBuilder抽象类

XMLConfigBuilder

主要负责解析mybatis-config.xml配置文件

  1. XMLConfigBuilderpropertiesElement方法会解析 mybatis-config.xml配置文件中的<properties>节点并形成java.util.Properties对象,之后将该Properties对象设置到XPathParser和Configuration的variables字段中
  2. XMLConfig Builder.settingsAsProperties方法负责解析<settings>节点,在<settings>节点下的配置是MyBatis全局性的配置
  3. XMLConfig Builder.typeAliasesElement方法负责解析<typeAliases>节点及其子节点,并通过TypeAliasRegistry 完成别名的注册
  4. XMLConfigBuilder.typeHandlerElement方法负责解析<typeHandlers>节点,并通过TypeHandlerRegistry 对象完成TypeHandler的注册
  5. XMLConfigBuilder.pluginElement方法负责解析<plugins>节点中定义的插件,并完成实例化和配置操作
  6. XMLConfigBuilder.objectFactoryElement方法负责解析并实例化<objectFactory>节点指定的ObjectFactory实现类,之后将自定义的ObjectFactory 对象记录到Configuration.objectFactory字段中
  7. XMLConfigBuilder.environmentsElement方法负责解析<environments>的相关配置,它会根据XMLConfigBuilder.environment 字段值确定要使用的<environmen>配置,之后创建对应的TransactionFactory和DataSource对象,并封装进Environment对象中
  8. XMLConfigBuilder.databaseldProviderElement方法负责解析<databaseldProvider>节点,并创建指定的DatabaseldProvider对象。DatabaseldProvider会返回 databaseld值,MyBatis会根据databaseld选择合适的SQL进行执行
  9. 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对象表示

MyBatis源码结构 - 图13

  • 在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对象

MyBatis源码结构 - 图14

SqlNode

接口有多个实现类,每个实现类对应一个动态SQL节点

MyBatis源码结构 - 图15

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

MyBatis源码结构 - 图16

  • 无论是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接口

  1. public interface KeyGenerator{
  2. //在执行insert之前执行,设置属性order="BEFORE"
  3. void processBeforeExecutor executorMappedStatement msStatement stmtObject parameter);
  4. //在执行insert之后执行,设置属性order="AFTER"
  5. void processAfterExecutor executorMappedStatement msStatement stmtobject parameter);
  6. }

MyBatis 提供了三个KeyGenerator接口的实现

MyBatis源码结构 - 图17

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语句,将结果集映射成结果对象
  1. public interface StatementHandler{
  2. //从连接中获取一个Statement
  3. Statement prepareConnection connectionInteger transactionTimeoutthrows SQLException
  4. //绑定 statement执行时所需的实参
  5. void parameterizeStatement statement).throws SQLException
  6. //批量执行SQL语句
  7. void batchStatement statementthrows SQLException
  8. //执行update/insert/delete语句
  9. int updateStatement statementthrows SQLException
  10. //执行select语句
  11. <E>List<E>queryStatement statementResultHandler resultHandlerthrows SQLException
  12. <E>Cursor<E>queryCursorStatement statementthrows SQLException
  13. BoundSql getBoundSql();
  14. ParameterHandler getParameterHandler();//获取其中封装的ParameterHandler

MyBatis源码结构 - 图18

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接口中定义的方法如下

  1. public interface Executor{
  2. //执行update、insert、delete三种类型的sQL语句
  3. int updateMappedStatement msObject parameterthrows SQLException;
  4. //执行select类型的SQL语句,返回值分为结果对象列表或游标对象
  5. <E>List<E>queryMappedStatement msObject parameterRowBounds rowBoundsResultHandler resultHandlerCacheKey cacheKeyBoundSql boundSqlthrows SQLException;
  6. <E>List<E>queryMappedstatement msObject parameterRowBounds rowBoundsResultHandler resultHandlerthrows SQLException;
  7. <E>Cursor<E>queryCursorMappedStatement msObject parameterRowBounds rowBoundsthrows SQLExceptionList<BatchResult>flushStatements()throws SQLException;//批量执行SQL 语句
  8. void commitboolean requiredthrows SQLException;//提交事务
  9. void rollbackboolean requiredthrows SQLException;//回滚事务
  10. //创建缓存中用到的CacheKey对象
  11. Cachekey createCacheKeyMappedStatement msobject parameterobjectRowBounds rowBoundsBoundSql boundSql);
  12. boolean isCachedMappedStatement msCachekey key);//根据CacheKey对象查找缓存
  13. void clearLocalCache();//清空一级缓存
  14. //延迟加载一级缓存中的数据,DeferredLoad的相关内容后面会详细介绍
  15. void deferLoadMappedStatement msMetaobject resultobjectString propertyCacheKey keyClass<?> targetType);
  16. Transaction getTransaction();//获取事务对象
  17. void closeboolean forceRollback);//关闭Executor 对象
  18. boolean isClosed();//检测Executor是否已关闭
  19. }

MyBatis源码结构 - 图19

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源码结构 - 图20

总体运行逻辑

  • 解析配置文件,根据配置初始化Mybatis运行环境(全局配置,数据连接池,插件,类型处理,别名处理等),解析映射文件(包含resulmap和sql的xml),将XML解析的结果和Mapper接口绑定
  • 当代码中通过注入的Mapper对象调用相应的方法时,通过生成的动态代理对象调用实际定义的数据库操作逻辑代码(设计参数映射以及缓存优化),最后完成映射的逻辑返回结果

MyBatis源码结构 - 图21

插件扩展

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接口代码如下

  1. public interface Interceptor{
  2. Object interceptInvocation invocationthrows Throwable
  3. Object pluginObject target);
  4. void setPropertiesProperties properties);
  5. }

拦截器的配置方法。在mybatis-config.xml中,一般情况下,拦截器的配置如下

  1. <plugins>
  2. <plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
  3. <property name="propl"value="valuel"/><property name="prop2"value="value2"/>
  4. </plugin>
  5. </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:设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法。
  1. QIntercepts({
  2. @Signature
  3. type=ResultSetHandler.classmethod="handleResultSets"args={Statement.class})
  4. })public class ResultSetInterceptor implements Interceptor(){
  5. @Override//执行拦截逻辑的方法
  6. public Object intercept(Invocation invocation) throws Throwable {
  7. //通过invocation.getArgs()可以得到当前执行方法的参数
  8. //第一个args[0]是MappedStatement对象,第二个args[1]是参数对象parameterObject。
  9. final Object[] args = invocation.getArgs();
  10. MappedStatement mappedStatement = (MappedStatement) args[0];
  11. Object parameter = args[1];
  12. BoundSql boundSql = mappedStatement.getBoundSql(parameter);
  13. return invocation.proceed();
  14. }
  15. @Override//决定是否触发intercept()方法
  16. public Object plugin(Object target) {
  17. //只拦截Executor对象,减少目标被代理的次数
  18. if (target instanceof Executor) {
  19. return Plugin.wrap(target, this);
  20. } else {
  21. return target;
  22. }
  23. }
  24. @Override//根据配置初始化Interceptor对象
  25. public void setProperties(Properties properties) {
  26. }
  27. }