【深刻淺出MyBatis系列五】SQL執行流程分析(源碼篇)

#0 系列目錄#html

前面的章節主要講mybatis如何解析配置文件,這些都是一次性的過程。從本章開始講解動態的過程,它們跟應用程序對mybatis的調用密切相關。本章先從sqlsession開始。spring

#1 SqlSessionFactory 與 SqlSession# 經過前面的章節對於mybatis 的介紹及使用,你們都能體會到SqlSession的重要性了吧,沒錯,從表面上來看,我們都是經過SqlSession去執行sql語句(注意:是從表面看,實際的待會兒就會講)。sql

正如其名,Sqlsession對應着一次數據庫會話。因爲數據庫會話不是永久的,所以Sqlsession的生命週期也不該該是永久的,相反,在你每次訪問數據庫時都須要建立它(固然並非說在Sqlsession裏只能執行一次sql,你能夠執行屢次,當一旦關閉了Sqlsession就須要從新建立它)。數據庫

那麼我們就先看看是怎麼獲取SqlSession的吧:緩存

輸入圖片說明

  1. 首先,SqlSessionFactoryBuilder去讀取mybatis的配置文件,而後build一個DefaultSqlSessionFactory。源碼以下:
/**
  * 一系列的構造方法最終都會調用本方法(配置文件爲Reader時會調用本方法,還有一個InputStream方法與此對應)
  * @param reader
  * @param environment
  * @param properties
  * @return
  */
 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
   try {
     //經過XMLConfigBuilder解析配置文件,解析的配置相關信息都會封裝爲一個Configuration對象
     XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
     //這兒建立DefaultSessionFactory對象
     return build(parser.parse());
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error building SqlSession.", e);
   } finally {
     ErrorContext.instance().reset();
     try {
       reader.close();
     } catch (IOException e) {
       // Intentionally ignore. Prefer previous error.
     }
   }
 }

 public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }
  1. 當咱們獲取到SqlSessionFactory以後,就能夠經過SqlSessionFactory去獲取SqlSession對象。源碼以下:
/**
  * 一般一系列openSession方法最終都會調用本方法
  * @param execType 
  * @param level
  * @param autoCommit
  * @return
  */
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     //經過Confuguration對象去獲取Mybatis相關配置信息, Environment對象包含了數據源和事務的配置
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     //以前說了,從表面上來看,我們是用sqlSession在執行sql語句, 實際呢,實際上是經過excutor執行, excutor是對於Statement的封裝
     final Executor executor = configuration.newExecutor(tx, execType);
     //關鍵看這兒,建立了一個DefaultSqlSession對象
     return new DefaultSqlSession(configuration, executor, autoCommit);
   } 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();
   }
 }
  1. 經過以上步驟,我們已經獲得SqlSession對象了。接下來就是該幹嗎幹嗎去了(話說還能幹嗎,固然是執行sql語句咯)。看了上面,我們也回想一下以前寫的Demo:
SqlSessionFactory sessionFactory = null;  
String resource = "mybatis-conf.xml";  
try {
    //SqlSessionFactoryBuilder讀取配置文件
   sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
} catch (IOException e) {  
   e.printStackTrace();  
}    
//經過SqlSessionFactory獲取SqlSession
SqlSession sqlSession = sessionFactory.openSession();
  1. 建立Sqlsession的地方只有一個,那就是SqlsessionFactory的openSession方法:
public SqlSessionopenSession() {  
    return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);  
}

咱們能夠看到實際建立SqlSession的地方是openSessionFromDataSource,以下:session

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  
 
    Connection connection = null;  
 
    try {  
 
        final Environment environment = configuration.getEnvironment();  
 
        final DataSource dataSource = getDataSourceFromEnvironment(environment);  
        
        // MyBatis對事務的處理相對簡單,TransactionIsolationLevel中定義了幾種隔離級別,並不支持內嵌事務這樣較複雜的場景,同時因爲其是持久層的緣故,因此真正在應用開發中會委託Spring來處理事務實現真正的與開發者隔離。分析事務的實現是個入口,藉此能夠了解很多JDBC規範方面的事情。
        TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  
 
        connection = dataSource.getConnection();  
 
        if (level != null) {  
            connection.setTransactionIsolation(level.getLevel());
        }  
 
        connection = wrapConnection(connection);  
 
        Transaction tx = transactionFactory.newTransaction(connection,autoCommit);  
 
        Executorexecutor = configuration.newExecutor(tx, execType);  
 
        return newDefaultSqlSession(configuration, executor, autoCommit);  
 
    } catch (Exceptione) {  
        closeConnection(connection);  
        throwExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);  
    } finally {
        ErrorContext.instance().reset();
    }
}

能夠看出,建立sqlsession通過了如下幾個主要步驟:mybatis

  1. 從配置中獲取Environment;app

  2. 從Environment中取得DataSource;ide

  3. 從Environment中取得TransactionFactory;源碼分析

  4. 從DataSource裏獲取數據庫鏈接對象Connection;

  5. 在取得的數據庫鏈接上建立事務對象Transaction;

  6. 建立Executor對象(該對象很是重要,事實上sqlsession的全部操做都是經過它完成的);

  7. 建立sqlsession對象。

還真這麼一回事兒,對吧!

SqlSession我們也拿到了,我們能夠調用SqlSession中一系列的select..., insert..., update..., delete...方法輕鬆自如的進行CRUD操做了。就這樣?那咱配置的映射文件去哪兒了?別急,我們接着往下看。

#2 利器之MapperProxy#

輸入圖片說明

在mybatis中,經過MapperProxy動態代理我們的dao, 也就是說, 當我們執行本身寫的dao裏面的方法的時候,實際上是對應的mapperProxy在代理。那麼,我們就看看怎麼獲取MapperProxy對象吧:

  1. 經過SqlSession從Configuration中獲取。源碼以下:
/**
  * 什麼都不作,直接去configuration中找, 哥就是這麼任性
  */
 @Override
 public <T> T getMapper(Class<T> type) {
   return configuration.<T>getMapper(type, this);
 }
  1. SqlSession把包袱甩給了Configuration, 接下來就看看Configuration。源碼以下:
/**
  * 燙手的山芋,俺不要,你找mapperRegistry去要
  * @param type
  * @param sqlSession
  * @return
  */
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   return mapperRegistry.getMapper(type, sqlSession);
 }
  1. Configuration不要這燙手的山芋,接着甩給了MapperRegistry, 那咱看看MapperRegistry。 源碼以下:
/**
  * 爛活淨讓我來作了,無法了,下面沒人了,我不作誰來作
  * @param type
  * @param sqlSession
  * @return
  */
 @SuppressWarnings("unchecked")
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //能偷懶的就偷懶,俺把粗活交給MapperProxyFactory去作
   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
   if (mapperProxyFactory == null) {
     throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
   }
   try {
     //關鍵在這兒
     return mapperProxyFactory.newInstance(sqlSession);
   } catch (Exception e) {
     throw new BindingException("Error getting mapper instance. Cause: " + e, e);
   }
 }
  1. MapperProxyFactory是個苦B的人,粗活最終交給它去作了。我們看看源碼:
/**
  * 別人虐我千百遍,我待別人如初戀
  * @param mapperProxy
  * @return
  */
 @SuppressWarnings("unchecked")
 protected T newInstance(MapperProxy<T> mapperProxy) {
   //動態代理咱們寫的dao接口
   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }
 
 public T newInstance(SqlSession sqlSession) {
   final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
   return newInstance(mapperProxy);
 }

經過以上的動態代理,我們就能夠方便地使用dao接口啦, 就像以前我們寫的demo那樣:

UserDao userMapper = sqlSession.getMapper(UserDao.class);  
User insertUser = new User();

這下方便多了吧, 呵呵, 貌似mybatis的源碼就這麼一回事兒啊。具體詳細介紹,請參見MyBatis Mapper 接口如何經過JDK動態代理來包裝SqlSession 源碼分析。別急,還沒完, 我們還沒看具體是怎麼執行sql語句的呢。

#3 Excutor#

Executor與Sqlsession的關係就像市長與書記,Sqlsession只是個門面,真正幹事的是Executor,Sqlsession對數據庫的操做都是經過Executor來完成的。與Sqlsession同樣,Executor也是動態建立的:

輸入圖片說明

Executor建立的源代碼:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
  
        executorType = executorType == null ? defaultExecutorType : executorType;  
  
        executorType = executorType == null ?ExecutorType.SIMPLE : executorType;  
  
        Executor executor;  
  
        if(ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this,transaction);
        } else if(ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this,transaction);  
        } else {  
            executor = newSimpleExecutor(this, transaction);
        }
  
        if (cacheEnabled) {
           executor = new CachingExecutor(executor);  
        }
        executor = (Executor) interceptorChain.pluginAll(executor);  
        return executor;  
    }

能夠看出,若是不開啓cache的話,建立的Executor只是3中基礎類型之一,BatchExecutor專門用於執行批量sql操做,ReuseExecutor會重用statement執行sql操做,SimpleExecutor只是簡單執行sql沒有什麼特別的。開啓cache的話(默認是開啓的而且沒有任何理由去關閉它),就會建立CachingExecutor,它之前面建立的Executor做爲惟一參數。CachingExecutor在查詢數據庫前先查找緩存,若沒找到的話調用delegate(就是構造時傳入的Executor對象)從數據庫查詢,並將查詢結果存入緩存中

Executor對象是能夠被插件攔截的,若是定義了針對Executor類型的插件,最終生成的Executor對象是被各個插件插入後的代理對象

接下來,我們纔要真正去看sql的執行過程了。上面,我們拿到了MapperProxy, 每一個MapperProxy對應一個dao接口, 那麼我們在使用的時候,MapperProxy是怎麼作的呢? 源碼奉上:

**MapperProxy:**咱們知道對被代理對象的方法的訪問都會落實到代理者的invoke上來,MapperProxy的invoke以下:

/**
   * MapperProxy在執行時會觸發此方法
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //二話不說,主要交給MapperMethod本身去管
    return mapperMethod.execute(sqlSession, args);
  }

**MapperMethod:**就像是一個分發者,他根據參數和返回值類型選擇不一樣的sqlsession方法來執行。這樣mapper對象與sqlsession就真正的關聯起來了

/**
   * 看着代碼很多,不過其實就是先判斷CRUD類型,而後根據類型去選擇到底執行sqlSession中的哪一個方法,繞了一圈,又轉回sqlSession了
   * @param sqlSession
   * @param args
   * @return
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

既然又回到SqlSession了,前面提到過,sqlsession只是一個門面,真正發揮做用的是executor,對sqlsession方法的訪問最終都會落到executor的相應方法上去。Executor分紅兩大類,一類是CacheExecutor,另外一類是普通Executor。Executor的建立前面已經介紹了,那麼我們就看看SqlSession的CRUD方法了,爲了省事,仍是就選擇其中的一個方法來作分析吧。這兒,我們選擇了selectList方法

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //CRUD其實是交給Excetor去處理, excutor其實也只是穿了個馬甲而已,小樣,別覺得穿個馬甲我就不認識你嘞!
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

**CacheExecutor:**CacheExecutor有一個重要屬性delegate,它保存的是某類普通的Executor,值在構照時傳入。執行數據庫update操做時,它直接調用delegate的update方法,執行query方法時先嚐試從cache中取值,取不到再調用delegate的查詢方法,並將查詢結果存入cache中。代碼以下:

public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {  
    if (ms != null) {  
        Cache cache = ms.getCache();  
        if (cache != null) {  
            flushCacheIfRequired(ms);  
            cache.getReadWriteLock().readLock().lock();  
            try {  
                if (ms.isUseCache() && resultHandler ==null) {  
                    CacheKey key = createCacheKey(ms, parameterObject, rowBounds);  
                    final List cachedList = (List)cache.getObject(key);  
                    if (cachedList != null) {  
                        return cachedList;  
                    } else {  
                        List list = delegate.query(ms,parameterObject, rowBounds, resultHandler);  
                        tcm.putObject(cache,key, list);  
                        return list;  
                    }  
                } else {  
                    return delegate.query(ms,parameterObject, rowBounds, resultHandler);  
                }  
            } finally {  
                cache.getReadWriteLock().readLock().unlock();  
            }
        }  
    }  
    return delegate.query(ms,parameterObject, rowBounds, resultHandler);  
}

**普通Executor:**有3類,他們都繼承於BaseExecutor,BatchExecutor專門用於執行批量sql操做,ReuseExecutor會重用statement執行sql操做,SimpleExecutor只是簡單執行sql沒有什麼特別的。下面以SimpleExecutor爲例:

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {  
    Statement stmt = null;  
    try {  
        Configuration configuration = ms.getConfiguration();  
        StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);  
        stmt =prepareStatement(handler);  
        returnhandler.query(stmt, resultHandler);  
    } finally {  
        closeStatement(stmt);  
    }  
}

而後,經過一層一層的調用,最終會來到doQuery方法, 這兒我們就隨便找個Excutor看看doQuery方法的實現吧,我這兒選擇了SimpleExecutor:

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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler封裝了Statement, 讓 StatementHandler 去處理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

Mybatis內置的ExecutorType有3種,默認的是simple,該模式下它爲每一個語句的執行建立一個新的預處理語句,單條提交sql;而batch模式重複使用已經預處理的語句, 而且批量執行全部更新語句,顯然batch性能將更優;

但batch模式也有本身的問題,好比在Insert操做時,在事務沒有提交以前,是沒有辦法獲取到自增的id,這在某型情形下是不符合業務要求的;

經過走碼和研讀spring相關文件發現,在同一事務中batch模式和simple模式之間沒法轉換,因爲本項目一開始選擇了simple模式,因此碰到須要批量更新時,只能在單獨的事務中進行;

在代碼中使用batch模式可使用如下方式:

//從spring注入原有的sqlSessionTemplate
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
 
public void testInsertBatchByTrue() {
    //新獲取一個模式爲BATCH,自動提交爲false的session
    //若是自動提交設置爲true,將沒法控制提交的條數,改成最後統一提交,可能致使內存溢出
    SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
    //經過新的session獲取mapper
    fooMapper = session.getMapper(FooMapper.class);
    int size = 10000;
    try {
        for (int i = 0; i < size; i++) {
            Foo foo = new Foo();
            foo.setName(String.valueOf(System.currentTimeMillis()));
            fooMapper.insert(foo);
            if (i % 1000 == 0 || i == size - 1) {
                //手動每1000個一提交,提交後沒法回滾
                session.commit();
                //清理緩存,防止溢出
                session.clearCache();
            }
        }
    } catch (Exception e) {
        //沒有提交的數據能夠回滾
        session.rollback();
    } finally {
        session.close();
    }
}

上述代碼沒有使用spring的事務,改手動控制,若是和原spring事務一塊兒使用,將沒法回滾,必須注意,最好單獨使用;

#4 StatementHandler# 能夠看出,Executor本質上也是個甩手掌櫃,具體的事情原來是StatementHandler來完成的。當Executor將指揮棒交給StatementHandler後,接下來的工做就是StatementHandler的事了。咱們先看看StatementHandler是如何建立的:

public StatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,  
        ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);  
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);  
    return statementHandler;
}

能夠看到每次建立的StatementHandler都是RoutingStatementHandler,它只是一個分發者,他一個屬性delegate用於指定用哪一種具體的StatementHandler。可選的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種。選用哪一種在mapper配置文件的每一個statement裏指定,默認的是PreparedStatementHandler。同時還要注意到StatementHandler是能夠被攔截器攔截的,和Executor同樣,被攔截器攔截後的對像是一個代理對象。因爲mybatis沒有實現數據庫的物理分頁,衆多物理分頁的實現都是在這個地方使用攔截器實現的,本文做者也實現了一個分頁攔截器,在後續的章節會分享給你們,敬請期待。

StatementHandler建立後須要執行一些初始操做,好比statement的開啓和參數設置、對於PreparedStatement還須要執行參數的設置操做等。代碼以下:

private Statement prepareStatement(StatementHandler handler) throws SQLException {  
    Statement stmt;  
    Connection connection = transaction.getConnection();  
    stmt =handler.prepare(connection);  
    handler.parameterize(stmt);  
    return stmt;  
}

statement的開啓和參數設置沒什麼特別的地方,handler.parameterize卻是能夠看看是怎麼回事。handler.parameterize經過調用ParameterHandler的setParameters完成參數的設置,ParameterHandler隨着StatementHandler的建立而建立,默認的實現是DefaultParameterHandler

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
   ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);  
   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
   return parameterHandler;  
}

同Executor和StatementHandler同樣,ParameterHandler也是能夠被攔截的。DefaultParameterHandler裏設置參數的代碼以下:

public void setParameters(PreparedStatement ps) throws SQLException {  
    ErrorContext.instance().activity("settingparameters").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();  
                PropertyTokenizer prop = newPropertyTokenizer(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);  
                }  
                TypeHandler typeHandler = parameterMapping.getTypeHandler();  
                if (typeHandler == null) {  
                   throw new ExecutorException("Therewas no TypeHandler found for parameter " + propertyName  + " of statement " + mappedStatement.getId());  
                }  
                typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());  
            }  
  
        }  
  
    }  
}

這裏面最重要的一句其實就是最後一句代碼,它的做用是用合適的TypeHandler完成參數的設置。那麼什麼是合適的TypeHandler呢,它又是如何決斷出來的呢?BaseStatementHandler的構造方法裏有這麼一句:

this.boundSql= mappedStatement.getBoundSql(parameterObject);

它觸發了sql 的解析,在解析sql的過程當中,TypeHandler也被決斷出來了,決斷的原則就是根據參數的類型和參數對應的JDBC類型決定使用哪一個TypeHandler。好比:參數類型是String的話就用StringTypeHandler,參數類型是整數的話就用IntegerTypeHandler等。

參數設置完畢後,執行數據庫操做(update或query)。若是是query最後還有個查詢結果的處理過程。

接下來,我們看看StatementHandler 的一個實現類 PreparedStatementHandler(這也是咱們最經常使用的,封裝的是PreparedStatement), 看看它使怎麼去處理的:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 到此,原形畢露, PreparedStatement, 這個你們都已經倒背如流了吧
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 結果交給了ResultSetHandler 去處理
    return resultSetHandler.<E> handleResultSets(ps);
  }

結果處理使用ResultSetHandler來完成,默認的ResultSetHandler是FastResultSetHandler,它在建立StatementHandler時一塊兒建立,代碼以下:

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement,  
RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {  
   ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
   return resultSetHandler;  
}

能夠看出ResultSetHandler也是能夠被攔截的,能夠編寫本身的攔截器改變ResultSetHandler的默認行爲。ResultSetHandler內部一條記錄一條記錄的處理,在處理每條記錄的每一列時會調用TypeHandler轉換結果,以下:

protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {  
    boolean foundValues = false;  
    for (String columnName : unmappedColumnNames) {  
        final String property = metaObject.findProperty(columnName);  
        if (property!= null) {  
            final ClasspropertyType =metaObject.getSetterType(property);  
            if (typeHandlerRegistry.hasTypeHandler(propertyType)) {  
                final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);  
                final Object value = typeHandler.getResult(rs,columnName);  
                if (value != null) {  
                    metaObject.setValue(property, value);  
                    foundValues = true;  
                }  
            }  
        }  
    }  
    return foundValues;  
}

從代碼裏能夠看到,決斷TypeHandler使用的是結果參數的屬性類型。所以咱們在定義做爲結果的對象的屬性時必定要考慮與數據庫字段類型的兼容性。到此, 一次sql的執行流程就完了。 我這兒僅拋磚引玉,建議有興趣的去看看Mybatis3的源碼。

相關文章
相關標籤/搜索