type
status
date
slug
summary
tags
category
icon
password

1. 接口层

1.1. session 模块

1.1.1. 核心对象生命周期

1.1.1.1. SqlSessionFactoryBuilder

首先是SqlSessionFactoryBuiler。它是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。

1.1.1.2. SqlSessionFactory

SqlSessionFactory是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。

1.1.1.3. SqlSession

SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。

1.1.1.4. Mapper

Mapper(实际上是一个代理对象)是从SqlSession中获取的。
eg: UserMapper mapper = sqlSession.getMapper(UserMapper.class)
它的作用是发送SQL来操作数据库的数据。它应该在一个SqlSession事务方法之内。
notion image

1.2. SqlSessionFactory 02-工厂模式

implements DefaultSqlSessionFactory、SqlSessionManager
获取 SqlSessionFactory对象方式

1.2.1. SqlSessionFactoryBuilder

首先我们new了一个SqlSessionFactoryBuilder,这是建造者模式的运用(建造者模式用来创建复杂对象,而不需要关注内部细节,是一种封装的体现)。MyBatis中很多地方用到了建造者模式 具体点击 建造者模式应用(名字以Builder结尾的类还有9个)。
SqlSessionFactoryBuilder中用来创建SqlSessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方式来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。
在build方法中首先是创建了一个XMLConfigBuilder对象,XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类(关联到源码路径),比如:
  • XMLMapperBuilder:解析Mapper映射器
  • XMLStatementBuilder:解析增删改查标签
  • XMLScriptBuilder:解析动态SQL
  • 执行 build(parser.parse());
构建的代码,parser.parse()方法返回的是一个Configuration对象,build方法的如下
在这儿我们可以看到SessionFactory最终实现是DefaultSqlSessionFactory对象。

1.2.1.1. XMLConfigBuilder

XMLConfigBuilder初始化的时候做了哪些操作

1.2.1.2. Configuration

初始化做的操作
通过上面的分析我们可以看到XMLConfigBuilder完成了XML文件的解析对应XPathParser和Configuration对象的初始化操作,然后我们再来看下parse方法到底是如何解析配置文件的.

1.2.1.3. parser.parse()

1.2.1.3.1. 全局配置文件解析

properties解析
第一个是解析<properties>标签,读取我们引入的外部配置文件,例如db.properties。 这里面又有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径的(url)。
解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面(Hashtable对象,KV存储),最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。
settings解析
获取子节点
loadCustomVfsImpl
在MyBatis中,基于<settings>标签中的<vfsImpl>配置,我们可以创建一个抽象类 VFS 并提供两个具体实现类 JBoss6VFSDefaultVFS
loadCustomLogImpl(settings)方法
<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2,SLF4J等等,在logging包中。
typeAliasesj解析
plugins解析
@插件的使用
objectFactory,objectWrapperFactory及reflectorFactory解析
ObjectFactory用来创建返回的对象。
ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。
ReflectorFactory是反射的工具箱,对反射的操作进行了封装(官网和文档没有这个对象的描述)。
以上四个对象,都是用resolveClass创建的。
settingsElement(settings)方法
这里就是对<settings>标签里面所有子标签的处理了,前面我们已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。
settings二级标签中一共26个配置,比如二级缓存、延迟加载、默认执行器类型等等。
需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。
所有的值,都会赋值到Configuration的属性里面去。
environments解析
这一步是解析<environments>标签。
我们前面讲过,一个environment就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。
databaseIdProviderElement()
解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。
typeHandlerElement()
跟TypeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系,存放在TypeHandlerRegistry对象里面。
mapper解析
最后就是<mappers>标签的解析。
根据全局配置文件中不同的注册方式,用不同的方式扫描,但最终都是做了两件事情,对于语句的注册和接口的注册。
扫描类型
含义
resource
相对路径
url
绝对路径
package
class
单个接口

1.2.1.4. 映射文件的解析

首先进入parse方法 ( XMLConfigBuilder.class -> parseConfiguration() -> mappersElement() )
configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
bindMapperForNamespace()——把namespace(接口类型)和工厂类MapperProxyFactory绑定起来。
configurationElement方法
configurationElement是对Mapper.xml中所有具体的标签的解析,包括namespace、cache、parameterMap、resultMap、sql和select|insert|update|delete。
在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。
bindMapperForNamespace方法
通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。
同样的再进入parse方法中查看
sum:
  1. 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。
  1. 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
  1. 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。
    1. notion image

1.3. SqlSession

程序每一次操作数据库,都需要创建一个会话,我们用openSession()方法来创建。接下来我们看看SqlSession创建过程中做了哪些操作。
通过前面创建的DefaultSqlSessionFactory的openSession方法来创建
首先会获取默认的执行器类型。默认的是simple
TransactionFactory 在解析environment标签 时创建
notion image
根据事务工厂和默认的执行器类型,创建执行器
openSessionFromDataSource 最后返回一个DefaultSqlSession对象
在这个DefaultSqlSession对象中包括了Configuration和Executor对象
sum : 创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,Executor是SQL的实际执行对象。
notion image

1.4. Mapper代理对象

接下来看下通过getMapper方法获取对应的接口的代理对象的实现原理
进入DefaultSqlSession中查看
代码中发现代理对象是通过JDK动态代理 11-代理模式,返回的代理对象。而且里面也传递了一个实现了InvocationHandler接口的触发管理类。
notion image
总结:获得Mapper对象的过程,实质上是获取了一个JDK动态代理对象(类型是$ProxyN)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。
notion image

1.5. SQL执行

看看SQL语句的具体执行过程是怎么样的
由于所有的Mapper都是JDK动态代理对象,所以任意的方法都是执行触发管理类MapperProxy的invoke()方法
然后进入到PlainMethodInvoker的invoke方法

1.5.1. mapperMethod.execute

在这一步,根据不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:
1)调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。
2)调用sqlSession的insert()、update()、delete()、selectOne ()方法。我们以查询为例,会走到selectOne()方法。

1.5.2. sqlSession.selectOne

这里来到了对外的接口的默认实现类DefaultSqlSession。
selectOne()最终也是调用了selectList()
在SelectList()中,我们先根据command name(Statement ID)从Configuration中拿到MappedStatement。ms里面有xml中增删改查标签配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等
然后执行了Executor的query()方法。
Executor是第二步openSession的时候创建的,创建了执行器基本类型之后,依次执行了二级缓存装饰,和插件包装。
所以,如果有被插件包装,这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false,再走到CachingExecutor的逻辑,然后会走到BaseExecutor的query()方法。

1.5.3. CachingExecutor.query()

二级缓存的CacheKey是怎么构成的呢?或者说,什么样的查询才能确定是同一个查询呢
在BaseExecutor的createCacheKey方法中,用到了六个要素:
A: 方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同,才会被认为是同一个查询。
CacheKey的实际值举例(toString()生成的),debug可以看到:
注意看一下CacheKey的属性,里面有一个List按顺序存放了这些要素。
Q:怎么比较两个CacheKey是否相等呢?
A: 如果一上来就是依次比较六个要素是否相等,要比较6次,这样效率不高。有没有更高效的方法呢?继承Object的每个类,都有一个hashCode ()方法,用来生成哈希码。它是用来在集合中快速判重的。
在生成CacheKey的时候(update方法),也更新了CacheKey的hashCode,它是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。
hashcode = multiplier * hashcode + baseHashCode;
Object中的hashCode()是一个本地方法,通过随机数算法生成(OpenJDK8 ,默认,可以通过-XX:hashCode修改)。CacheKey中的hashCode()方法进行了重写,返回自己生成的hashCode。
Q: 为什么要用37作为乘法因子呢?
A: 跟String中的31类似。
CacheKey中的equals也进行了重写,比较CacheKey是否相等。
如果哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后才循环比较要素,防止哈希碰撞。
CacheKey生成之后,调用另一个query()方法。

1.5.4. BaseExecutor.query CachingExecutor.query 方法

notion image
在configuration.newStatementHandler()中,new一个StatementHandler,先得到RoutingStatementHandler。
RoutingStatementHandler里面没有任何的实现,是用来创建基本的StatementHandler的。这里会根据MappedStatement里面的statementType决定StatementHandler的类型。默认是PREPARED(STATEMENT、PREPARED、CALLABLE)。
StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。
这两个对象都是在上面new的时候创建的。
这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。
创建Statement
用new出来的StatementHandler创建Statement对象。
notion image
执行查询操作,如果有插件包装,会先走到被拦截的业务逻辑。
进入到PreparedStatementHandler中处理
执行PreparedStatement的execute()方法,后面就是JDBC包中的PreparedStatement的执行了。ResultSetHandler处理结果集,如果有插件包装,会先走到被拦截的业务逻辑。
总结:调用代理对象执行SQL操作的流程
notion image

1.6. MyBatis核心对象

对象
相关对象
作用
Configuration
MapperRegistry<br/>TypeAliasRegistry<br/>TypeHandlerRegistry
包含了MyBatis的所有的配置信息
SqlSession
SqlSessionFactory<br/>DefaultSqlSession
对操作数据库的增删改查的API进行了封装,提供给应用层使用
Executor
BaseExecutor<br/>SimpleExecutor<br/>BatchExecutor<br/>ReuseExecutor
MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler
BaseStatementHandler<br>SimpleStatementHandler<br/>PreparedStatementHandler<br/>CallableStatementHandler
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler
DefaultParameterHandler
把用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler
DefaultResultSetHandler
把JDBC返回的ResultSet结果集对象转换成List类型的集合
MapperProxy
MapperProxyFactory
触发管理类,用于代理Mapper接口方法
MappedStatement
SqlSource<br/>BoundSql
MappedStatement维护了一条<select|update|delete|insert>节点的封装,表示一条SQL包括了SQL信息、入参信息、出参信息
sum :用自己的话描述下Mybatis的工作原理

1. 接口层

1.1. session 模块

1.1.1. 核心对象生命周期

1.1.1.1. SqlSessionFactoryBuilder

首先是SqlSessionFactoryBuiler。它是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。

1.1.1.2. SqlSessionFactory

SqlSessionFactory是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。

1.1.1.3. SqlSession

SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。

1.1.1.4. Mapper

Mapper(实际上是一个代理对象)是从SqlSession中获取的。
eg: UserMapper mapper = sqlSession.getMapper(UserMapper.class)
它的作用是发送SQL来操作数据库的数据。它应该在一个SqlSession事务方法之内。
notion image

1.2. SqlSessionFactory 02-工厂模式

implements DefaultSqlSessionFactory、SqlSessionManager
获取 SqlSessionFactory对象方式

1.2.1. SqlSessionFactoryBuilder

首先我们new了一个SqlSessionFactoryBuilder,这是建造者模式的运用(建造者模式用来创建复杂对象,而不需要关注内部细节,是一种封装的体现)。MyBatis中很多地方用到了建造者模式 具体点击 建造者模式应用(名字以Builder结尾的类还有9个)。
SqlSessionFactoryBuilder中用来创建SqlSessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方式来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。
在build方法中首先是创建了一个XMLConfigBuilder对象,XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类(关联到源码路径),比如:
  • XMLMapperBuilder:解析Mapper映射器
  • XMLStatementBuilder:解析增删改查标签
  • XMLScriptBuilder:解析动态SQL
  • 执行 build(parser.parse());
构建的代码,parser.parse()方法返回的是一个Configuration对象,build方法的如下
在这儿我们可以看到SessionFactory最终实现是DefaultSqlSessionFactory对象。

1.2.1.1. XMLConfigBuilder

XMLConfigBuilder初始化的时候做了哪些操作

1.2.1.2. Configuration

初始化做的操作
通过上面的分析我们可以看到XMLConfigBuilder完成了XML文件的解析对应XPathParser和Configuration对象的初始化操作,然后我们再来看下parse方法到底是如何解析配置文件的.

1.2.1.3. parser.parse()

1.2.1.3.1. 全局配置文件解析

properties解析
第一个是解析<properties>标签,读取我们引入的外部配置文件,例如db.properties。 这里面又有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径的(url)。
解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面(Hashtable对象,KV存储),最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。
settings解析
获取子节点
loadCustomVfsImpl
在MyBatis中,基于<settings>标签中的<vfsImpl>配置,我们可以创建一个抽象类 VFS 并提供两个具体实现类 JBoss6VFSDefaultVFS
loadCustomLogImpl(settings)方法
<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2,SLF4J等等,在logging包中。
typeAliasesj解析
plugins解析
@插件的使用
objectFactory,objectWrapperFactory及reflectorFactory解析
ObjectFactory用来创建返回的对象。
ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。
ReflectorFactory是反射的工具箱,对反射的操作进行了封装(官网和文档没有这个对象的描述)。
以上四个对象,都是用resolveClass创建的。
settingsElement(settings)方法
这里就是对<settings>标签里面所有子标签的处理了,前面我们已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。
settings二级标签中一共26个配置,比如二级缓存、延迟加载、默认执行器类型等等。
需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。
所有的值,都会赋值到Configuration的属性里面去。
environments解析
这一步是解析<environments>标签。
我们前面讲过,一个environment就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。
databaseIdProviderElement()
解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。
typeHandlerElement()
跟TypeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系,存放在TypeHandlerRegistry对象里面。
mapper解析
最后就是<mappers>标签的解析。
根据全局配置文件中不同的注册方式,用不同的方式扫描,但最终都是做了两件事情,对于语句的注册和接口的注册。
扫描类型
含义
resource
相对路径
url
绝对路径
package
class
单个接口

1.2.1.4. 映射文件的解析

首先进入parse方法 ( XMLConfigBuilder.class -> parseConfiguration() -> mappersElement() )
configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
bindMapperForNamespace()——把namespace(接口类型)和工厂类MapperProxyFactory绑定起来。
configurationElement方法
configurationElement是对Mapper.xml中所有具体的标签的解析,包括namespace、cache、parameterMap、resultMap、sql和select|insert|update|delete。
在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。
bindMapperForNamespace方法
通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。
同样的再进入parse方法中查看
sum:
  1. 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。
  1. 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
  1. 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。
    1. notion image

1.3. SqlSession

程序每一次操作数据库,都需要创建一个会话,我们用openSession()方法来创建。接下来我们看看SqlSession创建过程中做了哪些操作。
通过前面创建的DefaultSqlSessionFactory的openSession方法来创建
首先会获取默认的执行器类型。默认的是simple
TransactionFactory 在解析environment标签 时创建
notion image
根据事务工厂和默认的执行器类型,创建执行器
openSessionFromDataSource 最后返回一个DefaultSqlSession对象
在这个DefaultSqlSession对象中包括了Configuration和Executor对象
sum : 创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,Executor是SQL的实际执行对象。
notion image

1.4. Mapper代理对象

接下来看下通过getMapper方法获取对应的接口的代理对象的实现原理
进入DefaultSqlSession中查看
代码中发现代理对象是通过JDK动态代理 11-代理模式,返回的代理对象。而且里面也传递了一个实现了InvocationHandler接口的触发管理类。
notion image
总结:获得Mapper对象的过程,实质上是获取了一个JDK动态代理对象(类型是$ProxyN)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。
notion image

1.5. SQL执行

看看SQL语句的具体执行过程是怎么样的
由于所有的Mapper都是JDK动态代理对象,所以任意的方法都是执行触发管理类MapperProxy的invoke()方法
然后进入到PlainMethodInvoker的invoke方法

1.5.1. mapperMethod.execute

在这一步,根据不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:
1)调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。
2)调用sqlSession的insert()、update()、delete()、selectOne ()方法。我们以查询为例,会走到selectOne()方法。

1.5.2. sqlSession.selectOne

这里来到了对外的接口的默认实现类DefaultSqlSession。
selectOne()最终也是调用了selectList()
在SelectList()中,我们先根据command name(Statement ID)从Configuration中拿到MappedStatement。ms里面有xml中增删改查标签配置的所有属性,包括id、statementType、sqlSource、useCache、入参、出参等等
然后执行了Executor的query()方法。
Executor是第二步openSession的时候创建的,创建了执行器基本类型之后,依次执行了二级缓存装饰,和插件包装。
所以,如果有被插件包装,这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false,再走到CachingExecutor的逻辑,然后会走到BaseExecutor的query()方法。

1.5.3. CachingExecutor.query()

二级缓存的CacheKey是怎么构成的呢?或者说,什么样的查询才能确定是同一个查询呢
在BaseExecutor的createCacheKey方法中,用到了六个要素:
A: 方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同,才会被认为是同一个查询。
CacheKey的实际值举例(toString()生成的),debug可以看到:
注意看一下CacheKey的属性,里面有一个List按顺序存放了这些要素。
Q:怎么比较两个CacheKey是否相等呢?
A: 如果一上来就是依次比较六个要素是否相等,要比较6次,这样效率不高。有没有更高效的方法呢?继承Object的每个类,都有一个hashCode ()方法,用来生成哈希码。它是用来在集合中快速判重的。
在生成CacheKey的时候(update方法),也更新了CacheKey的hashCode,它是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。
hashcode = multiplier * hashcode + baseHashCode;
Object中的hashCode()是一个本地方法,通过随机数算法生成(OpenJDK8 ,默认,可以通过-XX:hashCode修改)。CacheKey中的hashCode()方法进行了重写,返回自己生成的hashCode。
Q: 为什么要用37作为乘法因子呢?
A: 跟String中的31类似。
CacheKey中的equals也进行了重写,比较CacheKey是否相等。
如果哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后才循环比较要素,防止哈希碰撞。
CacheKey生成之后,调用另一个query()方法。

1.5.4. BaseExecutor.query CachingExecutor.query 方法

notion image
在configuration.newStatementHandler()中,new一个StatementHandler,先得到RoutingStatementHandler。
RoutingStatementHandler里面没有任何的实现,是用来创建基本的StatementHandler的。这里会根据MappedStatement里面的statementType决定StatementHandler的类型。默认是PREPARED(STATEMENT、PREPARED、CALLABLE)。
StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。
这两个对象都是在上面new的时候创建的。
这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。
创建Statement
用new出来的StatementHandler创建Statement对象。
notion image
执行查询操作,如果有插件包装,会先走到被拦截的业务逻辑。
进入到PreparedStatementHandler中处理
执行PreparedStatement的execute()方法,后面就是JDBC包中的PreparedStatement的执行了。ResultSetHandler处理结果集,如果有插件包装,会先走到被拦截的业务逻辑。
总结:调用代理对象执行SQL操作的流程
notion image

1.6. MyBatis核心对象

对象
相关对象
作用
Configuration
MapperRegistry<br/>TypeAliasRegistry<br/>TypeHandlerRegistry
包含了MyBatis的所有的配置信息
SqlSession
SqlSessionFactory<br/>DefaultSqlSession
对操作数据库的增删改查的API进行了封装,提供给应用层使用
Executor
BaseExecutor<br/>SimpleExecutor<br/>BatchExecutor<br/>ReuseExecutor
MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler
BaseStatementHandler<br>SimpleStatementHandler<br/>PreparedStatementHandler<br/>CallableStatementHandler
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler
DefaultParameterHandler
把用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler
DefaultResultSetHandler
把JDBC返回的ResultSet结果集对象转换成List类型的集合
MapperProxy
MapperProxyFactory
触发管理类,用于代理Mapper接口方法
MappedStatement
SqlSource<br/>BoundSql
MappedStatement维护了一条<select|update|delete|insert>节点的封装,表示一条SQL包括了SQL信息、入参信息、出参信息
sum :用自己的话描述下Mybatis的工作原理
Mybatis架构Mybatis-Cache模块
Loading...