1. 程式人生 > >Mybatis整體框架設計

Mybatis整體框架設計

MyBatis框架整體設計

MyBatis框架整體設計

5.1 介面層-和資料庫互動的方式

MyBatis和資料庫的互動有兩種方式:

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

  2. 使用Mapper介面;

5.1.1 使用傳統的MyBatis提供的API

這是傳統的傳遞Statement Id 和查詢引數給 SqlSession 物件,使用 SqlSession物件完成和資料庫的互動;MyBatis提供了非常方便和簡單的API,供使用者實現對資料庫的增刪改查資料操作,以及對資料庫連線資訊和MyBatis 自身配置資訊的維護操作。

傳統的MyBatis工作模式

上述使用MyBatis 的方法,是建立一個和資料庫打交道的SqlSession物件,然後根據Statement Id 和引數來操作資料庫

,這種方式固然很簡單和實用,但是它不符合面嚮物件語言的概念和麵向介面程式設計的程式設計習慣。由於面向介面的程式設計是面向物件的大趨勢,MyBatis 為了適應這一趨勢,增加了第二種使用MyBatis 支援介面(Interface)呼叫方式。

5.1.2 使用Mapper介面

MyBatis 將配置檔案中的每一個<mapper> 節點抽象為一個 Mapper 介面:

這個介面中宣告的方法和<mapper> 節點中的<select|update|delete|insert> 節點項對應,即<select|update|delete|insert> 節點的id值為Mapper 介面中的方法名稱,parameterType 值表示Mapper 對應方法的入參型別

,而resultMap 值則對應了Mapper 介面表示的返回值型別或者返回結果集的元素型別

Mapper介面和Mapper.xml配置檔案之間的對應關係

根據MyBatis 的配置規範配置好後,通過SqlSession.getMapper(XXXMapper.class)方法,MyBatis 會根據相應的介面宣告的方法資訊,通過動態代理機制生成一個Mapper 例項,我們使用Mapper介面的某一個方法時,MyBatis會根據這個方法的方法名和引數型別,確定Statement Id,底層還是通過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實現對資料庫的操作,MyBatis引用Mapper 介面這種呼叫方式,純粹是為了滿足面向介面程式設計的需要

。(其實還有一個原因是在於,面向介面的程式設計,使得使用者在介面上可以使用註解來配置SQL語句,這樣就可以脫離XML配置檔案,實現“0配置”)。

5.2 資料處理層

資料處理層可以說是MyBatis的核心,從大的方面上講,它要完成兩個功能:

  1. 通過傳入引數構建動態SQL語句;

  2. SQL語句的執行以及封裝查詢結果整合List<E>;

5.2.1 引數對映和動態SQL語句生成

動態語句生成可以說是MyBatis框架非常優雅的一個設計,MyBatis 通過傳入的引數值,使用 Ognl 來動態地構造SQL語句,使得MyBatis 有很強的靈活性和擴充套件性。

引數對映指的是對於java 資料型別和jdbc資料型別之間的轉換:這裡有包括兩個過程:查詢階段,我們要將java型別的資料,轉換成jdbc型別的資料,通過 preparedStatement.setXXX() 來設值;另一個就是對resultset查詢結果集的jdbcType 資料轉換成java 資料型別

5.2.2 SQL語句的執行以及封裝查詢結果整合List<E>

動態SQL語句生成之後,MyBatis 將執行SQL語句,並將可能返回的結果集轉換成List<E> 列表。MyBatis 在對結果集的處理中,支援結果集關係一對多和多對一的轉換,並且有兩種支援方式,一種為巢狀查詢語句的查詢,還有一種是巢狀結果集的查詢

5.3 框架支撐層

  1. 事務管理機制

    事務管理機制對於ORM框架而言是不可缺少的一部分,事務管理機制的質量也是考量一個ORM框架是否優秀的一個標準。

  2. 連線池管理機制

    由於建立一個數據庫連線所佔用的資源比較大,對於資料吞吐量大和訪問量非常大的應用而言,連線池的設計就顯得非常重要

  3. 快取機制

    為了提高資料利用率和減小伺服器和資料庫的壓力,MyBatis 會對於一些查詢提供會話級別的資料快取,會將對某一次查詢,放置到SqlSession 中,在允許的時間間隔內,對於完全相同的查詢,MyBatis會直接將快取結果返回給使用者,而不用再到資料庫中查詢。

  4. SQL語句的配置方式

    傳統的MyBatis 配置SQL語句方式就是使用XML檔案進行配置的,但是這種方式不能很好地支援面向介面程式設計的理念,為了支援面向介面的程式設計,MyBatis 引入了Mapper介面的概念,面向介面的引入,對使用註解來配置SQL語句成為可能,使用者只需要在介面上新增必要的註解即可,不用再去配置XML檔案了,但是,目前的MyBatis 只是對註解配置SQL語句提供了有限的支援,某些高階功能還是要依賴XML配置檔案配置SQL 語句。

5.4 引導層

引導層是配置和啟動MyBatis配置資訊的方式。MyBatis 提供兩種方式來引導MyBatis :基於XML配置檔案的方式和基於Java API 的方式

5.5 主要構件及其相互關係

從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主要構件關係如圖

6 SqlSession工作過程分析

  1. 開啟一個數據庫訪問會話---建立SqlSession物件

    SqlSession sqlSession = factory.openSession();

    MyBatis封裝了對資料庫的訪問,把對資料庫的會話和事務控制放到了SqlSession物件中
  2. 為SqlSession傳遞一個配置的Sql語句的Statement Id和引數,然後返回結果:

    List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

    上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params是傳遞的查詢引數。

    讓我們來看一下sqlSession.selectList()方法的定義:

    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的配置檔案中配置資訊的組織格式和記憶體中物件的組織格式幾乎完全對應的

    上述例子中的:

    <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
     select   
         EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY  
     from LOUIS.EMPLOYEES  
     <if test="min_salary != null">  
         where SALARY < #{min_salary,jdbcType=DECIMAL}  
     </if>  
    </select>

    載入到記憶體中會生成一個對應的MappedStatement物件,然後會以key="com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" ,value為MappedStatement物件的形式維護到Configuration的一個Map中。當以後需要使用的時候,只需要通過Id值來獲取就可以了。

    從上述的程式碼中我們可以看到SqlSession的職能是:SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然後呼叫mybatis執行器來執行具體的操作

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

    /** 
     * BaseExecutor 類部分程式碼 
     * 
     */  
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 1. 根據具體傳入的引數,動態地生成需要執行的SQL語句,用BoundSql物件表示    
        BoundSql boundSql = ms.getBoundSql(parameter);  
        // 2. 為當前的查詢建立一個快取Key  
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);  
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);  
    }  
    
    @SuppressWarnings("unchecked")  
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
         ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
         if (closed) throw new ExecutorException("Executor was closed.");  
         if (queryStack == 0 && ms.isFlushCacheRequired()) {  
             clearLocalCache();  
         }  
         List<E> list;  
         try {  
             queryStack++;  
             list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;  
             if (list != null) {  
                 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);  
             } else {  
                 // 3.快取中沒有值,直接從資料庫中讀取資料    
                 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  
             }  
         } finally {  
             queryStack--;  
         }  
         if (queryStack == 0) {  
             for (DeferredLoad deferredLoad : deferredLoads) {  
                 deferredLoad.load();  
             }  
             deferredLoads.clear(); // issue #601  
             if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {  
                 clearLocalCache(); // issue #482  
             }  
         }  
         return list;  
    }
    
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
        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);  
    }

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

    /**   
     * ResultSetHandler類的handleResultSets()方法實現 
     *  
     */  
    public List<Object> handleResultSets(Statement stmt) throws SQLException {  
        final List<Object> multipleResults = new ArrayList<Object>();  
    
        int resultSetCount = 0;  
        ResultSetWrapper rsw = getFirstResultSet(stmt);  
    
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();  
        int resultMapCount = resultMaps.size();  
        validateResultMapsCount(rsw, resultMapCount);  
    
        while (rsw != null && resultMapCount > resultSetCount) {  
            ResultMap resultMap = resultMaps.get(resultSetCount);  
    
            //將resultSet  
            handleResultSet(rsw, resultMap, multipleResults, null);  
            rsw = getNextResultSet(stmt);  
            cleanUpAfterHandlingResultSet();  
            resultSetCount++;  
        }
    
        String[] resultSets = mappedStatement.getResulSets();  
        if (resultSets != null) {  
            while (rsw != null && resultSetCount < resultSets.length) {  
                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);  
                if (parentMapping != null) {  
                    String nestedResultMapId = parentMapping.getNestedResultMapId();  
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);  
                    handleResultSet(rsw, resultMap, null, parentMapping);  
                }  
                rsw = getNextResultSet(stmt);  
                cleanUpAfterHandlingResultSet();  
                resultSetCount++;  
            }  
        }  
    
        return collapseSingleResultList(multipleResults);  
    }

7 MyBatis初始化機制

7.1 MyBatis的初始化做了什麼

任何框架的初始化,無非是載入自己執行時所需要的配置資訊。MyBatis的配置資訊,大概包含以下資訊,其高層級結構如下:


MyBatis配置資訊結構圖

MyBatis的上述配置資訊會配置在XML配置檔案中,那麼,這些資訊被載入進入MyBatis內部,MyBatis是怎樣維護的呢?

MyBatis採用了一個非常直白和簡單的方式---使用 org.apache.ibatis.session.Configuration物件作為一個所有配置資訊的容器,Configuration物件的組織結構和XML配置檔案的組織結構幾乎完全一樣(當然,Configuration物件的功能並不限於此,它還負責建立一些MyBatis內部使用的物件,如Executor等,這將在後續的文章中討論)。如下圖所示:


Configuration物件的組織結構和XML配置檔案的組織結構幾乎完全一樣

MyBatis根據初始化好Configuration資訊,這時候使用者就可以使用MyBatis進行資料庫操作了。可以這麼說,MyBatis初始化的過程,就是建立 Configuration物件的過程

MyBatis的初始化可以有兩種方式:

基於XML配置檔案:基於XML配置檔案的方式是將MyBatis的所有配置資訊放在XML檔案中,MyBatis通過載入並XML配置檔案,將配置文資訊組裝成內部的Configuration物件。

基於Java API:這種方式不使用XML配置檔案,需要MyBatis使用者在Java程式碼中,手動建立Configuration物件,然後將配置引數set 進入Configuration物件中。

接下來我們將通過 基於XML配置檔案方式的MyBatis初始化,深入探討MyBatis是如何通過配置檔案構建Configuration物件,並使用它。

7.2 基於XML配置檔案建立Configuration物件

現在就從使用MyBatis的簡單例子入手,深入分析一下MyBatis是怎樣完成初始化的,都初始化了什麼。看以下程式碼:

String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);  
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
SqlSession sqlSession = sqlSessionFactory.openSession();  
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

有過MyBatis使用經驗的讀者會知道,上述語句的作用是執行com.foo.bean.BlogMapper.queryAllBlogInfo 定義的SQL語句,返回一個List結果集。總的來說,上述程式碼經歷了mybatis初始化 -->建立SqlSession -->執行SQL語句返回結果三個過程。

上述程式碼的功能是根據配置檔案mybatis-config.xml 配置檔案,建立SqlSessionFactory物件,然後產生SqlSession,執行SQL語句。而mybatis的初始化就發生在第三句:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 現在就讓我們看看第三句到底發生了什麼。

  1. MyBatis初始化基本過程:

    SqlSessionFactoryBuilder根據傳入的資料流生成Configuration物件,然後根據Configuration物件建立預設的SqlSessionFactory例項。

    初始化的基本過程如下序列圖所示:


    MyBatis初始化序列圖

    由上圖所示,mybatis初始化要經過簡單的以下幾步:

    1. 呼叫SqlSessionFactoryBuilder物件的build(inputStream)方法;

    2. SqlSessionFactoryBuilder會根據輸入流inputStream等資訊建立XMLConfigBuilder物件;

    3. SqlSessionFactoryBuilder呼叫XMLConfigBuilder物件的parse()方法;

    4. XMLConfigBuilder物件返回Configuration物件;

    5. SqlSessionFactoryBuilder根據Configuration物件建立一個DefaultSessionFactory物件;

    6. SqlSessionFactoryBuilder返回 DefaultSessionFactory物件給Client,供Client使用。

    SqlSessionFactoryBuilder相關的程式碼如下所示:

    public SqlSessionFactory build(InputStream inputStream)  {  
        return build(inputStream, null, null);  
    }  
    
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)  {  
        try  {  
            //2. 建立XMLConfigBuilder物件用來解析XML配置檔案,生成Configuration物件  
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
            //3. 將XML配置檔案內的資訊解析成Java物件Configuration物件  
            Configuration config = parser.parse();  
            //4. 根據Configuration物件創建出SqlSessionFactory物件  
            return build(config);  
        } catch (Exception e) {  
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
        } finally {  
            ErrorContext.instance().reset();  
            try {  
                inputStream.close();  
            } catch (IOException e) {  
                // Intentionally ignore. Prefer previous error.  
            }  
        }
    }
    
    // 從此處可以看出,MyBatis內部通過Configuration物件來建立SqlSessionFactory,使用者也可以自己通過API構造好Configuration物件,呼叫此方法創SqlSessionFactory  
    public SqlSessionFactory build(Configuration config) {  
        return new DefaultSqlSessionFactory(config);  
    }

    上述的初始化過程中,涉及到了以下幾個物件:

    SqlSessionFactoryBuilder :SqlSessionFactory的構造器,用於建立SqlSessionFactory,採用了Builder設計模式

    Configuration :該物件是mybatis-config.xml檔案中所有mybatis配置資訊

    SqlSessionFactory:SqlSession工廠類,以工廠形式建立SqlSession物件,採用了Factory工廠設計模式

    XmlConfigParser :負責將mybatis-config.xml配置檔案解析成Configuration物件,共SqlSessonFactoryBuilder使用,建立SqlSessionFactory

  2. 建立Configuration物件的過程:
    接著上述的 MyBatis初始化基本過程討論,當SqlSessionFactoryBuilder執行build()方法,呼叫了XMLConfigBuilder的parse()方法,然後返回了Configuration物件。那麼parse()方法是如何處理XML檔案,生成Configuration物件的呢?

    • (1)XMLConfigBuilder會將XML配置檔案的資訊轉換為Document物件,而XML配置定義檔案DTD轉換成XMLMapperEntityResolver物件,然後將二者封裝到XpathParser物件中,XpathParser的作用是提供根據Xpath表示式獲取基本的DOM節點Node資訊的操作。如下圖所示:


      XpathParser組成結構圖和生成圖
    • (2)之後XMLConfigBuilder呼叫parse()方法:會從XPathParser中取出 <configuration>節點對應的Node物件,然後解析此Node節點的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers:

      public Configuration parse() {  
          if (parsed) {  
              throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
          }  
          parsed = true;  
          //原始碼中沒有這一句,只有parseConfiguration(parser.evalNode("/configuration"));  
          //為了讓讀者看得更明晰,原始碼拆分為以下兩句  
          XNode configurationNode = parser.evalNode("/configuration");  
          parseConfiguration(configurationNode);  
          return configuration;
      }
      /** 
       * 解析 "/configuration"節點下的子節點資訊,然後將解析的結果設定到Configuration物件中 
       */  
      private void parseConfiguration(XNode root) {  
          try {  
              //1.首先處理properties 節點     
              propertiesElement(root.evalNode("properties")); //issue #117 read properties first  
              //2.處理typeAliases  
              typeAliasesElement(root.evalNode("typeAliases"));  
              //3.處理外掛  
              pluginElement(root.evalNode("plugins"));  
              //4.處理objectFactory  
              objectFactoryElement(root.evalNode("objectFactory"));  
              //5.objectWrapperFactory  
              objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
              //6.settings  
              settingsElement(root.evalNode("settings"));  
              //7.處理environments  
              environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631  
              //8.database  
              databaseIdProviderElement(root.evalNode("databaseIdProvider"));  
              //9.typeHandlers  
              typeHandlerElement(root.evalNode("typeHandlers"));  
              //10.mappers  
              mapperElement(root.evalNode("mappers"));  
          } catch (Exception e) {  
              throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);  
          }  
      }

      注意:在上述程式碼中,還有一個非常重要的地方,就是解析XML配置檔案子節點<mappers>的方法mapperElements(root.evalNode("mappers")), 它將解析我們配置的Mapper.xml配置檔案,Mapper配置檔案可以說是MyBatis的核心,MyBatis的特性和理念都體現在此Mapper的配置和設計上。

    • (3)然後將這些值解析出來設定到Configuration物件中:

      解析子節點的過程這裡就不一一介紹了,使用者可以參照MyBatis原始碼仔細揣摩,我們就看上述的environmentsElement(root.evalNode("environments")); 方法是如何將environments的資訊解析出來,設定到Configuration物件中的:

      /** 
       * 解析environments節點,並將結果設定到Configuration物件中 
       * 注意:建立envronment時,如果SqlSessionFactoryBuilder指定了特定的環境(即資料來源); 
       *      則返回指定環境(資料來源)的Environment物件,否則返回預設的Environment物件; 
       *      這種方式實現了MyBatis可以連線多資料來源 
       */  
      private void environmentsElement(XNode context) throws Exception {  
         if (context != null)  
         {  
              if (environment == null)  
              {  
                  environment = context.getStringAttribute("default");  
              }  
              for (XNode child : context.getChildren())  
              {  
                   String id = child.getStringAttribute("id");  
                   if (isSpecifiedEnvironment(id))  
                   {  
                       //1.建立事務工廠 TransactionFactory  
                       TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                       DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                       //2.建立資料來源DataSource  
                       DataSource dataSource = dsFactory.getDataSource();  
                       //3. 構造Environment物件  
                       Environment.Builder environmentBuilder = new Environment.Builder(id)  
                  .transactionFactory(txFactory)  
                  .dataSource(dataSource);  
                       //4. 將建立的Envronment物件設定到configuration 物件中  
                       configuration.setEnvironment(environmentBuilder.build());  
                  }  
              }  
         }  
      }
      private boolean isSpecifiedEnvironment(String id)  
      {  
          if (environment == null)  
          {  
               throw new BuilderException("No environment specified.");  
          }  
          else if (id == null)  
          {  
               throw new BuilderException("Environment requires an id attribute.");  
          }  
          else if (environment.equals(id))  
          {  
              return true;  
          }  
          return false;  
      }
    • (4)返回Configuration物件:

      將上述的MyBatis初始化基本過程的序列圖細化:


      基於XML配置建立Configuration物件的過程

相關推薦

Mybatis整體框架設計

MyBatis框架整體設計 MyBatis框架整體設計 5.1 介面層-和資料庫互動的方式 MyBatis和資料庫的互動有兩種方式: 使用傳統的MyBatis提供的API; 使用Mapper介面; 5.1.1 使用傳統的MyBatis提供的API 這是傳統的傳遞Statement Id 和查詢引數

Mybatis 整體流程詳解、部分原始碼解讀以及運用到了哪些設計模式

MyBatis主要的類 Configuration        MyBatis所有的配置資訊都維持在Configuratio

遊戲UI框架設計(五): 配置管理與應用

oid per b- 測試 中心 序列化對象 ner stun sage 遊戲UI框架設計(五) --配置管理與應用 在開發企業級遊戲/VR/AR產品時候,我們總是希望可以總結出一些通用的技術體系,框架結構等,為簡化我們的開發起到“四兩撥千金”的作用。所謂“配

遊戲UI框架設計(五): 配置管理與應用

unity界面框架 unityui框架 ui框架配置 unity配置管理 遊戲UI框架設計(五)--配置管理與應用 在開發企業級遊戲/VR/AR產品時候,我們總是希望可以總結出一些通用的技術體系,框架結構等,為簡化我們的開發起到“四兩撥千金”的作用。所謂“配置管理”是指一個遊戲項目(軟件項

殺毒軟件框架設計

文本文 圖片 繼續 查找文件 方式 找文件 使用 特點 folder 這個軟件,既可以對文件夾殺毒,也可以對某個指定的文件進行殺毒。可以根據不同文件的特點,為不同的文件提供不同的殺毒方式。圖片文件和文本文件的殺毒方式是有差異的。 文件夾中包含文件和文件夾,在文件夾中可以包含

遊戲UI框架設計(7): 資源國際化技術

我們 客戶 value json 查詢 ons bug emp pty 遊戲UI框架設計(7) --資源國際化技術 說起“資源國際化”技術,個人認為可以追述到微軟Window2000 PC操作系統的發布,在這之前windows98操作系統的開發都是先由美國

wifi androd 整體框架

bsp yar 整體 tail hand 框架 rod sdn .html 1. http://blog.csdn.net/myarrow/article/details/8129607/ 2. http://blog.csdn.net/liuhaomatou/artic

.NET架構設計框架設計系列文章總結

str 統架構 經驗分享 .net 4.5 bind lin ati digg icon 架構設計: ElasticSearch大數據分布式彈性搜索引擎使用 (推薦) DDD實施經驗分享—價值導向、從上往下進行(圈內第一個吃螃蟹DDD實施方案)(推薦) 軟件工程—思考項

Java EE 架構設計——基於okhttp3 的網絡框架設計

知識 tap 使用 pass web asc 封裝 useragent 占用 轉載請註明出處:http://blog.csdn.net/smartbetter/article/details/77893903 本篇文章帶大家設計一套滿意業務需求、代碼健壯高效(高內聚低耦

Netty自娛自樂之類Dubbo RPC 框架設計構想 【上篇】

哈哈 ebe cte proc 文件 num one lex round   之前在前一篇的《Netty自娛自樂之協議棧設計》,菜鳥我已經自娛自樂了設計協議棧,gitHub地址為https://github.com/vOoT/ncustomer-protocal。先這一篇中

靜態網頁框架設計首次體驗(文章改)

指點 .com 框架設計 關於 odi 綜合 bsp contex 參考 根據教材與上網成功解決了Tomcat與Myeclipse的安裝,同時熟悉了Java web創建項目到部署運行整個過程。今天起正式開始學習有關Java web的編程部分。Java web靜態網頁(HTM

SSM(Spring+SpringMVC+Mybatis框架搭建詳細教程【附源代碼Demo】

oid rep images end 訪問靜態文件 into *** 寫到 where http://www.cnblogs.com/qixiaoyizhan/p/7751732.html 【前言】   應某網絡友人邀約,需要一個SSM框架的Demo作為基礎學習資料,於

活動基礎框架設計

數據 成長 系統 ble 推送 減少 結構 活動圖 類型 活動系統的類型大概有,運營活動,常規活動,軍團活動,開服活動,合服活動,活動是多樣性的但又很多相同的地方值得我們抽象,下面來進行具體分析。 一、活動數據存儲 沒有完整的框架之前,多個同事開發不同活動,往往會自己建

接口自動化測試系列之PHPUnit-框架設計構思

phpunit 自動化測試 接口測試 小強測試品牌 測試幫日記 關於case設計我們通常總是關註代碼的編寫往往忘了case的設計也是非常重要的。如果你是做接口功能自動化測試,那麽你要考慮各種接口參數的組合情況,比如,正常的時候,不正常的時候,必填選填等等。這時候考驗case設計的功底就來了

python+selenium之框架設計

get 生成 size ava earch 良好的 形象 aid set 一、自動化測試框架 1.什麽是自動化測試框架 簡單來說,自動化測試框架就是由一些標準,協議,規則組成,提供腳本運行的環境。自動化測試框架能夠提供很多便利給用戶高效完成一些事情,例如,結

springmvc4 mybatis 整合 框架源碼 bootstrap html5 mysql o

寫入 任務調度 絢麗 友情鏈接 信息 secret sqlserver 防火墻 一鍵還原 A代碼編輯器,在線模版編輯,仿開發工具編輯器,pdf在線預覽,文件轉換編碼B 集成代碼生成器 [正反雙向](單表、主表、明細表、樹形表,快速開發利器)+快速表單構建器freemaker

Net分布式系統之一:系統整體框架介紹

文件 和數 mongo lan 不能 結合 logs mssql mongodb 本文轉載收藏於:https://www.cnblogs.com/Andon_liu/p/5353488.html  一、設計目的   從事.Net平臺開發系統已有8年多了,一直思考

springmvc4 mybatis 整合 框架源碼 bootstrap SSM

數據庫還原 圖表 應用 文件格式 微信自定義菜單 ots 異步操作 pan bootstra A代碼編輯器,在線模版編輯,仿開發工具編輯器,pdf在線預覽,文件轉換編碼B 集成代碼生成器 [正反雙向](單表、主表、明細表、樹形表,快速開發利器)+快速表單構建器freema

Android組件化框架設計與實踐

交叉 string 構建 即使 插入 開始 build 很多 www 在目前移動互聯網時代,每個 APP 就是流量入口,與過去 PC Web 瀏覽器時代不同的是,APP 的體驗與叠代速度影響著用戶的粘性,這同時也對從事移動開發人員提出更高要求,進而移動端框架也層出不窮。

世界杯皇冠體育足球競猜系統整體架構設計

ref 缺少 默認 == targe 架構設計 body left 結果 競猜業務邏輯很簡單世界杯皇冠體育足球源碼下載dsluntan.com 企娥3393756370世界杯皇冠體育足球源碼下載、普遍用於各種賽事中、籃球賽、足球賽、包括最近興起的遊戲電競賽事,對於社區產品來