《深入理解mybatis原理》 MyBatis的架構設計以及實例分析

   MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,並且討論MyBatis的幾個核心部件,然後結合一個select查詢實例,深入代碼,來探究MyBatis的實現。

一、MyBatis的框架設計

        注:上圖很大程度上參考了iteye 上的chenjc_it   所寫的博文原理分析之二:框架整體設計 中的MyBatis架構體圖,chenjc_it總結的非常好,贊一個!

1.接口層---和數據庫交互的方式

MyBatis和數據庫的交互有兩種方式:

a.使用傳統的MyBatis提供的API;

b. 使用Mapper接口

    1.1.使用傳統的MyBatis提供的API

      這是傳統的傳遞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 的核心,從大的方面上講,它要完成三個功能:

a. 通過傳入參數構建動態SQL語句;

b. SQL語句的執行以及封裝查詢結果集成List<E>

     2.1.參數映射和動態SQL語句生成

       動態語句生成可以說是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表,插入數據:      

  1.     
  2.   --創建一個員工基本信息表  
  3.    create  table "EMPLOYEES"(  
  4.        "EMPLOYEE_ID" NUMBER(6) not null,  
  5.       "FIRST_NAME" VARCHAR2(20),  
  6.       "LAST_NAME" VARCHAR2(25) not null,  
  7.       "EMAIL" VARCHAR2(25) not null unique,  
  8.       "SALARY" NUMBER(8,2),  
  9.        constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")  
  10.    );  
  11.    comment on table EMPLOYEES is '員工信息表';  
  12.    comment on column EMPLOYEES.EMPLOYEE_ID is '員工id';  
  13.    comment on column EMPLOYEES.FIRST_NAME is 'first name';  
  14.    comment on column EMPLOYEES.LAST_NAME is 'last name';  
  15.    comment on column EMPLOYEES.EMAIL is 'email address';  
  16.    comment on column EMPLOYEES.SALARY is 'salary';  
  17.      
  18.    --添加數據  
  19. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  20. values (100, 'Steven''King''SKING', 24000.00);  
  21.   
  22. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  23. values (101, 'Neena''Kochhar''NKOCHHAR', 17000.00);  
  24.   
  25. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  26. values (102, 'Lex''De Haan''LDEHAAN', 17000.00);  
  27.   
  28. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  29. values (103, 'Alexander''Hunold''AHUNOLD', 9000.00);  
  30.   
  31. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  32. values (104, 'Bruce''Ernst''BERNST', 6000.00);  
  33.   
  34. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  35. values (105, 'David''Austin''DAUSTIN', 4800.00);  
  36.   
  37. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  38. values (106, 'Valli''Pataballa''VPATABAL', 4800.00);  
  39.   
  40. insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)  
  41. values (107, 'Diana''Lorentz''DLORENTZ', 4200.00);      
  

  2. 配置Mybatis的配置文件,命名爲mybatisConfig.xml:

[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-config.dtd">  
  4. <configuration>  
  5.   <environments default="development">  
  6.     <environment id="development">  
  7.       <transactionManager type="JDBC" />  
  8.       <dataSource type="POOLED">  
  9.      <property name="driver" value="oracle.jdbc.driver.OracleDriver" />    
  10.          <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />    
  11.          <property name="username" value="louis" />    
  12.          <property name="password" value="123456" />  
  13.       </dataSource>  
  14.     </environment>  
  15.   </environments>  
  16.     <mappers>  
  17.        <mapper  resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>  
  18.     </mappers>  
  19. </configuration>  

3.     創建Employee實體Bean 以及配置Mapper配置文件

[java] view plain copy
 print?
  1. package com.louis.mybatis.model;  
  2.   
  3. import java.math.BigDecimal;  
  4.   
  5. public class Employee {  
  6.     private Integer employeeId;  
  7.   
  8.     private String firstName;  
  9.   
  10.     private String lastName;  
  11.   
  12.     private String email;  
  13.   
  14.     private BigDecimal salary;  
  15.   
  16.     public Integer getEmployeeId() {  
  17.         return employeeId;  
  18.     }  
  19.   
  20.     public void setEmployeeId(Integer employeeId) {  
  21.         this.employeeId = employeeId;  
  22.     }  
  23.   
  24.     public String getFirstName() {  
  25.         return firstName;  
  26.     }  
  27.   
  28.     public void setFirstName(String firstName) {  
  29.         this.firstName = firstName;  
  30.     }  
  31.   
  32.     public String getLastName() {  
  33.         return lastName;  
  34.     }  
  35.   
  36.     public void setLastName(String lastName) {  
  37.         this.lastName = lastName;  
  38.     }  
  39.   
  40.     public String getEmail() {  
  41.         return email;  
  42.     }  
  43.   
  44.     public void setEmail(String email) {  
  45.         this.email = email;  
  46.     }  
  47.   
  48.     public BigDecimal getSalary() {  
  49.         return salary;  
  50.     }  
  51.   
  52.     public void setSalary(BigDecimal salary) {  
  53.         this.salary = salary;  
  54.     }  
  55. }  

[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  
  3. <mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >  
  4.   
  5.   <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >  
  6.     <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />  
  7.     <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />  
  8.     <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />  
  9.     <result column="EMAIL" property="email" jdbcType="VARCHAR" />  
  10.     <result column="SALARY" property="salary" jdbcType="DECIMAL" />  
  11.   </resultMap>  
  12.     
  13.   <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >  
  14.     select   
  15.         EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY  
  16.         from LOUIS.EMPLOYEES  
  17.         where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}  
  18.   </select>  
  19. </mapper>  

4. 創建eclipse 或者myeclipse 的maven項目,maven配置如下:

[html] view plain copy
 print?
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  3.   <modelVersion>4.0.0</modelVersion>  
  4.   
  5.   <groupId>batis</groupId>  
  6.   <artifactId>batis</artifactId>  
  7.   <version>0.0.1-SNAPSHOT</version>  
  8.   <packaging>jar</packaging>  
  9.   
  10.   <name>batis</name>  
  11.   <url>http://maven.apache.org</url>  
  12.   
  13.   <properties>  
  14.     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  15.   </properties>  
  16.   
  17.   <dependencies>  
  18.     <dependency>  
  19.       <groupId>junit</groupId>  
  20.       <artifactId>junit</artifactId>  
  21.       <version>3.8.1</version>  
  22.       <scope>test</scope>  
  23.     </dependency>  
  24.   
  25.     <dependency>  
  26.             <groupId>org.mybatis</groupId>  
  27.             <artifactId>mybatis</artifactId>  
  28.             <version>3.2.7</version>  
  29.     </dependency>  
  30.       
  31.     <dependency>  
  32.         <groupId>com.oracle</groupId>  
  33.         <artifactId>ojdbc14</artifactId>  
  34.         <version>10.2.0.4.0</version>  
  35.     </dependency>  
  36.       
  37.   </dependencies>  
  38. </project>  

 5. 客戶端代碼:

[java] view plain copy
 print?
  1. package com.louis.mybatis.test;  
  2.   
  3. import java.io.InputStream;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7.   
  8. import org.apache.ibatis.io.Resources;  
  9. import org.apache.ibatis.session.SqlSession;  
  10. import org.apache.ibatis.session.SqlSessionFactory;  
  11. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  12.   
  13. import com.louis.mybatis.model.Employee;  
  14.   
  15. /** 
  16.  * SqlSession 簡單查詢演示類 
  17.  * @author louluan 
  18.  */  
  19. public class SelectDemo {  
  20.   
  21.     public static void main(String[] args) throws Exception {  
  22.         /* 
  23.          * 1.加載mybatis的配置文件,初始化mybatis,創建出SqlSessionFactory,是創建SqlSession的工廠 
  24.          * 這裏只是爲了演示的需要,SqlSessionFactory臨時創建出來,在實際的使用中,SqlSessionFactory只需要創建一次,當作單例來使用 
  25.          */  
  26.         InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");  
  27.         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();  
  28.         SqlSessionFactory factory = builder.build(inputStream);  
  29.           
  30.         //2. 從SqlSession工廠 SqlSessionFactory中創建一個SqlSession,進行數據庫操作  
  31.         SqlSession sqlSession = factory.openSession();  
  32.       
  33.         //3.使用SqlSession查詢  
  34.         Map<String,Object> params = new HashMap<String,Object>();  
  35.           
  36.         params.put("min_salary",10000);  
  37.         //a.查詢工資低於10000的員工  
  38.         List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);  
  39.         //b.未傳最低工資,查所有員工  
  40.         List<Employee> result1 = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");  
  41.         System.out.println("薪資低於10000的員工數:"+result.size());  
  42.         //~output :   查詢到的數據總數:5    
  43.         System.out.println("所有員工數: "+result1.size());  
  44.         //~output :  所有員工數: 8  
  45.     }  
  46.   
  47. }  


二、SqlSession 的工作過程分析:

 1. 開啓一個數據庫訪問會話---創建SqlSession對象:

[java] view plain copy
 print?
  1. SqlSession sqlSession = factory.openSession();  

           MyBatis封裝了對數據庫的訪問,把對數據庫的會話和事務控制放到了SqlSession對象中。

      

2. 爲SqlSession傳遞一個配置的Sql語句 的Statement Id和參數,然後返回結果:

[java] view plain copy
 print?
  1. 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()方法的定義: 

[java] view plain copy
 print?
  1. public <E> List<E> selectList(String statement, Object parameter) {  
  2.   return this.selectList(statement, parameter, RowBounds.DEFAULT);  
  3. }  
  4.   
  5. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
  6.   try {  
  7.     //1.根據Statement Id,在mybatis 配置對象Configuration中查找和配置文件相對應的MappedStatement      
  8.     MappedStatement ms = configuration.getMappedStatement(statement);  
  9.     //2. 將查詢任務委託給MyBatis 的執行器 Executor  
  10.     List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
  11.     return result;  
  12.   } catch (Exception e) {  
  13.     throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
  14.   } finally {  
  15.     ErrorContext.instance().reset();  
  16.   }  
  17. }  

MyBatis在初始化的時候,會將MyBatis的配置信息全部加載到內存中,使用org.apache.ibatis.session.Configuration實例來維護。使用者可以使用sqlSession.getConfiguration()方法來獲取。MyBatis的配置文件中配置信息的組織格式和內存中對象的組織格式幾乎完全對應的。上述例子中的

[html] view plain copy
 print?
  1. <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
  2.   select   
  3.     EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY  
  4.     from LOUIS.EMPLOYEES  
  5.     <if test="min_salary != null">  
  6.         where SALARY < #{min_salary,jdbcType=DECIMAL}  
  7.     </if>  
  8. </select>  
加載到內存中會生成一個對應的MappedStatement對象,然後會以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" ,valueMappedStatement對象的形式維護到Configuration的一個Map中。當以後需要使用的時候,只需要通過Id值來獲取就可以了。

從上述的代碼中我們可以看到SqlSession的職能是:

SqlSession根據Statement ID, 在mybatis配置對象Configuration中獲取到對應的MappedStatement對象,然後調用mybatis執行器來執行具體的操作。


3.MyBatis執行器Executor根據SqlSession傳遞的參數執行query()方法(由於代碼過長,讀者只需閱讀我註釋的地方即可):

[java] view plain copy
 print?
  1. /** 
  2. * BaseExecutor 類部分代碼 
  3. * 
  4. */  
  5. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  6.         
  7.     // 1.根據具體傳入的參數,動態地生成需要執行的SQL語句,用BoundSql對象表示    
  8.     BoundSql boundSql = ms.getBoundSql(parameter);  
  9.     // 2.爲當前的查詢創建一個緩存Key  
  10.     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);  
  11.     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);  
  12.  }  
  13.   
  14.   @SuppressWarnings("unchecked")  
  15.   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
  16.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
  17.     if (closed) throw new ExecutorException("Executor was closed.");  
  18.     if (queryStack == 0 && ms.isFlushCacheRequired()) {  
  19.       clearLocalCache();  
  20.     }  
  21.     List<E> list;  
  22.     try {  
  23.       queryStack++;  
  24.       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;  
  25.       if (list != null) {  
  26.         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);  
  27.       } else {  
  28.         // 3.緩存中沒有值,直接從數據庫中讀取數據    
  29.         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  
  30.       }  
  31.     } finally {  
  32.       queryStack--;  
  33.     }  
  34.     if (queryStack == 0) {  
  35.       for (DeferredLoad deferredLoad : deferredLoads) {  
  36.         deferredLoad.load();  
  37.       }  
  38.       deferredLoads.clear(); // issue #601  
  39.       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {  
  40.         clearLocalCache(); // issue #482  
  41.       }  
  42.     }  
  43.     return list;  
  44.   }  
  45.  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
  46.     List<E> list;  
  47.     localCache.putObject(key, EXECUTION_PLACEHOLDER);  
  48.     try {  
  49.           
  50.       //4. 執行查詢,返回List 結果,然後    將查詢的結果放入緩存之中  
  51.       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);  
  52.     } finally {  
  53.       localCache.removeObject(key);  
  54.     }  
  55.     localCache.putObject(key, list);  
  56.     if (ms.getStatementType() == StatementType.CALLABLE) {  
  57.       localOutputParameterCache.putObject(key, parameter);  
  58.     }  
  59.     return list;  
  60.   }  

[java] view plain copy
 print?
  1. /** 
  2. * 
  3. *SimpleExecutor類的doQuery()方法實現 
  4. * 
  5. */  
  6.   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {  
  7.     Statement stmt = null;  
  8.     try {  
  9.       Configuration configuration = ms.getConfiguration();  
  10.       //5. 根據既有的參數,創建StatementHandler對象來執行查詢操作  
  11.       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
  12.       //6. 創建java.Sql.Statement對象,傳遞給StatementHandler對象  
  13.       stmt = prepareStatement(handler, ms.getStatementLog());  
  14.       //7. 調用StatementHandler.query()方法,返回List結果集  
  15.       return handler.<E>query(stmt, resultHandler);  
  16.     } finally {  
  17.       closeStatement(stmt);  
  18.     }  
  19.   }  

上述的Executor.query()方法幾經轉折,最後會創建一個StatementHandler對象,然後將必要的參數傳遞給StatementHandler,使用StatementHandler來完成對數據庫的查詢,最終返回List結果集。

從上面的代碼中我們可以看出,Executor的功能和作用是:

(1、根據傳遞的參數,完成SQL語句的動態解析,生成BoundSql對象,供StatementHandler使用;

(2、爲查詢創建緩存,以提高性能(具體它的緩存機制不是本文的重點,我會單獨拿出來跟大家探討,感興趣的讀者可以關注我的其他博文);

(3、創建JDBCStatement連接對象,傳遞給StatementHandler對象,返回List查詢結果。

4. StatementHandler對象負責設置Statement對象中的查詢參數、處理JDBC返回的resultSet,將resultSet加工爲List 集合返回:

      接着上面的Executor第六步,看一下:prepareStatement() 方法的實現:

[java] view plain copy
 print?
  1. /** 
  2. * 
  3. *SimpleExecutor類的doQuery()方法實現 
  4. * 
  5. */  
  6. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = nulltry { 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); } }  
  7.   
  8.   private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
  9.     Statement stmt;  
  10.     Connection connection = getConnection(statementLog);  
  11.     stmt = handler.prepare(connection);  
  12.     //對創建的Statement對象設置參數,即設置SQL 語句中 ? 設置爲指定的參數  
  13.     handler.parameterize(stmt);  
  14.     return stmt;  
  15.   }  

      以上我們可以總結StatementHandler對象主要完成兩個工作:

         (1. 對於JDBCPreparedStatement類型的對象,創建的過程中,我們使用的是SQL語句字符串會包含 若干個? 佔位符,我們其後再對佔位符進行設值。

StatementHandler通過parameterize(statement)方法對Statement進行設值;       

         (2.StatementHandler通過List<E> query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement對象返回的resultSet封裝成List


5.   StatementHandler 的parameterize(statement) 方法的實現:

[java] view plain copy
 print?
  1. /** 
  2. *   StatementHandler 類的parameterize(statement) 方法實現  
  3. */  
  4. public void parameterize(Statement statement) throws SQLException {  
  5.     //使用ParameterHandler對象來完成對Statement的設值    
  6.     parameterHandler.setParameters((PreparedStatement) statement);  
  7.   }  

[java] view plain copy
 print?
  1. /** 
  2.  *  
  3.  *ParameterHandler類的setParameters(PreparedStatement ps) 實現 
  4.  * 對某一個Statement進行設置參數 
  5.  */  
  6. public void setParameters(PreparedStatement ps) throws SQLException {  
  7.   ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());  
  8.   List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
  9.   if (parameterMappings != null) {  
  10.     for (int i = 0; i < parameterMappings.size(); i++) {  
  11.       ParameterMapping parameterMapping = parameterMappings.get(i);  
  12.       if (parameterMapping.getMode() != ParameterMode.OUT) {  
  13.         Object value;  
  14.         String propertyName = parameterMapping.getProperty();  
  15.         if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params  
  16.           value = boundSql.getAdditionalParameter(propertyName);  
  17.         } else if (parameterObject == null) {  
  18.           value = null;  
  19.         } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {  
  20.           value = parameterObject;  
  21.         } else {  
  22.           MetaObject metaObject = configuration.newMetaObject(parameterObject);  
  23.           value = metaObject.getValue(propertyName);  
  24.         }  
  25.           
  26.         // 每一個Mapping都有一個TypeHandler,根據TypeHandler來對preparedStatement進行設置參數  
  27.         TypeHandler typeHandler = parameterMapping.getTypeHandler();  
  28.         JdbcType jdbcType = parameterMapping.getJdbcType();  
  29.         if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();  
  30.         // 設置參數  
  31.         typeHandler.setParameter(ps, i + 1, value, jdbcType);  
  32.       }  
  33.     }  
  34.   }  
  35. }  

從上述的代碼可以看到,StatementHandler 的parameterize(Statement) 方法調用了 ParameterHandler的setParameters(statement) 方法,

ParameterHandler的setParameters(Statement)方法負責 根據我們輸入的參數,對statement對象的 ? 佔位符處進行賦值。


6.   StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實現:

[java] view plain copy
 print?
  1.  /** 
  2.   * PreParedStatement類的query方法實現 
  3.   */  
  4.  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  5. // 1.調用preparedStatemnt。execute()方法,然後將resultSet交給ResultSetHandler處理    
  6.    PreparedStatement ps = (PreparedStatement) statement;  
  7.    ps.execute();  
  8.    //2. 使用ResultHandler來處理ResultSet  
  9.    return resultSetHandler.<E> handleResultSets(ps);  
  10.  }  
[java] view plain copy
 print?
  1. /**   
  2. *ResultSetHandler類的handleResultSets()方法實現 
  3. * 
  4. */  
  5. public List<Object> handleResultSets(Statement stmt) throws SQLException {  
  6.     final List<Object> multipleResults = new ArrayList<Object>();  
  7.   
  8.     int resultSetCount = 0;  
  9.     ResultSetWrapper rsw = getFirstResultSet(stmt);  
  10.   
  11.     List<ResultMap> resultMaps = mappedStatement.getResultMaps();  
  12.     int resultMapCount = resultMaps.size();  
  13.     validateResultMapsCount(rsw, resultMapCount);  
  14.       
  15.     while (rsw != null && resultMapCount > resultSetCount) {  
  16.       ResultMap resultMap = resultMaps.get(resultSetCount);  
  17.         
  18.       //將resultSet  
  19.       handleResultSet(rsw, resultMap, multipleResults, null);  
  20.       rsw = getNextResultSet(stmt);  
  21.       cleanUpAfterHandlingResultSet();  
  22.       resultSetCount++;  
  23.     }  
  24.   
  25.     String[] resultSets = mappedStatement.getResulSets();  
  26.     if (resultSets != null) {  
  27.       while (rsw != null && resultSetCount < resultSets.length) {  
  28.         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);  
  29.         if (parentMapping != null) {  
  30.           String nestedResultMapId = parentMapping.getNestedResultMapId();  
  31.           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);  
  32.           handleResultSet(rsw, resultMap, null, parentMapping);  
  33.         }  
  34.         rsw = getNextResultSet(stmt);  
  35.         cleanUpAfterHandlingResultSet();  
  36.         resultSetCount++;  
  37.       }  
  38.     }  
  39.   
  40.     return collapseSingleResultList(multipleResults);  
  41.   }  

從上述代碼我們可以看出,StatementHandler List<E> query(Statement statement, ResultHandler resultHandler)方法的實現,是調用了ResultSetHandlerhandleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法會將Statement語句執行後生成的resultSet 結果集轉換成List<E> 結果集:

[java] view plain copy
 print?
  1. //  
  2. // DefaultResultSetHandler 類的handleResultSets(Statement stmt)實現   
  3. //HANDLE RESULT SETS  
  4. //  
  5.   
  6. public List<Object> handleResultSets(Statement stmt) throws SQLException {  
  7.   final List<Object> multipleResults = new ArrayList<Object>();  
  8.   
  9.   int resultSetCount = 0;  
  10.   ResultSetWrapper rsw = getFirstResultSet(stmt);  
  11.   
  12.   List<ResultMap> resultMaps = mappedStatement.getResultMaps();  
  13.   int resultMapCount = resultMaps.size();  
  14.   validateResultMapsCount(rsw, resultMapCount);  
  15.     
  16.   while (rsw != null && resultMapCount > resultSetCount) {  
  17.     ResultMap resultMap = resultMaps.get(resultSetCount);  
  18.       
  19.     //將resultSet  
  20.     handleResultSet(rsw, resultMap, multipleResults, null);  
  21.     rsw = getNextResultSet(stmt);  
  22.     cleanUpAfterHandlingResultSet();  
  23.     resultSetCount++;  
  24.   }  
  25.   
  26.   String[] resultSets = mappedStatement.getResulSets();  
  27.   if (resultSets != null) {  
  28.     while (rsw != null && resultSetCount < resultSets.length) {  
  29.       ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);  
  30.       if (parentMapping != null) {  
  31.         String nestedResultMapId = parentMapping.getNestedResultMapId();  
  32.         ResultMap resultMap = configuration.getResultMap(nestedResultMapId);  
  33.         handleResultSet(rsw, resultMap, null, parentMapping);  
  34.       }  
  35.       rsw = getNextResultSet(stmt);  
  36.       cleanUpAfterHandlingResultSet();  
  37.       resultSetCount++;  
  38.     }  
  39.   }  
  40.   
  41.   return collapseSingleResultList(multipleResults);  
  42. }  



由於上述的過程時序圖太過複雜,就不貼出來了,讀者可以下載MyBatis源碼, 使用Eclipse、Intellij IDEA、NetBeans 等IDE集成環境創建項目,Debug MyBatis源碼,一步步跟蹤MyBatis的實現,這樣對學習MyBatis框架很有幫助~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章