MyBatis是目前很是流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,而且討論MyBatis的幾個核心部件,而後結合一個select查詢實例,深刻代碼,來探究MyBatis的實現。html
1.接口層---和數據庫交互的方式
MyBatis和數據庫的交互有兩種方式:java
a.使用傳統的MyBatis提供的API;sql
b. 使用Mapper接口數據庫
1.1.使用傳統的MyBatis提供的APIapache
這是傳統的傳遞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 接口表示的返回值類型或者返回結果集的元素類型。session
根據MyBatis 的配置規範配置好後,經過SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 會根據相應的接口聲明的方法信息,經過動態代理機制生成一個Mapper 實例,咱們使用Mapper 接口的某一個方法時,MyBatis 會根據這個方法的方法名和參數類型,肯定Statement Id,底層仍是經過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實現對數據庫的操做,(至於這裏的動態機制是怎樣實現的,我將準備專門一片文章來討論,敬請關注~)mybatis
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的主要的核心部件有如下幾個:
- 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的理解不一樣,分析出的結果天然會有所不一樣,歡迎讀者提出質疑和不一樣的意見,咱們共同探討~)
它們的關係以下圖所示:
1、數據準備(很是熟悉和應用過MyBatis 的讀者能夠迅速瀏覽此節便可)
1. 準備數據庫數據,建立EMPLOYEES表,插入數據:
2. 配置Mybatis的配置文件,命名爲mybatisConfig.xml:
3. 建立Employee實體Bean 以及配置Mapper配置文件
4. 建立eclipse 或者myeclipse 的maven項目,maven配置以下:
5. 客戶端代碼:
1. 開啓一個數據庫訪問會話---建立SqlSession對象:
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的功能和做用是:
(一、根據傳遞的參數,完成SQL語句的動態解析,生成BoundSql對象,供StatementHandler使用;
(二、爲查詢建立緩存,以提升性能(具體它的緩存機制不是本文的重點,我會單獨拿出來跟你們探討,感興趣的讀者能夠關注個人其餘博文);
(三、建立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);
- }