《深入理解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框架是否優秀的一個標準,對於資料管理機制我已經在我的博文 中有非常詳細的討論,感興趣的讀者可以點選檢視。
3.2. 連線池管理機制
由於建立一個數據庫連線所佔用的資源比較大, 對於資料吞吐量大和訪問量非常大的應用而言,連線池的設計就顯得非常重要,對於連線池管理機制我已經在我的博文 中有非常詳細的討論,感興趣的讀者可以點選檢視。
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 的方式,讀者可以參考我的另一片博文:
二、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表,插入資料:
--建立一個員工基本資訊表 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);
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>
3. 建立Employee實體Bean 以及配置Mapper配置檔案
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配置如下:
<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>
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 } }
二、SqlSession 的工作過程分析:
1. 開啟一個數據庫訪問會話---建立SqlSession物件:
SqlSession sqlSession = factory.openSession();
MyBatis封裝了對資料庫的訪問,把對資料庫的會話和事務控制放到了SqlSession物件中。
2. 為SqlSession傳遞一個配置的Sql語句 的Statement Id和引數,然後返回結果:
上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params 是傳遞的查詢引數。List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
讓我們來看一下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的配置檔案中配置資訊的組織格式和記憶體中物件的組織格式幾乎完全對應的。上述例子中的載入到記憶體中會生成一個對應的MappedStatement物件,然後會以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" ,value為MappedStatement物件的形式維護到Configuration的一個Map中。當以後需要使用的時候,只需要通過Id值來獲取就可以了。<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>
從上述的程式碼中我們可以看到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 { 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; }
/** * *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使用;
(2、為查詢建立快取,以提高效能(具體它的快取機制不是本文的重點,我會單獨拿出來跟大家探討,感興趣的讀者可以關注我的其他博文);
(3、建立JDBC的Statement連線物件,傳遞給StatementHandler物件,返回List查詢結果。
4. StatementHandler物件負責設定Statement物件中的查詢引數、處理JDBC返回的resultSet,將resultSet加工為List 集合返回:
接著上面的Executor第六步,看一下:prepareStatement() 方法的實現:
/** * *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<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); }
/** *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); }
從上述程式碼我們可以看出,StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實現,是呼叫了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法會將Statement語句執行後生成的resultSet 結果集轉換成List<E> 結果集:
// // DefaultResultSetHandler 類的handleResultSets(Statement stmt)實現 //HANDLE RESULT SETS // 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框架很有幫助~
-----------------------------------------------------------------------------------------------------------------------------------------
本文源自 http://blog.csdn.net/luanlouis/,如需轉載,請註明出處,謝謝!