文章目錄
- 一、前言
- 二、MyBatis四層架構
- 2.1 介面層
- 2.1.1 MyBatis使用Statement ID與資料庫互動
- 2.1.2 Mybatis使用Mapper介面與資料庫互動
- 2.2 資料處理層
- 2.2.1 引數映射
- 2.2.2 動態SQL陳述句生成
- 2.2.3 SQL陳述句的執行
- 2.2.4 結果處理
- 2.3 框架支撐層
- 2.3.1 事務管理機制
- 2.3.2 連接池管理機制
- 2.3.3 快取機制
- 2.3.4 SQL陳述句的配置方式
- 2.4 引導層
- 三、MyBatis的主要構件及其相互關系
- 四、從MyBatis一次select 查詢陳述句來分析MyBatis的架構設計
- 4.1 從宏觀的角度看
- 4.1.1 資料庫資料準備
- 4.1.2 Mybatis組態檔
- 4.1.3 引數映射
- 4.1.4 相關依賴
- 4.1.5 客戶端代碼
- 4.2 從原始碼的角度看mybatis查詢
- 4.2.1 開啟一個資料庫訪問會話
- 4.2.2 SqlSession組件
- 4.2.3 Executor執行器
- 4.2.4 StatementHandler與Statement
- 4.2.4.1 設定引數
- 4.2.4.2 查詢
- 4.2.5 小結
- 五、尾聲
一、前言
MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實作卻比較簡單、優雅,本文主要講述MyBatis的架構設計思路,并且討論MyBatis的幾個核心部件,然后結合一個select查詢實體,深入代碼,來探究MyBatis的實作,
二、MyBatis四層架構
MyBatis從上到下分為介面層、資料處理層、框架支撐層、引導層,如下:

介面層作用:介面層用于接收請求,呼叫sql陳述句,
介面層詳細:Mybatis介面層提供兩種資料庫訪問方式,Mybatis Statement ID訪問資料庫和Mapper介面訪問資料庫,底層都是一樣的,都是新建SqlSession物件,并向其傳入statementId和引數,最后使用SqlSession調動sql陳述句;
資料處理層作用:處理資料,
資料處理包括四個步驟:
(1) 引數映射(查詢階段,java型別的資料轉換成jdbc型別的資料,通過 preparedStatement.setXXX() 來設值;回傳階段,resultset查詢結果集中的 jdbcType 型別的資料轉換成java型別資料);
(2) sql陳述句動態生成;
(3) sql陳述句執行;
(4) 回傳值處理(對于回傳的結果集,支持結果集關系一對多和多對一的轉換,并且有兩種支持方式,一種為 嵌套查詢陳述句的查詢,還有一種是 嵌套結果集的查詢,)
框架支撐層作用:在資料處理層下面,為資料處理提供技術支持,
技術性支持包括四個:快取、事務、資料源與連接池、兩種配置方式的支持(xml配置和注解+注解驅動),其中,快取包括一級快取和二級快取,用于select查詢加速,update/delete/insert快取即清空;事務保證一致性;連接池實作Collection連接復用;
引導層作用:位于最下面,用于引導整個mybatis啟動與運行,
引導層包括兩種方式:xml配置方式和Java api方式,
2.1 介面層
介面層提供MyBatis和資料庫的互動兩種方式:
a.使用傳統的MyBatis提供的API,即statementId;
b. 使用Mapper介面
2.1.1 MyBatis使用Statement ID與資料庫互動
MyBatis 提供了非常方便和簡單的API,供用戶實作對資料庫的增刪改查資料操作,以及對資料庫連接資訊和MyBatis 自身配置資訊的維護操作,如下:

如上圖所示,MyBatis使用API與資料庫互動,分為兩步驟:
(1)創建一個和資料庫打交道的SqlSession物件
(2)根據Statement Id 和引數傳遞給SqlSession物件,使用 SqlSession物件完成和資料庫的互動
SqlSession session = sqlSessionFactory.openSession(); // SqlSession物件
Blog blog =(Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id); // statement Id
上述使用MyBatis 的方法,是創建一個和資料庫打交道的SqlSession物件,然后根據Statement Id 和引數來操作資料庫,這種方式固然很簡單和實用,但是它不符合面向物件語言的概念和面向介面編程的編程習慣,由于面向介面的編程是面向物件的大趨勢,MyBatis 為了適應這一趨勢,增加了第二種使用MyBatis 支持Mapper介面呼叫方式,
2.1.2 Mybatis使用Mapper介面與資料庫互動
Mybatis使用Mapper介面與資料庫互動,符合面向物件面向介面的設計原則,步驟如下:
(1)MyBatis 將組態檔中的每一個<mapper> 節點抽象為一個 Mapper 介面;
(2)Mapper介面中宣告的方法和跟<mapper> 節點中的<select|update|delete|insert> 節點項對應;
(3)<select|update|delete|insert> 節點的id值為Mapper 介面中的方法名稱,parameterType 值表示Mapper 對應方法的入參型別,而resultMap 值則對應了Mapper 介面表示的回傳值型別或者回傳結果集的元素型別,

根據MyBatis 的配置規范配置好后,底層實際運行步驟是:
(1)實體化Mapper介面實作類:通過 SqlSession.getMapper(XXXMapper.class) 方法,MyBatis底層原始碼會根據相應的介面宣告的方法資訊,通過動態代理機制生成一個 Mapper 實體;
(2)方法名和引數串列作為方法簽名(方法的唯一標識),確定statement id,底層還是使用SqlSession呼叫sql陳述句:我們使用Mapper 介面的某一個方法時,MyBatis 會根據這個方法的方法名和引數型別,確定Statement Id,底層還是使用SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實作對資料庫的操作;
MyBatis 參考Mapper 介面這種呼叫方式兩個原因:
(1) 為了滿足面向介面編程的需要;
(2) 使得用戶在介面上可以使用注解來配置SQL陳述句,這樣就可以脫離XML組態檔,實作“0配置”,
小結:
Mybatis介面層提供兩個資料庫訪問方式,Mybatis Statement ID和Mapper介面,底層都是一樣的,使用mapper也是通過方法名和引數串列作為方法簽名,確定statement id,使用SqlSesson呼叫sql陳述句;
介面層在整個Mybatis四層第一層,其作用是:接收請求,開始呼叫sql陳述句,
2.2 資料處理層
資料處理層可以說是MyBatis 的核心,從大的方面上講,它要完成兩四個功能:
a. 通過傳入引數構建動態SQL陳述句;(引數映射+動態SQL陳述句生成)
b. SQL陳述句的執行以及封裝查詢結果集成List,(SQL執行+結果處理)
2.2.1 引數映射
引數映射指的是對于 java資料型別和jdbc資料型別之間的轉換:
這里有包括兩個程序:
第一,查詢階段,java型別的資料轉換成jdbc型別的資料,通過 preparedStatement.setXXX() 來設值;
第二,回傳階段,resultset查詢結果集中的 jdbcType 型別的資料轉換成java型別資料,
2.2.2 動態SQL陳述句生成
動態陳述句生成可以說是MyBatis框架非常優雅的一個設計,MyBatis 通過傳入的引數值,使用 Ognl 來動態地構造SQL陳述句,使得MyBatis 有很強的靈活性和擴展性,
2.2.3 SQL陳述句的執行
動態SQL陳述句生成之后,MyBatis 執行SQL陳述句,
2.2.4 結果處理
Mybatis執行完成SQL陳述句后,可能將回傳的結果集轉換成List 串列,
MyBatis 在對結果集的處理中,支持結果集關系一對多和多對一的轉換,并且有兩種支持方式,一種為 嵌套查詢陳述句的查詢,還有一種是 嵌套結果集的查詢,
面試官:談一談你對mybatis關聯查詢(Mapper介面的嵌套陳述句查詢和嵌套結果查詢)的理解?
2.3 框架支撐層
2.3.1 事務管理機制
事務管理機制對于ORM框架而言是不可缺少的一部分,事務管理機制的質量也是考量一個ORM框架是否優秀的一個標準,
2.3.2 連接池管理機制
由于創建一個資料庫連接所占用的資源比較大, 對于資料吞吐量大和訪問量非常大的應用而言,連接池的設計就顯得非常重要,
2.3.3 快取機制
為了提高資料利用率和減小服務器和資料庫的壓力,MyBatis 會對于一些查詢提供會話級別的資料快取,會將對某一次查詢,放置到SqlSession 中,在允許的時間間隔內,對于完全相同的查詢,MyBatis 會直接將快取結果回傳給用戶,而不用再到資料庫中查找,
2.3.4 SQL陳述句的配置方式
當介面層MyBatis使用Statement ID與資料庫時,框架支撐層只能使用XML配置方式;當介面層MyBatis使用Mapper介面與資料庫互動時,框架支撐層可以使用XML配置方式,也可以使用注解配置方式,推薦使用注解配置方式,符合面向物件的思想,后期引入通用mapper,單表查詢不再需要在mapper.xml中寫sql陳述句,
傳統的MyBatis 配置SQL 陳述句方式就是使用XML檔案進行配置的,但是這種方式不能很好地支持面向介面編程的理念,為了支持面向介面的編程,MyBatis 引入了Mapper介面的概念,面向介面的引入,對使用注解來配置SQL 陳述句成為可能,用戶只需要在介面上添加必要的注解即可(一般是就是@Mapper注解),不用再去配置XML檔案了,
Spring中,@Service @Controller注解 + @ComponentScan掃描注解,等效于 xml配置
Mybatis中,@Mapper注解 + @MapperScan掃描注解,等效于 xml配置
總之,注解+掃描 等效于 xml配置,
2.4 引導層
引導層是配置和啟動MyBatis 配置資訊的方式,
MyBatis 提供兩種方式來引導MyBatis :基于XML組態檔的方式和基于Java API 的方式,
三、MyBatis的主要構件及其相互關系
從MyBatis代碼實作的角度來看,MyBatis的主要的核心部件有以下幾個:
| 組件名稱 | 作用 |
|---|---|
| SqlSession(介面層) | 作為MyBatis作業的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能 |
| Executor(資料處理層-動態SQL陳述句生成) | MyBatis執行器,是MyBatis 調度的核心,負責SQL陳述句的生成和查詢快取的維護 |
| StatementHandler(資料處理層-動態SQL陳述句生成) | 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合, |
| ParameterHandler(資料處理層-引數映射) | 負責對用戶傳遞的引數轉換成JDBC Statement 所需要的引數 |
| ResultSetHandler(資料處理層-結果處理與映射) | 負責將JDBC回傳的ResultSet結果集物件轉換成List型別的集合 |
| TypeHandler(資料處理層-引數映射) | 負責java資料型別和jdbc資料型別之間的映射和轉換 |
| MappedStatement(介面層) | MappedStatement維護了一條<select|update|delete|insert>節點的封裝 |
| SqlSource(資料處理層-SQL決議) | 負責根據用戶傳遞的parameterObject,動態地生成SQL陳述句,將資訊封裝到BoundSql物件中,并回傳 |
| BoundSql(資料處理層-動態SQL陳述句生成) | 表示動態生成的SQL陳述句以及相應的引數資訊 |
| Configuration(全家配置) | MyBatis所有的配置資訊都維持在Configuration物件之中, |
注:這里只是列出了我個人認為屬于核心的部件,請讀者不要先入為主,認為MyBatis就只有這些部件哦!每個人對MyBatis的理解不同,分析出的結果自然會有所不同,歡迎讀者提出質疑和不同的意見,我們共同探討,它們的關系如下圖所示:

mybatis和jdbc是兩個不同的東西
(1) mybatis是服務端程式的一個ORM框架,物件關系映射框架,上圖中只解釋mybatis十個組件,不解釋jdbc的組件,
(2) jdbc是java database connection,java資料庫連接工具,只用于java陳述句,但是可以連接不同資料庫,一般是mysql,
mybatis在服務端程式的下面,jdbc在資料庫的上面,所以上圖中,mybatis在上面,jdbc在下面,
四、從MyBatis一次select 查詢陳述句來分析MyBatis的架構設計
4.1 從宏觀的角度看
4.1.1 資料庫資料準備
--創建一個員工基本資訊表
create table "EMPLOYEES"(
"EMPLOYEE_ID" NUMBER(6) not null,
"FIRST_NAME" VARCHAR2(20),
"LAST_NAME" VARCHAR2(25) not null,
"EMAIL" VARCHAR2(25) not null unique,
"SALARY" NUMBER(8,2),
constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
);
comment on table EMPLOYEES is '員工資訊表';
comment on column EMPLOYEES.EMPLOYEE_ID is '員工id';
comment on column EMPLOYEES.FIRST_NAME is 'first name';
comment on column EMPLOYEES.LAST_NAME is 'last name';
comment on column EMPLOYEES.EMAIL is 'email address';
comment on column EMPLOYEES.SALARY is 'salary';
--添加資料
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (100, 'Steven', 'King', 'SKING', 24000.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
4.1.2 Mybatis組態檔
組態檔一般命名為mybatisConfig.xml(名稱任意),內容如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="louis" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
</mappers>
</configuration>
mapper的識別兩種方式:
mybatisConfig.xml中配置 或者 @Mapper注解+@MapperScan注解
4.1.3 引數映射
POJO+XxxMapper.java+XxxMapper.xml
package com.louis.mybatis.model;
import java.math.BigDecimal;
public class Employee {
private Integer employeeId;
private String firstName;
private String lastName;
private String email;
private BigDecimal salary;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >
<resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
<id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
<result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
<result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
<result column="EMAIL" property="email" jdbcType="VARCHAR" />
<result column="SALARY" property="salary" jdbcType="DECIMAL" />
</resultMap>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
from LOUIS.EMPLOYEES
where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
</select>
</mapper>
XxxMapper.xml中用來寫sql陳述句,使用通用mapper之后,單表查詢不用在寫XxxMapper.xml中寫sql陳述句了,
4.1.4 相關依賴
pom.xml中,匯入mybatis和jdbc依賴,如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>batis</groupId>
<artifactId>batis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>batis</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.4.0</version>
</dependency>
</dependencies>
</project>
4.1.5 客戶端代碼
package com.louis.mybatis.test;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.louis.mybatis.model.Employee;
/**
* SqlSession 簡單查詢演示類
* @author louluan
*/
public class SelectDemo {
public static void main(String[] args) throws Exception {
/*
* 1.加載mybatis的組態檔,初始化mybatis,創建出SqlSessionFactory,是創建SqlSession的工廠
* 這里只是為了演示的需要,SqlSessionFactory臨時創建出來,在實際的使用中,SqlSessionFactory只需要創建一次,當作單例來使用
*/
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
//2. 從SqlSession工廠 SqlSessionFactory中創建一個SqlSession,進行資料庫操作
SqlSession sqlSession = factory.openSession();
//3.使用SqlSession查詢
Map<String,Object> params = new HashMap<String,Object>();
params.put("min_salary",10000);
//a.查詢工資低于10000的員工
List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
//b.未傳最低工資,查所有員工
List<Employee> result1 = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
System.out.println("薪資低于10000的員工數:"+result.size());
//~output : 查詢到的資料總數:5
System.out.println("所有員工數: "+result1.size());
//~output : 所有員工數: 8
}
}
從4.1.1到4.1.5,表設計、mybatisConfig.xml(mapper的兩種方式,xml中配置或者@Mapper+@MapperScan)、POJO+XxxMapper.java+XxxMapper.xml(使用通用mapper,單表操作不用寫sql陳述句)、依賴匯入(mybatis依賴和jdbc依賴)、客戶端代碼(從組態檔得到InputStream,然后SqlSessionFactoryBuilder、然后SqlSessionFactory、然后SqlSession、傳入statementid和引數,執行sql陳述句,拿到結果集)
4.2 從原始碼的角度看mybatis查詢
這里介紹SqlSession的整個作業程序
4.2.1 開啟一個資料庫訪問會話
我們所說的開啟一個資料庫訪問會話,代碼實作就是創建SqlSession物件,如下:
SqlSession sqlSession = factory.openSession();
MyBatis封裝了對資料庫的訪問,把對資料庫的會話和事務控制放到了SqlSession物件中,

如上圖所示,SqlSession中兩個內容:事務控制+資料查詢,事務控制就是commit和rollback,資料查詢就是 select update insert delete,
4.2.2 SqlSession組件
SqlSession物件呼叫selectList方法,傳遞一個配置Sql陳述句的StatementId和引數,然后回傳結果,代碼如下:
List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params 是傳遞的查詢引數,
注意:這里是statementId就是 類名+方法名
讓我們來看一下sqlSession.selectList()方法的定義:
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.根據Statement Id,在mybatis配置物件Configuration中查找和組態檔相對應的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢任務委托給MyBatis 的執行器 Executor
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MyBatis在初始化的時候,會將MyBatis的配置資訊全部加載到記憶體中,使用org.apache.ibatis.session.Configuration實體來維護,使用者可以使用sqlSession.getConfiguration()方法來獲取,MyBatis的組態檔中配置資訊的組織格式和記憶體中物件的組織格式幾乎完全對應的,上述例子中的
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
select
EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
from LOUIS.EMPLOYEES
<if test="min_salary != null">
where SALARY < #{min_salary,jdbcType=DECIMAL}
</if>
</select>
加載到記憶體中會生成一個對應的MappedStatement物件,然后會以key=“com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary” ,value為MappedStatement物件的形式維護到Configuration的一個Map中,當以后需要使用的時候,只需要通過Id值來獲取就可以了,
從上述的代碼中我們可以看到SqlSession的職能是:SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然后呼叫mybatis執行器Executor來執行具體的操作,代碼如下:
//1.根據Statement Id,在mybatis 配置物件Configuration中查找和組態檔相對應的MappedStatement,Configuration里面是key,value,這里根據key得到value
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢任務委托給MyBatis 的執行器 Executor執行
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
SqlSession的使命完成,看Executor的表演,
4.2.3 Executor執行器
MyBatis執行器Executor根據SqlSession傳遞的引數執行query()方法,如下:
/**
* BaseExecutor 類部分代碼
*
*/
public <E> List<E> 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);
}
@SuppressWarnings("unchecked")
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 3.快取中沒有值,直接從資料庫中讀取資料
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> 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;
}
進一步深入,doQuery()方法是怎么執行的?
/**
*
*SimpleExecutor類的doQuery()方法實作
*
*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
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.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上述的Executor.query()方法幾經轉折,最后會創建一個StatementHandler物件,然后將必要的引數傳遞給StatementHandler,使用StatementHandler來完成對資料庫的查詢,最侄訓傳List結果集,
從上面的代碼中我們可以看出,Executor的功能和作用是:
(1、根據傳遞的引數,完成SQL陳述句的動態決議,生成BoundSql物件,供StatementHandler使用
// 1.根據具體傳入的引數,動態地生成需要執行的SQL陳述句,用BoundSql物件表示
BoundSql boundSql = ms.getBoundSql(parameter);
(2、為查詢創建快取,以提高性能
// 2.為當前的查詢創建一個快取Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 這個query()中同時用到了動態生成的boundSql和快取key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
(3、快取中沒有值,直接從資料庫中讀取資料
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 3.快取中沒有值,直接從資料庫中讀取資料
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
(4、 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中
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;
(5、具體doQuery()查詢中,創建JDBC的Statement連接物件,傳遞給StatementHandler物件,回傳List查詢結果
//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結果集
// StatementHandler作為呼叫者,Statement作為實參
return handler.<E>query(stmt, resultHandler);
好了,Executor的任務完成了,看StatementHandler和Statement的表演,核心就是:
// StatementHandler作為呼叫者,Statement作為實參
return handler.<E>query(stmt, resultHandler);
4.2.4 StatementHandler與Statement
StatementHandler物件負責設定Statement物件中的查詢引數、處理JDBC回傳的resultSet,將resultSet加工為List集合并回傳,doQuery() 方法的實作,如下(接著上面的Executor第六步和第七步):
/**
*
*SimpleExecutor類的doQuery()方法實作
*
*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 1.準備Statement物件,并設定Statement物件的引數
stmt = prepareStatement(handler, ms.getStatementLog());
// 2. StatementHandler執行query()方法,回傳List結果
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
//對創建的Statement物件設定引數,即設定SQL 陳述句中 ? 設定為指定的引數
handler.parameterize(stmt);
return stmt;
}
以上我們可以總結StatementHandler物件主要完成兩個作業:
(1. 設定引數:對于JDBC的PreparedStatement型別的物件,創建的程序中,我們使用的是SQL陳述句字串會包含 若干個? 占位符,我們其后再對占位符進行設值,StatementHandler通過parameterize(statement)方法對Statement進行設值;
(2. 查詢:StatementHandler通過List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement物件回傳的resultSet封裝成List,
StatementHandler 的兩個作用:設定引數、查詢,
4.2.4.1 設定引數
設定引數:StatementHandler 的parameterize(statement) 方法的實作
/**
* StatementHandler 類的parameterize(statement) 方法實作
*/
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler物件來完成對Statement的設值
parameterHandler.setParameters((PreparedStatement) statement);
}
/**
*
*ParameterHandler類的setParameters(PreparedStatement ps) 實作
* 對某一個Statement進行設定引數
*/
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
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 == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 每一個Mapping都有一個TypeHandler,根據TypeHandler來對preparedStatement進行設定引數
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
// 設定引數,這里將java型別變為jdbc型別
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
從上述的代碼可以看到,StatementHandler 的parameterize(Statement) 方法呼叫了 ParameterHandler的setParameters(statement) 方法,這個方法根據我們輸入的引數,對statement物件的 ? 占位符處進行賦值,如下:
typeHandler.setParameter(ps, i + 1, value, jdbcType);
解釋一下setParameter方法:根據boundSql動態Sql得到一個parameterMappings,然后遍歷這個list,對于每一個parameterMapping,從parameterMapping得到propertyName,從propertyName得到value,然后,從parameterMapping得到typeHandler,從parameterMapping得到jdbcType ,最后使用 typeHandler.setParameter(ps, i + 1, value, jdbcType);
4.2.4.2 查詢
查詢:StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實作
/**
* PreParedStatement類的query方法實作
*/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 1.呼叫preparedStatemnt.execute()方法,然后將resultSet交給ResultSetHandler處理
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//2. 使用ResultHandler來處理ResultSet
return resultSetHandler.<E> handleResultSets(ps);
}
StatementHandler將查詢的業務交給ResultSetHandler來完成,下面的主角成為ResultSetHandler了
/**
* DefaultResultSetHandler類的handleResultSets()方法實作
*
*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//將resultSet
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}


從上述代碼我們可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的實作,是呼叫了ResultSetHandler的handleResultSets(Statement) 方法,ResultSetHandler的handleResultSets(Statement) 方法會將Statement陳述句執行后生成的resultSet 結果集轉換成List 結果集,
4.2.5 小結
第一個主角SqlSession
SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然后呼叫mybatis執行器來執行具體的操作,
//1.根據Statement Id,在mybatis 配置物件Configuration中查找和組態檔相對應的MappedStatement,Configuration里面是key,value,這里根據key得到value
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢任務委托給MyBatis 的執行器 Executor執行
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
SqlSession的使命完成,看Executor的表演,
第二個主角Executor
Executor的功能和作用是:
(1、根據傳遞的引數,完成SQL陳述句的動態決議,生成BoundSql物件,供StatementHandler使用
// 1.根據具體傳入的引數,動態地生成需要執行的SQL陳述句,用BoundSql物件表示
BoundSql boundSql = ms.getBoundSql(parameter);
(2、為查詢創建快取,以提高性能
// 2.為當前的查詢創建一個快取Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 這個query()中同時用到了動態生成的boundSql和快取key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
(3、快取中沒有值,直接從資料庫中讀取資料
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 3.快取中沒有值,直接從資料庫中讀取資料
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
(4、 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中
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;
(5、具體doQuery()查詢中,創建JDBC的Statement連接物件,傳遞給StatementHandler物件,回傳List查詢結果
//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.<E>query(stmt, resultHandler);
第三個主角StatementHandler、第四個主角ParameterHandlder、第五個主角TypeHandler和第六個主角ResultSetHandler
以上我們可以總結StatementHandler物件主要完成兩個作業:
(1. 設定引數:對于JDBC的PreparedStatement型別的物件,創建的程序中,我們使用的是SQL陳述句字串會包含 若干個? 占位符,我們其后再對占位符進行設值,StatementHandler通過parameterize(statement)方法對Statement進行設值;
typeHandler.setParameter(ps, i + 1, value, jdbcType);
解釋一下setParameter方法:根據boundSql動態Sql得到一個parameterMappings,然后遍歷這個list,對于每一個parameterMapping,從parameterMapping得到propertyName,從propertyName 得到value,然后,從parameterMapping得到typeHandler,從parameterMapping得到jdbcType ,最后使用 typeHandler.setParameter(ps, i + 1, value, jdbcType);
(2.查詢:StatementHandler通過List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement物件回傳的resultSet封裝成List,
StatementHandler將查詢的業務交給ResultSetHandler來完成,對于ResultSetHandler,回傳的時候應該也是要用到TypeHandler類將jdbc型別轉換為java型別,但是沒找到,DefaultResultSetHandler類中搜索getTypeHandler()就好了,
五、尾聲
《沿著哈勃望遠,看清MyBatis整體架構》,完成了,
天天打碼,天天進步!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/306422.html
標籤:java
下一篇:1.序言—為什么建立自己的博客?
