创意电子

标题: 从源码角度分析 Mybatis 工作原理 [打印本页]

作者: 高可用架构    时间: 2021-9-3 11:57
标题: 从源码角度分析 Mybatis 工作原理
作者:vivo互联网服务器团队-Zhang Peng


一、MyBatis 完整示例


这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的。


注:本文后面章节中的原理、源码部分也将基于这个示例来举行讲解。完整示例源码地点


1.1. 数据库准备


在本示例中,必要针对一张用户表举行 CRUD 操纵。其数据模型如下:
CREATE TABLE IF NOT EXISTS user ( id BIGINT(10) UNSIGNED NOT  AUTO_INCREMENT COMMENT 'Id', name VARCHAR(10) NOT  DEFAULT '' COMMENT '用户名', age INT(3) NOT  DEFAULT 0 COMMENT '年龄', address VARCHAR(32) NOT  DEFAULT '' COMMENT '地点', email VARCHAR(32) NOT  DEFAULT '' COMMENT '邮件', PRIMARY KEY (id)) COMMENT = '用户表';
INSERT INTO user (name, age, address, email)VALUES ('张三', 18, '北京', '[email protected]');INSERT INTO user (name, age, address, email)VALUES ('李四', 19, '上海', '[email protected]');

1.2. 添加 MyBatis


如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> org.Mybatis</span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"groupId/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Mybatis</span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"artifactId/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> x.x.x</span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"version/span></span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"dependency/span>

1.3. MyBatis 配置


XML 配置文件中包含了对 MyBatis 系统的焦点设置,包括获取数据库连接实例的数据源(DataSource)以及决定事件作用域和控制方式的事件管理器(TransactionManager)。


本示例中只是给出最简化的配置。【示例】MyBatis-config.xml 文件
<span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet__tag" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"dataSource/span> </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"environment/span> </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"environments/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"mappers/span></span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"configuration/span>说明:上面的配置文件中仅仅指定了数据源连接方式和 User 表的映射配置文件。


1.4 Mapper


1.4.1 Mapper.xml


个人理解,Mapper.xml 文件可以看做是 MyBatis 的 JDBC SQL 模板。【示例】UserMapper.xml 文件。


下面是一个通过 MyBatis Generator 主动生成的完整的 Mapper 文件。
<span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"resultMap/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  delete from user where id = #{id,jdbcType=BIGINT} </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"delete/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  insert into user (id, name, age, address, email) values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{address,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}) </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"insert/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  update user set name = #{name,jdbcType=VARCHAR}, age = #{age,jdbcType=INTEGER}, address = #{address,jdbcType=VARCHAR}, email = #{email,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"update/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  select id, name, age, address, email from user where id = #{id,jdbcType=BIGINT} </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"select/span><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  select id, name, age, address, email from user </span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"select/span></span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"mapper/span>

1.4.2 Mapper.java


Mapper.java 文件是 Mapper.xml 对应的 Java 对象。【示例】UserMapper.java 文件
public interface UserMapper {
int deleteByPrimaryKey(Long id);
int insert(User record);
User selectByPrimaryKey(Long id);
List selectAll();
int updateByPrimaryKey(User record);
}

对比 UserMapper.java 和 UserMapper.xml 文件,不难发现:UserMapper.java 中的方法和 UserMapper.xml 的 CRUD 语句元素( 、、、)存在逐一对应关系。


在 MyBatis 中,正是通过方法的全限定名,将二者绑定在一起。


1.4.3 数据实体.java


【示例】User.java 文件
public class User { private Long id;
private String name;
private Integer age;
private String address;
private String email;
}

、、、 的 parameterType 属性以及  的 type 属性都可能会绑定到数据实体。这样就可以把 JDBC 操纵的输入输出和 JavaBean 结合起来,更加方便、易于理解。


1.5. 测试步伐


【示例】MyBatisDemo.java 文件
public class MyBatisDemo {
public static void main(String[] args) throws Exception { // 1. 加载 MyBatis 配置文件,创建 SqlSessionFactory // 注:在现实的应用中,SqlSessionFactory 应该是单例 InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream);
// 2. 创建一个 SqlSession 实例,举行数据库操纵 SqlSession sqlSession = factory.openSession();
// 3. Mapper 映射并执行 Long params = 1L; List list = sqlSession.selectList("io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey", params); for (User user : list) { System.out.println("user name: " + user.getName()); } // 输出:user name: 张三 }
}

说明:SqlSession 接口是 MyBatis API 焦点中的焦点,它代表 MyBatis 和数据库一次完整会话。


二、MyBatis 生命周期



                               
登录/注册后可看大图



2.1. SqlSessionFactoryBuilder


2.1.1 SqlSessionFactoryBuilder 的职责


SqlSessionFactoryBuilder 负责创建
SqlSessionFactory 实例。
SqlSessionFactoryBuilder 可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。


Configuration 类包含了对一个 SqlSessionFactory 实例你可能关心的所有内容。



                               
登录/注册后可看大图



SqlSessionFactoryBuilder 应用了建造者设计模式,它有五个 build 方法,答应你通过不同的资源创建 SqlSessionFactory 实例。
SqlSessionFactory build(InputStream inputStream)SqlSessionFactory build(InputStream inputStream, String environment)SqlSessionFactory build(InputStream inputStream, Properties properties)SqlSessionFactory build(InputStream inputStream, String env, Properties props)SqlSessionFactory build(Configuration config)

2.1.2 SqlSessionFactoryBuilder 的生命周期


SqlSessionFactoryBuilder 可以被实例化、使用和抛弃,一旦创建了 SqlSessionFactory,就不再必要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。


你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以包管所有的 XML 解析资源可以被释放给更紧张的事情。


2.2. SqlSessionFactory


2.2.1 SqlSessionFactory 职责


SqlSessionFactory 负责创建 SqlSession 实例。



                               
登录/注册后可看大图



SqlSessionFactory 应用了工厂设计模式,它提供了一组方法,用于创建 SqlSession 实例。
SqlSession openSession()SqlSession openSession(boolean autoCommit)SqlSession openSession(Connection connection)SqlSession openSession(TransactionIsolationLevel level)SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)SqlSession openSession(ExecutorType execType)SqlSession openSession(ExecutorType execType, boolean autoCommit)SqlSession openSession(ExecutorType execType, Connection connection)Configuration getConfiguration();

方法说明:
默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:


1)事件作用域将会开启(也就是不主动提交)。


2)TransactionIsolationLevel 表现事件隔离级别,它对应着 JDBC 的五个事件隔离级别。


3)ExecutorType 枚举类型定义了三个值:


2.2.2 SqlSessionFactory 生命周期


SQLSessionFactory 应该以单例形式在应用的运行期间一直存在。


2.3. SqlSession


2.3.1 SqlSession 职责


MyBatis 的紧张 Java 接口就是 SqlSession。它包含了所有执行语句,获取映射器和管理事件等方法。详细内容可以参考:「 MyBatis 官方文档之 SqlSessions 」 。


SQLSession 类的方法可按照下图举行大致分类:



                               
登录/注册后可看大图



2.3.2 SqlSession 生命周期


SqlSessions 是由 SqlSessionFactory 实例创建的;而 SqlSessionFactory
是由 SqlSessionFactoryBuilder 创建的。


留意:当 MyBatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,SqlSessions 将被依赖注入框架所创建,
以是你不必要使用 SqlSessionFactoryBuilder 或者 SqlSessionFactory。


每个线程都应该有它自己的 SqlSession 实例。


SqlSession 的实例不是线程安全的,因此是不能被共享的,以是它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,乃至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,好比 Servlet 框架中的 HttpSession。正确在 Web 中使用 SqlSession 的场景是:每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个相应,就关闭它。


编程模式:
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码}

2.4. 映射器


2.4.1 映射器职责


映射器是一些由用户创建的、绑定 SQL 语句的接口。


SqlSession 中的 insert、update、delete 和 select 方法都很强大,但也有些繁琐。更通用的方式是使用映射器类来执行映射语句。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。


MyBatis 将配置文件中的每一个  节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟  节点中的  节点相对应,即  节点的 id 值为 Mapper 接口中的方法名称,parameterType 值表现 Mapper 对应方法的入参类型,而 resultMap 值则对应了 Mapper 接口表现的返回值类型或者返回结果集的元素类型。


MyBatis 会根据相应的接口声明的方法信息,通过动态署理机制生成一个 Mapper 实例;MyBatis 会根据这个方法的方法名和参数类型,确定 Statement id,然后和 SqlSession 举行映射,底层还是通过 SqlSession 完成和数据库的交互。


下面的示例展示了一些方法署名以及它们是如何映射到 SqlSession 上的。



                               
登录/注册后可看大图



留意:


2.4.2 映射器生命周期


映射器接口的实例是从 SqlSession 中得到的。因此从技能层面讲,任何映射器实例的最大作用域是和请求它们的 SqlSession 雷同的。只管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可抛弃。


编程模式:
try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); // 你的应用逻辑代码}

映射器注解
MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。MyBatis 3 以后,支持注解配置。注解配置基于配置 API;而配置 API 基于 XML 配置。


MyBatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解。
详细内容请参考:MyBatis 官方文档之 sqlSessions,其中枚举了 MyBatis 支持的注解清单,以及基本用法。


三、 MyBatis 的架构


从 MyBatis 代码实现的角度来看,MyBatis 的紧张组件有以下几个:




这些组件的架构层次如下:



                               
登录/注册后可看大图



3.1. 配置层


配置层决定了 MyBatis 的工作方式。


MyBatis 提供了两种配置方式:
SqlSessionFactoryBuilder 会根据配置创建 SqlSessionFactory ;SqlSessionFactory 负责创建 SqlSessions 。


3.2. 接口层


接口层负责和数据库交互的方式。MyBatis 和数据库的交互有两种方式:


1)使用 SqlSession:SqlSession 封装了所有执行语句,获取映射器和管理事件的方法。


2)使用 Mapper 接口:MyBatis 会根据相应的接口声明的方法信息,通过动态署理机制生成一个 Mapper 实例;MyBatis 会根据这个方法的方法名和参数类型,确定 Statement Id,然后和 SqlSession 举行映射,底层还是通过 SqlSession 完成和数据库的交互。


3.3. 数据处理层


数据处理层可以说是 MyBatis 的焦点,从大的方面上讲,它要完成两个功能:


1)根据传参 Statement 和参数构建动态 SQL 语句


2)执行 SQL 语句以及处理相应结果集 ResultSet


3.4. 框架支撑层


1) 事件管理机制 - MyBatis 将事件抽象成了 Transaction 接口。MyBatis 的事件管理分为两种形式:


2) 连接池管理


3) SQL 语句的配置 - 支持两种方式:


4) 缓存机制 - MyBatis 采用两级缓存结构;




                               
登录/注册后可看大图



四、SqlSession 内部工作机制


从前文,我们已经了解了,MyBatis 封装了对数据库的访问,把对数据库的会话和事件控制放到了 SqlSession 对象中。那么具体是如何工作的呢?接下来,我们通过源码解读来举行分析。



                               
登录/注册后可看大图



SqlSession 对于 insert、update、delete、select 的内部处理机制基本上大同小异。以是,接下来,我会以一次完整的 select 查询流程为例讲解 SqlSession 内部的工作机制。信赖读者如果理解了 select 的处理流程,对于其他 CRUD 操纵也能做到一通百通。


4.1 SqlSession 子组件


前面的内容已经介绍了:SqlSession 是 MyBatis 的顶层接口,它提供了所有执行语句,获取映射器和管理事件等方法。


现实上,SqlSession 是通过聚合多个子组件,让每个子组件负责各自功能的方式,实现了任务的下发。


在了解各个子组件工作机制前,先让我们简单认识一下 SqlSession 的焦点子组件。


4.1.1 Executor


Executor 即执行器,它负责生成动态 SQL 以及管理缓存。



                               
登录/注册后可看大图





4.1.2 StatementHandler


StatementHandler 对象负责设置 Statement 对象中的查询参数、处理 JDBC 返回的 resultSet,将 resultSet 加工为 List 集合返回。


StatementHandler 的家族成员:



                               
登录/注册后可看大图





4.1.3 ParameterHandler


ParameterHandler 负责将传入的 Java 对象转换 JDBC 类型对象,并为 PreparedStatement 的动态 SQL 添补数值。


ParameterHandler 只有一个具体实现类,即 DefaultParameterHandler。


4.1.4 ResultSetHandler


ResultSetHandler 负责两件事:
ResultSetHandler 只有一个具体实现类,即 DefaultResultSetHandler。


4.1.5 TypeHandler


TypeHandler 负责将 Java 对象类型和 JDBC 类型举行相互转换。


4.2 SqlSession 和 Mapper


先往返忆一下 MyBatis 完整示例章节的 测试步伐部分的代码。


MyBatisDemo.java 文件中的代码片断:
// 2. 创建一个 SqlSession 实例,举行数据库操纵SqlSession sqlSession = factory.openSession();
// 3. Mapper 映射并执行Long params = 1L;List list = sqlSession.selectList("io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey", params);for (User user : list) { System.out.println("user name: " + user.getName());}

示例代码中,给 sqlSession 对象的通报一个配置的 Sql 语句的 Statement Id 和参数,然后返回结果io.github.dunwu.spring.orm.mapper.
UserMapper.selectByPrimaryKey 是配置在 UserMapper.xml 的 Statement ID,params 是 SQL 参数。


UserMapper.xml 文件中的代码片断:
<span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">  select id, name, age, address, email from user where id = #{id,jdbcType=BIGINT} </span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"select/span>

MyBatis 通过方法的全限定名,将 SqlSession 和 Mapper 相互映射起来。


4.3. SqlSession 和 Executor


org.apache.ibatis.session.defaults.DefaultSqlSession 中 selectList 方法的源码:
@Overridepublic  List selectList(String statement) { return this.selectList(statement, );}
@Overridepublic  List selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}
@Overridepublic  List selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 1. 根据 Statement Id,在配置对象 Configuration 中查找和配置文件相对应的 MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); // 2. 将 SQL 语句交由执行器 Executor 处理 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}

说明:
MyBatis 所有的配置信息都维持在 Configuration 对象之中。中维护了一个 Map 对象。其中,key 为 Mapper 方法的全限定名(对于本例而言,key 就是 io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey ),value 为 MappedStatement 对象。以是,传入 Statement Id 就可以从 Map 中找到对应的 MappedStatement。


MappedStatement 维护了一个 Mapper 方法的元数据信息,数据组织可以参考下面 debug 截图:



                               
登录/注册后可看大图



小结:通过 "SqlSession 和 Mapper" 以及 "SqlSession 和 Executor" 这两节,我们已经知道:SqlSession 的职能是:根据 Statement ID, 在 Configuration 中获取到对应的 MappedStatement 对象,然后调用 Executor 来执行具体的操纵。


4.4. Executor 工作流程


继续上一节的流程,SqlSession 将 SQL 语句交由执行器 Executor 处理。那又做了哪些事呢?


(1)执行器查询入口
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 1. 根据传参,动态生成必要执行的 SQL 语句,用 BoundSql 对象表现 BoundSql boundSql = ms.getBoundSql(parameter); // 2. 根据传参,创建一个缓存Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }

执行器查询入口紧张做两件事:


(2)执行器查询第二入口
@SuppressWarnings("unchecked") @Override public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 略 List list; try { queryStack++; list = resultHandler ==  ? (List) localCache.getObject(key) : ; // 3. 缓存中有值,则直接从缓存中取数据;否则,查询数据库 if (list != ) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } // 略 return list; }

现实查询方法紧张的职能是判断缓存 key 是否能命中缓存:


(3)查询数据库
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 4. 执行查询,获取 List 结果,并将查询的结果更新本地缓存中 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }

queryFromDatabase 方法的职责是调用 doQuery,向数据库发起查询,并将返回的结果更新到本地缓存。


(4)现实查询方法。SimpleExecutor 类的 doQuery()方法实现;
@Override public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = ; try { Configuration configuration = ms.getConfiguration(); // 5. 根据既有的参数,创建StatementHandler对象来执行查询操纵 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 6. 创建java.Sql.Statement对象,通报给StatementHandler对象 stmt = prepareStatement(handler, ms.getStatementLog()); // 7. 调用StatementHandler.query()方法,返回List结果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }

上述的 Executor.query()方法几经转折,最后会创建一个 StatementHandler 对象,然后将须要的参数通报给 StatementHandler,使用 StatementHandler 来完成对数据库的查询,最终返回 List 结果集。从上面的代码中我们可以看出,Executor 的功能和作用是:




prepareStatement() 方法的实现:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); //对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数 handler.parameterize(stmt); return stmt; }

对于 JDBC 的 PreparedStatement 类型的对象,创建的过程中,我们使用的是 SQL 语句字符串会包含多少个占位符,我们其后再对占位符举行设值。


4.5. StatementHandler 工作流程


StatementHandler 有一个子类 RoutingStatementHandler,它负责署理其他 StatementHandler 子类的工作。


它会根据配置的 Statement 类型,选择实例化相应的 StatementHandler,然后由其署理对象完成工作。


【源码】RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }
}

【源码】RoutingStatementHandler 的
parameterize 方法源码
【源码】PreparedStatementHandler 的
parameterize 方法源码


StatementHandler使用ParameterHandler对象来完成对Statement 的赋值。
@Overridepublic void parameterize(Statement statement) throws SQLException { // 使用 ParameterHandler 对象来完成对 Statement 的设值 parameterHandler.setParameters((PreparedStatement) statement);}

【源码】StatementHandler 的 query 方法源码


StatementHandler 使用 ResultSetHandler 对象来完成对 ResultSet 的处理。
@Overridepublic  List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); // 使用ResultHandler来处理ResultSet return resultSetHandler.handleResultSets(ps);}

4.6. ParameterHandler 工作流程


【源码】DefaultParameterHandler 的
setParameters 方法
@Override public void setParameters(PreparedStatement ps) { // parameterMappings 是对占位符 #{} 对应参数的封装 List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != ) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); // 不处理存储过程中的参数 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params // 获取对应的现实数值 value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == ) { value = ; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { // 获取对象中相应的属性或查找 Map 对象中的值 MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); }
TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value ==  && jdbcType == ) { jdbcType = configuration.getJdbcTypeFor(); } try { // 通过 TypeHandler 将 Java 对象参数转为 JDBC 类型的参数 // 然后,将数值动态绑定到 PreparedStaement 中 typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }

4.7. ResultSetHandler 工作流程


ResultSetHandler 的实现可以概括为:将 Statement 执行后的结果集,按照 Mapper 文件中配置的 ResultType 或 ResultMap 来转换成对应的 JavaBean 对象,最后将结果返回。


【源码】DefaultResultSetHandler 的 handleResultSets 方法。handleResultSets 方法是 DefaultResultSetHandler 的最关键方法。实在现如下:
@Overridepublic List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List multipleResults = new ArrayList();
int resultSetCount = 0; // 第一个结果集 ResultSetWrapper rsw = getFirstResultSet(stmt); List resultMaps = mappedStatement.getResultMaps(); // 判断结果集的数量 int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 遍历处理结果集 while (rsw !=  && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, ); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }
String[] resultSets = mappedStatement.getResultSets(); if (resultSets != ) { while (rsw !=  && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != ) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, , parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } }
return collapseSingleResultList(multipleResults);}

五、参考资料


官方


扩展插件


文章





技能原创及架构实践文章,接待通过公众号菜单「联系我们」举行投稿。




高可用架构改变互联网的构建方式
作者: Marianas    时间: 2021-9-3 14:02
转发了
作者: 卧室以外都沉没    时间: 2021-9-3 14:53
转发了
作者: 风姿卓越鲸鱼dP    时间: 2021-9-3 19:01
转发了
作者: sense1229    时间: 2021-9-3 21:20
转发了
作者: 程序猿的进阶之路    时间: 2021-9-3 21:37
转发了
作者: 顺续    时间: 2021-9-3 23:51
转发了
作者: 煜灵子    时间: 2021-9-4 00:05
转发了
作者: 嘟嘟嘟成    时间: 2021-9-4 00:47
转发了
作者: 温迪军    时间: 2021-9-4 03:21
转发了
作者: 漫游的羊    时间: 2021-9-4 06:17
转发了
作者: 花山137815319    时间: 2021-9-4 06:25
转发了
作者: 花山137815319    时间: 2021-9-4 06:26
转发了
作者: 风中追风CC    时间: 2021-9-4 08:01
转发了
作者: 太阳黑子12626301    时间: 2021-9-4 11:28
转发了
作者: 非凡柑桔1Y    时间: 2021-9-5 11:48
转发了
作者: 追梦之博198    时间: 2021-9-5 21:44
转发了
作者: 陈海俊1    时间: 2021-9-5 23:44
转发了
作者: Java小生不才    时间: 2021-9-6 11:41
转发了
作者: UncleCat哥    时间: 2021-9-9 19:16
转发了
作者: UncleCat哥    时间: 2021-9-9 19:50
转发了




欢迎光临 创意电子 (https://wxcydz.cc/) Powered by Discuz! X3.4