MyBatis是目前非常流行的ORM架構,它的功能很強大,然而其實作卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,并且讨論MyBatis的幾個核心部件,然後結合一個select查詢執行個體,深入代碼,來探究MyBatis的實作。
一、MyBatis的架構設計
注:上圖很大程度上參考了iteye 上的chenjc_it 所寫的博文原理分析之二:架構整體設計 中的MyBatis架構體圖,chenjc_it總結的非常好,贊一個!
1.接口層---和資料庫互動的方式
MyBatis和資料庫的互動有兩種方式:1.1.使用傳統的MyBatis提供的APIa.使用傳統的MyBatis提供的API;
b. 使用Mapper接口
這是傳統的傳遞Statement Id 和查詢參數給 SqlSession 對象,使用 SqlSession對象完成和資料庫的互動;MyBatis 提供了非常友善和簡單的API,供使用者實作對資料庫的增删改查資料操作,以及對資料庫連接配接資訊和MyBatis 自身配置資訊的維護操作。
上述使用MyBatis 的方法,是建立一個和資料庫打交道的SqlSession對象,然後根據Statement Id 和參數來操作資料庫,這種方式固然很簡單和實用,但是它不符合面向對象語言的概念和面向接口程式設計的程式設計習慣。由于面向接口的程式設計是面向對象的大趨勢,MyBatis 為了适應這一趨勢,增加了第二種使用MyBatis 支援接口(Interface)調用方式。1.2. 使用Mapper接口
MyBatis 将配置檔案中的每一個<mapper> 節點抽象為一個 Mapper 接口,而這個接口中聲明的方法和跟<mapper> 節點中的<select|update|delete|insert> 節點項對應,即<select|update|delete|insert> 節點的id值為Mapper 接口中的方法名稱,parameterType 值表示Mapper 對應方法的入參類型,而resultMap 值則對應了Mapper 接口表示的傳回值類型或者傳回結果集的元素類型。
根據MyBatis 的配置規範配置好後,通過SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 會根據相應的接口聲明的方法資訊,通過動态代理機制生成一個Mapper 執行個體,我們使用Mapper 接口的某一個方法時,MyBatis 會根據這個方法的方法名和參數類型,确定Statement Id,底層還是通過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實作對資料庫的操作,(至于這裡的動态機制是怎樣實作的,我将準備專門一片文章來讨論,敬請關注~)
MyBatis 引用Mapper 接口這種調用方式,純粹是為了滿足面向接口程式設計的需要。(其實還有一個原因是在于,面向接口的程式設計,使得使用者在接口上可以使用注解來配置SQL語句,這樣就可以脫離XML配置檔案,實作“0配置”)。
2.資料處理層
資料處理層可以說是MyBatis 的核心,從大的方面上講,它要完成三個功能:2.1.參數映射和動态SQL語句生成a. 通過傳入參數建構動态SQL語句;
b. SQL語句的執行以及封裝查詢結果內建List<E>
動态語句生成可以說是MyBatis架構非常優雅的一個設計,MyBatis 通過傳入的參數值,使用 Ognl 來動态地構造SQL語句,使得MyBatis 有很強的靈活性和擴充性。
參數映射指的是對于Java 資料類型和jdbc資料類型之間的轉換:這裡有包括兩個過程:查詢階段,我們要将java類型的資料,轉換成jdbc類型的資料,通過 preparedStatement.setXXX() 來設值;另一個就是對resultset查詢結果集的jdbcType 資料轉換成java 資料類型。
(至于具體的MyBatis是如何動态建構SQL語句的,我将準備專門一篇文章來讨論,敬請關注~)
2.2. SQL語句的執行以及封裝查詢結果內建List<E>
動态SQL語句生成之後,MyBatis 将執行SQL語句,并将可能傳回的結果集轉換成List<E> 清單。MyBatis 在對結果集的進行中,支援結果集關系一對多和多對一的轉換,并且有兩種支援方式,一種為嵌套查詢語句的查詢,還有一種是嵌套結果集的查詢。
3. 架構支撐層
3.1. 事務管理機制事務管理機制對于ORM架構而言是不可缺少的一部分,事務管理機制的品質也是考量一個ORM架構是否優秀的一個标準,對于資料管理機制我已經在我的博文《深入了解mybatis原理》 MyBatis事務管理機制 中有非常詳細的讨論,感興趣的讀者可以點選檢視。3.2. 連接配接池管理機制由于建立一個資料庫連接配接所占用的資源比較大, 對于資料吞吐量大和通路量非常大的應用而言,連接配接池的設計就顯得非常重要,對于連接配接池管理機制我已經在我的博文《深入了解mybatis原理》 Mybatis資料源與連接配接池 中有非常詳細的讨論,感興趣的讀者可以點選檢視。3.3. 緩存機制為了提高資料使用率和減小伺服器和資料庫的壓力,MyBatis 會對于一些查詢提供會話級别的資料緩存,會将對某一次查詢,放置到SqlSession中,在允許的時間間隔内,對于完全相同的查詢,MyBatis 會直接将緩存結果傳回給使用者,而不用再到資料庫中查找。(至于具體的MyBatis緩存機制,我将準備專門一篇文章來讨論,敬請關注~)3. 4. SQL語句的配置方式傳統的MyBatis 配置SQL 語句方式就是使用XML檔案進行配置的,但是這種方式不能很好地支援面向接口程式設計的理念,為了支援面向接口的程式設計,MyBatis 引入了Mapper接口的概念,面向接口的引入,對使用注解來配置SQL 語句成為可能,使用者隻需要在接口上添加必要的注解即可,不用再去配置XML檔案了,但是,目前的MyBatis 隻是對注解配置SQL 語句提供了有限的支援,某些進階功能還是要依賴XML配置檔案配置SQL 語句。
4 引導層
引導層是配置和啟動MyBatis 配置資訊的方式。MyBatis 提供兩種方式來引導MyBatis :基于XML配置檔案的方式和基于Java API 的方式,讀者可以參考我的另一片博文:Java Persistence with MyBatis 3(中文版) 第二章 引導MyBatis
二、MyBatis的主要構件及其互相關系
從MyBatis代碼實作的角度來看,MyBatis的主要的核心部件有以下幾個:
- SqlSession 作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增删改查功能
- Executor MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢緩存的維護
- StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定參數、将Statement結果集轉換成List集合。
- ParameterHandler 負責對使用者傳遞的參數轉換成JDBC Statement 所需要的參數,
- ResultSetHandler 負責将JDBC傳回的ResultSet結果集對象轉換成List類型的集合;
- TypeHandler 負責java資料類型和jdbc資料類型之間的映射和轉換
- MappedStatement MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
- SqlSource 負責根據使用者傳遞的parameterObject,動态地生成SQL語句,将資訊封裝到BoundSql對象中,并傳回
- BoundSql 表示動态生成的SQL語句以及相應的參數資訊
- Configuration MyBatis所有的配置資訊都維持在Configuration對象之中。
(注:這裡隻是列出了我個人認為屬于核心的部件,請讀者不要先入為主,認為MyBatis就隻有這些部件哦!每個人對MyBatis的了解不同,分析出的結果自然會有所不同,歡迎讀者提出質疑和不同的意見,我們共同探讨~)
它們的關系如下圖所示:
三、從MyBatis一次select 查詢語句來分析MyBatis的架構設計
一、資料準備(非常熟悉和應用過MyBatis 的讀者可以迅速浏覽此節即可)
1. 準備資料庫資料,建立EMPLOYEES表,插入資料:
[sql] view plain copy
print?
- --建立一個員工基本資訊表
- 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);
- values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
- values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
- values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
- values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
- values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
- values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
- values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
2. 配置Mybatis的配置檔案,命名為mybatisConfig.xml:
[html] view plain copy
- <?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>
3. 建立Employee實體Bean 以及配置Mapper配置檔案
[java] view plain copy
- 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>
4. 建立eclipse 或者myeclipse 的maven項目,maven配置如下:
5. 用戶端代碼:
- <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>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.2.7</version>
- <groupId>com.oracle</groupId>
- <artifactId>ojdbc14</artifactId>
- <version>10.2.0.4.0</version>
- </dependencies>
- </project>
- 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
二、SqlSession 的工作過程分析:
1. 開啟一個資料庫通路會話---建立SqlSession對象:
- SqlSession sqlSession = factory.openSession();
MyBatis封裝了對資料庫的通路,把對資料庫的會話和事務控制放到了SqlSession對象中。
2. 為SqlSession傳遞一個配置的Sql語句 的Statement Id和參數,然後傳回結果:
- List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params 是傳遞的查詢參數。
讓我們來看一下sqlSession.selectList()方法的定義:
MyBatis在初始化的時候,會将MyBatis的配置資訊全部加載到記憶體中,使用org.apache.ibatis.session.Configuration執行個體來維護。使用者可以使用sqlSession.getConfiguration()方法來擷取。MyBatis的配置檔案中配置資訊的組織格式和記憶體中對象的組織格式幾乎完全對應的。上述例子中的
- 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();
- }
- <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執行器來執行具體的操作。3.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 {
- localCache.putObject(key, EXECUTION_PLACEHOLDER);
- //4. 執行查詢,傳回List 結果,然後 将查詢的結果放入緩存之中
- list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
- localCache.removeObject(key);
- localCache.putObject(key, list);
- if (ms.getStatementType() == StatementType.CALLABLE) {
- localOutputParameterCache.putObject(key, parameter);
- *SimpleExecutor類的doQuery()方法實作
- public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
- Statement stmt = null;
- 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);
- closeStatement(stmt);
上述的Executor.query()方法幾經轉折,最後會建立一個StatementHandler對象,然後将必要的參數傳遞給StatementHandler,使用StatementHandler來完成對資料庫的查詢,最終傳回List結果集。
從上面的代碼中我們可以看出,Executor的功能和作用是:
(1、根據傳遞的參數,完成SQL語句的動态解析,生成BoundSql對象,供StatementHandler使用;
(2、為查詢建立緩存,以提高性能(具體它的緩存機制不是本文的重點,我會單獨拿出來跟大家探讨,感興趣的讀者可以關注我的其他博文);
(3、建立JDBC的Statement連接配接對象,傳遞給StatementHandler對象,傳回List查詢結果。
4. StatementHandler對象負責設定Statement對象中的查詢參數、處理JDBC傳回的resultSet,将resultSet加工為List 集合傳回:
接着上面的Executor第六步,看一下:prepareStatement() 方法的實作:
- 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<E> query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和将Statement對象傳回的resultSet封裝成List;
5. 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();
- // 設定參數
- typeHandler.setParameter(ps, i + 1, value, jdbcType);
從上述的代碼可以看到,StatementHandler 的parameterize(Statement) 方法調用了 ParameterHandler的setParameters(statement) 方法,
ParameterHandler的setParameters(Statement)方法負責 根據我們輸入的參數,對statement對象的 ? 占位符處進行指派。
6. 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 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實作,是調用了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法會将Statement語句執行後生成的resultSet 結果集轉換成List<E> 結果集:
- /**
- *ResultSetHandler類的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);
由于上述的過程時序圖太過複雜,就不貼出來了,讀者可以下載下傳MyBatis源碼, 使用Eclipse、Intellij IDEA、NetBeans 等IDE內建環境建立項目,Debug MyBatis源碼,一步步跟蹤MyBatis的實作,這樣對學習MyBatis架構很有幫助~
- //
- // DefaultResultSetHandler 類的handleResultSets(Statement stmt)實作
- //HANDLE RESULT SETS
- 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);
- return collapseSingleResultList(multipleResults);