MyBatis的深入原理分析之1-架構設計以及例項分析
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,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
- ExecutorMyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
- StatementHandler封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合。
- ParameterHandler負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數,
- ResultSetHandler負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合;
- TypeHandler負責java資料型別和jdbc資料型別之間的對映和轉換
- MappedStatementMappedStatement維護了一條<select|update|delete|insert>節點的封裝,
- SqlSource負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
- BoundSql表示動態生成的SQL語句以及相應的引數資訊
- ConfigurationMyBatis所有的配置資訊都維持在Configuration物件之中。
(注:這裡只是列出了我個人認為屬於核心的部件,請讀者不要先入為主,認為MyBatis就只有這些部件哦!每個人對MyBatis的理解不同,分析出的結果自然會有所不同,歡迎讀者提出質疑和不同的意見,我們共同探討~)
它們的關係如下圖所示:
三、從MyBatis一次select 查詢語句來分析MyBatis的架構設計
一、資料準備(非常熟悉和應用過MyBatis 的讀者可以迅速瀏覽此節即可)
1. 準備資料庫資料,建立EMPLOYEES表,插入資料:
2. 配置Mybatis的配置檔案,命名為teaboymybatis.xml:
3. 建立Employee實體Bean 以及配置Mapper配置檔案EmployeesMapper.xml
4. 建立eclipse 或者myeclipse 的maven專案,maven配置如下:
5. 客戶端程式碼:
package mybatisTest; 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.alibaba.fastjson.JSON; import com.teaboy.model.Employee; /** * * (查詢) * * <p> * 修改歷史: <br> * 修改日期 修改人員 版本 修改內容<br> * -------------------------------------------------<br> * 2016年9月26日 下午12:11:15 user 1.0 初始化建立<br> * </p> * * @author Peng.Li * @version 1.0 * @since JDK1.7 */ public class SelectDemo { public static void main(String[] args) throws Exception { //1.載入mybatis配置檔案,初始化mybatis,建立SqlSessionFactory的工廠 InputStream inputStream = Resources.getResourceAsStream("conf/core/teaboymybatis.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream); //2.從SqlSession工廠SqlSessionFactory中建立一個SqlSession,進行資料庫操作 SqlSession sqlSession = factory.openSession(); //3.使用sqlSession進行查詢 Map<String, Object>condMap = new HashMap<String, Object>(); condMap.put("min_salary", 10000); //selectList方法引數 statement == mapper (namespace.id) == Employee.selectByMinSalary List<Employee> elEmployees = sqlSession.selectList("Employee.selectByMinSalary", condMap); System.out.println(JSON.toJSONString(elEmployees)); } }
二、SqlSession 的工作過程分析:
1. 開啟一個數據庫訪問會話---建立SqlSession物件:
SqlSession sqlSession = factory.openSession();
DefaultSqlSessionFactory的openSession方法如下: public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
跟進去看DefaultSqlSessionFactory類中的openSessionFromDataSource方法:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //讀取配置檔案teaboymybatis.xml,通過configuration獲取xml配置檔案的環境資訊 final Environment environment = configuration.getEnvironment(); //根據環境資訊獲取環境資訊中的TransactionFactory,這裡teaboymybatis.xml配置裡面transactionManager type="JDBC",所以獲取到的TransactionFactory為JdbcTransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //通過TransactionFactory工廠根據PooledDataSource,隔離級別,建立一個Transaction事務,這裡的Transaction為JdbcTransaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //通過Configuration建立Executor final Executor executor = configuration.newExecutor(tx, execType, autoCommit); //建立DefaultSqlSession return new DefaultSqlSession(configuration, executor); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
跟進去看getTransactionFactoryFromEnvironment方法:獲取TransactionFactory
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
//這一步不會走 if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); }
//根據環境配置獲取到的TransactionFactory為JdbcTransactionFactory
return environment.getTransactionFactory();
}
跟進去看JdbcTransactionFactory類中的newTransaction方法:
package org.apache.ibatis.transaction.jdbc; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.session.TransactionIsolationLevel; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.TransactionFactory; /** * Creates {@link JdbcTransaction} instances. * * @see JdbcTransaction */ public class JdbcTransactionFactory implements TransactionFactory { public void setProperties(Properties props) { } public Transaction newTransaction(Connection conn) { return new JdbcTransaction(conn); }
//走的是這一步,根據資料來源,隔離級別,是否自動提交,得到一個JdbcTransaction,這個地方使用工廠方法設計模式
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit); } }
跟進去看Configuration裡面的newExecutor方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor;
//1.判斷執行器excutor的型別,如果配置裡面沒有配置的話,預設的是ExecutorType.SIMPLE if (ExecutorType.BATCH == executorType) {
//返回BatchExecutor的執行器 executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//返回reuseExecutor executor = new ReuseExecutor(this, transaction); } else {
//返回SimpleExecutor executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) {
//返回CathingExecutor executor = new CachingExecutor(executor, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
這裡走SimpleExecutor類,通過父類BaseExecutor的構造方法構造一個SimpleExecutor,原始碼如下:
/* * Copyright 2009-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.executor; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import java.util.List; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.logging.Log; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.transaction.Transaction; public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) {
//父類初始化SimpleExecutor執行器,根據Configuration和Transaction以及prepretualCathe等物件 super(configuration, transaction); } public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } 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(this, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { return Collections.emptyList(); } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); handler.parameterize(stmt); return stmt; } }
然後程式碼執行executor = new CachingExecutor(executor, autoCommit);方法,通過父類BaseExecutor的構造方法構造一個SimpleExecutor,原始碼如下:
/* * Copyright 2009-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.executor; import java.sql.SQLException; import java.util.List; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cache.TransactionalCacheManager; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.mapping.StatementType; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.transaction.Transaction; public class CachingExecutor implements Executor { private Executor delegate; private boolean autoCommit; // issue #573. No need to call commit() on autoCommit sessions private TransactionalCacheManager tcm = new TransactionalCacheManager(); private boolean dirty; public CachingExecutor(Executor delegate) { this(delegate, false); } //走這一步 public CachingExecutor(Executor delegate, boolean autoCommit) { this.delegate = delegate; this.autoCommit = autoCommit; } public Transaction getTransaction() { return delegate.getTransaction(); } public void close(boolean forceRollback) { try { //issue #499. Unresolved session handling //issue #573. Autocommit sessions should commit if (dirty && !autoCommit) { tcm.rollback(); } else { tcm.commit(); } } finally { delegate.close(forceRollback); } } public boolean isClosed() { return delegate.isClosed(); } public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); if (!dirty) {
//獲取讀寫鎖的讀鎖,ReadWriteLock的使用 cache.getReadWriteLock().readLock().lock(); try { @SuppressWarnings("unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList != null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } } List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks return list; } } return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public List<BatchResult> flushStatements() throws SQLException { return delegate.flushStatements(); } public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit(); dirty = false; } public void rollback(boolean required) throws SQLException { try { delegate.rollback(required); dirty = false; } finally { if (required) { tcm.rollback(); } } } private void ensureNoOutParams(MappedStatement ms, Object parameter, BoundSql boundSql) { if (ms.getStatementType() == StatementType.CALLABLE) { for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) { if (parameterMapping.getMode() != ParameterMode.IN) { throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement."); } } } } public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); } public boolean isCached(MappedStatement ms, CacheKey key) { throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus isCached() should never be called."); } public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus deferLoad() should never be called."); } public void clearLocalCache() { delegate.clearLocalCache(); } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { dirty = true; // issue #524. Disable using cached data for this session tcm.clear(cache); } } }
從上面的程式碼可以看出,建立Transaction事務,一次配置載入有且對應一個數據源。最後通過下面構造方法拿到DefaultSqlSession,sqlSession
public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; this.dirty = false; }
在看看DefaultSqlSession到底幹了什麼事情? MyBatis封裝了對資料庫的訪問,把對資料庫的會話和事務控制放到了SqlSession物件中。
2. 為SqlSession傳遞一個配置的Sql語句 的Statement Id和引數,然後返回結果:
//3.使用sqlSession進行查詢 Map<String, Object>condMap = new HashMap<String, Object>(); condMap.put("min_salary", 6000); //selectList方法引數 statement == mapper (namespace.id) == Employee.selectByMinSalary List<Employee> elEmployees = sqlSession.selectList("Employee.selectByMinSalary", condMap);上述的"Employee.selectByMinSalary",是配置在EmployeesMapper.xml的Statement ID,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 { MappedStatement ms = configuration.getMappedStatement(statement); 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的配置檔案中配置資訊的組織格式和記憶體中物件的組織格式幾乎完全對應的。上述例子中的<!-- 查詢工資低於 min_salary的員工 --> <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map"> select <include refid='BASE_COLUMN_LIST' /> from <include refid='TABLE_NAME' /> t <where> <if test="min_salary != null"> t.SALARY < #{min_salary,jdbcType=VARCHAR} </if> </where> </select>載入到記憶體中會生成一個對應的MappedStatement物件,然後會以key="Employee.selectByMinSalary",
value為MappedStatement物件的形式維護到Configuration的一個Map中。當以後需要使用的時候,只需要通過Id值來獲取就可以了。從上述的程式碼中我們可以看到SqlSession的職能是:
SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然後呼叫mybatis執行器來執行具體的操作。
3.MyBatis執行器Executor根據SqlSession傳遞的引數執行query()方法(由於程式碼過長,讀者只需閱讀我註釋的地方即可):
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; }進入BaseExecutor 的queryFromDatabase方法:
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); }
//5將查詢結果放入快取中 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 {
//5.獲取Configuration配置資訊 Configuration configuration = ms.getConfiguration();
//6.通過配置物件獲取一個新的StatementHandler,該類主要用來處理一次sql操作 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql); //7.預處理StatementHandler物件,得到Statement物件
stmt = prepareStatement(handler, ms.getStatementLog());
//8.呼叫StatementHandler.query()方法,返回List結果 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }進入StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);看下newStatementHandler到底幹了些什麼事情?
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //9.根據相關引數獲取對應的StatementHandler物件
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //10.為StatementHandler物件繫結攔截器外掛,可以自定義攔截器實現Interceptor攔截器,比如實現分頁攔截器,在prepare執行前,通過動態代理的方式
//在執行invoke方法的時候,呼叫intercept方法對BoundSql進行處理,設定分頁資訊,從而實現分頁查詢。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler; }進入RoutingStatementHandler類看下構造方法:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }根據MappedStatement物件的StatementType來建立不同的StatementHandler,這個跟前面的執行器類似。StatementType有STATEMENT、PREPARED和CALLABLE三種類型,跟JDBC裡面的Statement型別一一對應。
進入InterceptorChain類的pluginAll類看下方法:
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) {
//呼叫攔截器介面的Plugin方法 target = interceptor.plugin(target); } return target; }下面是自己實現了一個分頁攔截器如下:
package com.teaboy.dao.pagination; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.executor.statement.BaseStatementHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.property.PropertyTokenizer; import org.apache.ibatis.scripting.xmltags.ForEachSqlNode; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import com.teaboy.dao.pagination.dialect.Dialect; import com.teaboy.util.ReflectHelper; /** * Mybatis分頁攔截器 * * @Title:PaginationInterceptor.java * * @Description:MyBatis分頁攔截器 * * @author zhaoyang * @date Jan 17, 2014 10:05:02 AM * @version V1.0 */ @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) }) public class PaginationInterceptor implements Interceptor { //dialect private Dialect dialect = null; //分頁查詢攔截 private String paginationSqlRegEx = ".*ByCond"; /** * 連線 * override intercept * <p>Title: intercept</p> * <p>Description: </p> * @param invocation * @return * @throws Throwable */ public Object intercept(Invocation invocation) throws Throwable { //statement handler if (invocation.getTarget() instanceof RoutingStatementHandler) { RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget(); BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler, "delegate"); MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate, "mappedStatement"); //statement是否符合模式,檢查是符合正則表示式以ByCond結尾 if (mappedStatement.getId().matches(paginationSqlRegEx)) { //獲取sql BoundSql boundSql = delegate.getBoundSql(); String sql = boundSql.getSql(); sql = sql.replaceAll("\\s{1,}", " "); Object parameterObject = boundSql.getParameterObject(); if (parameterObject != null) { PaginationInfo paginationInfo = null; if (parameterObject instanceof Map<?, ?>) { paginationInfo = (PaginationInfo) ((Map<?, ?>) parameterObject).get("paginationInfo"); } else { Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "paginationInfo"); if (pageField != null) { paginationInfo = (PaginationInfo) ReflectHelper.getValueByFieldName(parameterObject, "paginationInfo"); } } if (paginationInfo != null) { int count = 0; Connection connection = null; PreparedStatement countStmt = null; ResultSet rs = null; try { connection = (Connection) invocation.getArgs()[0]; String countSql = this.dialect.getCountString(sql); countStmt = connection.prepareStatement(countSql); setParameters(countStmt, mappedStatement, boundSql, parameterObject); rs = countStmt.executeQuery(); if (rs.next()) { count = rs.getInt(1); } } catch (Exception e) { e.printStackTrace(); } finally { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } try { countStmt.close(); } catch (Exception e) { e.printStackTrace(); } } if (parameterObject instanceof Map<?, ?>) { paginationInfo = (PaginationInfo) ((Map<?, ?>) parameterObject).get("paginationInfo"); if (paginationInfo == null) { paginationInfo = new PaginationInfo(1, count); } } else { Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "paginationInfo"); if (pageField != null) { paginationInfo = (PaginationInfo) ReflectHelper.getValueByFieldName(parameterObject, "paginationInfo"); if (paginationInfo == null) { paginationInfo = new PaginationInfo(); } ReflectHelper.setValueByFieldName(parameterObject, "paginationInfo", paginationInfo); } else { throw new NoSuchFieldException(parameterObject.getClass().getName() + "不存在 PaginationInfo 屬性"); } } paginationInfo.setTotalRecord(count); paginationInfo.setTotalPage(((count - 1) / paginationInfo.getRecordPerPage()) + 1); String paginationSql = this.dialect.getLimitString(sql, paginationInfo.getOffset(), paginationInfo.getLimit()); //System.out.println("pagination sql : " + paginationSql); ReflectHelper.setValueByFieldName(boundSql, "sql", paginationSql); } } } } return invocation.proceed(); } /** * 設定查詢param * setParameters * @Title: setParameters * @Description: TODO * @param ps * @param mappedStatement * @param boundSql * @param parameterObject * @throws SQLException */ @SuppressWarnings("unchecked") public void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { Configuration configuration = mappedStatement.getConfiguration(); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); PropertyTokenizer prop = new PropertyTokenizer(propertyName); if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) { value = boundSql.getAdditionalParameter(prop.getName()); if (value != null) { value = configuration.newMetaObject(value).getValue( propertyName.substring(prop.getName().length())); } } else { value = metaObject == null ? null : metaObject.getValue(propertyName); } @SuppressWarnings("rawtypes") TypeHandler typeHandler = parameterMapping.getTypeHandler(); if (typeHandler == null) { throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); } typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); } } } } /** * 設定plugin * override plugin * <p>Title: plugin</p> * <p>Description: </p> * @param arg0 * @return */ public Object plugin(Object arg0) { return Plugin.wrap(arg0, this); } /** * 設定屬性 * override setProperties * <p>Title: setProperties</p> * <p>Description: </p> * @param p */ public void setProperties(Properties p) { } /** * 獲得dialect * getDialect * @Title: getDialect * @Description: TODO * @return */ public Dialect getDialect() { return dialect; } /** * * setDialect * @Title: setDialect * @Description: TODO * @param dialect */ public void setDialect(Dialect dialect) { this.dialect = dialect; } /** * * getPaginationSqlRegEx * @Title: getPaginationSqlRegEx * @Description: TODO * @return */ public String getPaginationSqlRegEx() { return paginationSqlRegEx; } /** * * setPaginationSqlRegEx * @Title: setPaginationSqlRegEx * @Description: TODO * @param paginationSqlRegEx */ public void setPaginationSqlRegEx(String paginationSqlRegEx) { this.paginationSqlRegEx = paginationSqlRegEx; } }接著上面的plugin方法,然後進入Plugin類,可以看到是JDK動態代理實現的類,裡面的wrap方式生成一個代理物件的方法,通過目標物件taget=PreparedStatementHandler以及上面黃色部分的註解StatementHandler 介面動態的生成代理物件。然後在執行invoke方法的時候,再次檢測
黃色部分註解資訊中的method = "prepare"方法,如果存在要攔截的方法,則執行PaginationInterceptor中的intercept方法做攔截處理,加上分頁條件等。然後才執行prepare方法。
/* * Copyright 2009-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.plugin; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.ibatis.reflection.ExceptionUtil; public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } //動態的生成代理物件,通過目標物件 public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } //每次呼叫目標類中的方法,必須要經過的invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); if (interceptsAnnotation == null) { // issue #251 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<Class<?>>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); } }攔截器介面如下:
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }上述的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()方法的實現:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); 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. PreparedStatementHandler的parameterize(statement) 方法的實現:
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler物件來完成對Statement的?佔位符的賦值
parameterHandler.setParameters((PreparedStatement) 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) { MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); 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 { value = metaObject == null ? null : metaObject.getValue(propertyName); }
//每個mapping都有一個typeHandler 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)方法的實現:
//PreparedStatementHandler中的query方法實現public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//1.呼叫preparedStatemnt excute方法,然後將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); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } while (rsw != null && resultSetCount < mappedStatement.getResulSets().length) { ResultMapping parentMapping = nextResultMaps.get(mappedStatement.getResulSets()[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>結果集。