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

陶邦仁 發(fā)布于 2015/12/22 16:20

原文鏈接

系列目錄

前面的章節(jié)主要講mybatis如何解析配置文件,這些都是一次性的過程。從本章開始講解動態(tài)的過程,它們跟應用程序?qū)ybatis的調(diào)用密切相關(guān)。本章先從sqlsession開始。

1 SqlSessionFactory 與 SqlSession

通過前面的章節(jié)對于mybatis 的介紹及使用,大家都能體會到SqlSession的重要性了吧,沒錯,從表面上來看,咱們都是通過SqlSession去執(zhí)行sql語句(注意:是從表面看,實際的待會兒就會講)。

正如其名,Sqlsession對應著一次數(shù)據(jù)庫會話。由于數(shù)據(jù)庫會話不是永久的,因此Sqlsession的生命周期也不應該是永久的,相反,在你每次訪問數(shù)據(jù)庫時都需要創(chuàng)建它(當然并不是說在Sqlsession里只能執(zhí)行一次sql,你可以執(zhí)行多次,當一旦關(guān)閉了Sqlsession就需要重新創(chuàng)建它)。

那么咱們就先看看是怎么獲取SqlSession的吧:

輸入圖片說明
  1. 首先,SqlSessionFactoryBuilder去讀取mybatis的配置文件,然后build一個DefaultSqlSessionFactory。源碼如下:
 /**
  * 一系列的構(gòu)造方法最終都會調(diào)用本方法(配置文件為Reader時會調(diào)用本方法,還有一個InputStream方法與此對應)
  * @param reader
  * @param environment
  * @param properties
  * @return
  */
 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
   try {
     //通過XMLConfigBuilder解析配置文件,解析的配置相關(guān)信息都會封裝為一個Configuration對象
     XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
     //這兒創(chuàng)建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方法最終都會調(diào)用本方法
  * @param execType 
  * @param level
  * @param autoCommit
  * @return
  */
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     //通過Confuguration對象去獲取Mybatis相關(guān)配置信息, Environment對象包含了數(shù)據(jù)源和事務(wù)的配置
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     //之前說了,從表面上來看,咱們是用sqlSession在執(zhí)行sql語句, 實際呢,其實是通過excutor執(zhí)行, excutor是對于Statement的封裝
     final Executor executor = configuration.newExecutor(tx, execType);
     //關(guān)鍵看這兒,創(chuàng)建了一個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. 通過以上步驟,咱們已經(jīng)得到SqlSession對象了。接下來就是該干嘛干嘛去了(話說還能干嘛,當然是執(zhí)行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. 創(chuàng)建Sqlsession的地方只有一個,那就是SqlsessionFactory的openSession方法:
public SqlSessionopenSession() {  
    return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);  
}

我們可以看到實際創(chuàng)建SqlSession的地方是openSessionFromDataSource,如下:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  

    Connection connection = null;  

    try {  

        final Environment environment = configuration.getEnvironment();  

        final DataSource dataSource = getDataSourceFromEnvironment(environment);  

        // MyBatis對事務(wù)的處理相對簡單,TransactionIsolationLevel中定義了幾種隔離級別,并不支持內(nèi)嵌事務(wù)這樣較復雜的場景,同時由于其是持久層的緣故,所以真正在應用開發(fā)中會委托Spring來處理事務(wù)實現(xiàn)真正的與開發(fā)者隔離。分析事務(wù)的實現(xiàn)是個入口,借此可以了解不少JDBC規(guī)范方面的事情。
        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();
    }
}  

可以看出,創(chuàng)建sqlsession經(jīng)過了以下幾個主要步驟:

  1. 從配置中獲取Environment;
  1. 從Environment中取得DataSource;
  1. 從Environment中取得TransactionFactory;
  1. 從DataSource里獲取數(shù)據(jù)庫連接對象Connection;
  1. 在取得的數(shù)據(jù)庫連接上創(chuàng)建事務(wù)對象Transaction;
  1. 創(chuàng)建Executor對象(該對象非常重要,事實上sqlsession的所有操作都是通過它完成的);
  1. 創(chuàng)建sqlsession對象。

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

SqlSession咱們也拿到了,咱們可以調(diào)用SqlSession中一系列的select..., insert..., update..., delete...方法輕松自如的進行CRUD操作了。就這樣?那咱配置的映射文件去哪兒了?別急,咱們接著往下看。

2 利器之MapperProxy

輸入圖片說明

在mybatis中,通過MapperProxy動態(tài)代理咱們的dao, 也就是說, 當咱們執(zhí)行自己寫的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 {
     //關(guān)鍵在這兒
     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) {
   //動態(tài)代理我們寫的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);
 }

通過以上的動態(tài)代理,咱們就可以方便地使用dao接口啦, 就像之前咱們寫的demo那樣:

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

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

3 Excutor

Executor與Sqlsession的關(guān)系就像市長與書記,Sqlsession只是個門面,真正干事的是Executor,Sqlsession對數(shù)據(jù)庫的操作都是通過Executor來完成的。與Sqlsession一樣,Executor也是動態(tài)創(chuàng)建的:

輸入圖片說明

Executor創(chuàng)建的源代碼:

    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的話,創(chuàng)建的Executor只是3中基礎(chǔ)類型之一,BatchExecutor專門用于執(zhí)行批量sql操作,ReuseExecutor會重用statement執(zhí)行sql操作,SimpleExecutor只是簡單執(zhí)行sql沒有什么特別的。開啟cache的話(默認是開啟的并且沒有任何理由去關(guān)閉它),就會創(chuàng)建CachingExecutor,它以前面創(chuàng)建的Executor作為唯一參數(shù)。CachingExecutor在查詢數(shù)據(jù)庫前先查找緩存,若沒找到的話調(diào)用delegate(就是構(gòu)造時傳入的Executor對象)從數(shù)據(jù)庫查詢,并將查詢結(jié)果存入緩存中。

Executor對象是可以被插件攔截的,如果定義了針對Executor類型的插件,最終生成的Executor對象是被各個插件插入后的代理對象。

接下來,咱們才要真正去看sql的執(zhí)行過程了。上面,咱們拿到了MapperProxy, 每個MapperProxy對應一個dao接口, 那么咱們在使用的時候,MapperProxy是怎么做的呢? 源碼奉上:

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

  /**
   * MapperProxy在執(zhí)行時會觸發(fā)此方法
   */
  @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:就像是一個分發(fā)者,他根據(jù)參數(shù)和返回值類型選擇不同的sqlsession方法來執(zhí)行。這樣mapper對象與sqlsession就真正的關(guān)聯(lián)起來了。

  /**
   * 看著代碼不少,不過其實就是先判斷CRUD類型,然后根據(jù)類型去選擇到底執(zhí)行sqlSession中的哪個方法,繞了一圈,又轉(zhuǎn)回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只是一個門面,真正發(fā)揮作用的是executor,對sqlsession方法的訪問最終都會落到executor的相應方法上去。Executor分成兩大類,一類是CacheExecutor,另一類是普通Executor。Executor的創(chuàng)建前面已經(jīng)介紹了,那么咱們就看看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,值在構(gòu)照時傳入。執(zhí)行數(shù)據(jù)庫update操作時,它直接調(diào)用delegate的update方法,執(zhí)行query方法時先嘗試從cache中取值,取不到再調(diào)用delegate的查詢方法,并將查詢結(jié)果存入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專門用于執(zhí)行批量sql操作,ReuseExecutor會重用statement執(zhí)行sql操作,SimpleExecutor只是簡單執(zhí)行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);  
    }  
}  

然后,通過一層一層的調(diào)用,最終會來到doQuery方法, 這兒咱們就隨便找個Excutor看看doQuery方法的實現(xiàn)吧,我這兒選擇了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內(nèi)置的ExecutorType有3種,默認的是simple,該模式下它為每個語句的執(zhí)行創(chuàng)建一個新的預處理語句,單條提交sql;而batch模式重復使用已經(jīng)預處理的語句, 并且批量執(zhí)行所有更新語句,顯然batch性能將更優(yōu);

但batch模式也有自己的問題,比如在Insert操作時,在事務(wù)沒有提交之前,是沒有辦法獲取到自增的id,這在某型情形下是不符合業(yè)務(wù)要求的;

通過走碼和研讀spring相關(guān)文件發(fā)現(xiàn),在同一事務(wù)中batch模式和simple模式之間無法轉(zhuǎn)換,由于本項目一開始選擇了simple模式,所以碰到需要批量更新時,只能在單獨的事務(wù)中進行;

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

//從spring注入原有的sqlSessionTemplate
@Autowired
private SqlSessionTemplate sqlSessionTemplate;

public void testInsertBatchByTrue() {
    //新獲取一個模式為BATCH,自動提交為false的session
    //如果自動提交設(shè)置為true,將無法控制提交的條數(shù),改為最后統(tǒng)一提交,可能導致內(nèi)存溢出
    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) {
        //沒有提交的數(shù)據(jù)可以回滾
        session.rollback();
    } finally {
        session.close();
    }
}

上述代碼沒有使用spring的事務(wù),改手動控制,如果和原spring事務(wù)一起使用,將無法回滾,必須注意,最好單獨使用;

4 StatementHandler

可以看出,Executor本質(zhì)上也是個甩手掌柜,具體的事情原來是StatementHandler來完成的。當Executor將指揮棒交給StatementHandler后,接下來的工作就是StatementHandler的事了。我們先看看StatementHandler是如何創(chuàng)建的:

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;
}  

可以看到每次創(chuàng)建的StatementHandler都是RoutingStatementHandler,它只是一個分發(fā)者,他一個屬性delegate用于指定用哪種具體的StatementHandler??蛇x的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種。選用哪種在mapper配置文件的每個statement里指定,默認的是PreparedStatementHandler。同時還要注意到StatementHandler是可以被攔截器攔截的,和Executor一樣,被攔截器攔截后的對像是一個代理對象。由于mybatis沒有實現(xiàn)數(shù)據(jù)庫的物理分頁,眾多物理分頁的實現(xiàn)都是在這個地方使用攔截器實現(xiàn)的,本文作者也實現(xiàn)了一個分頁攔截器,在后續(xù)的章節(jié)會分享給大家,敬請期待。

StatementHandler創(chuàng)建后需要執(zhí)行一些初始操作,比如statement的開啟和參數(shù)設(shè)置、對于PreparedStatement還需要執(zhí)行參數(shù)的設(shè)置操作等。代碼如下:

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

statement的開啟和參數(shù)設(shè)置沒什么特別的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通過調(diào)用ParameterHandler的setParameters完成參數(shù)的設(shè)置,ParameterHandler隨著StatementHandler的創(chuàng)建而創(chuàng)建,默認的實現(xiàn)是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里設(shè)置參數(shù)的代碼如下:

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完成參數(shù)的設(shè)置。那么什么是合適的TypeHandler呢,它又是如何決斷出來的呢?BaseStatementHandler的構(gòu)造方法里有這么一句:

this.boundSql= mappedStatement.getBoundSql(parameterObject);

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

參數(shù)設(shè)置完畢后,執(zhí)行數(shù)據(jù)庫操作(update或query)。如果是query最后還有個查詢結(jié)果的處理過程。

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

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 到此,原形畢露, PreparedStatement, 這個大家都已經(jīng)滾瓜爛熟了吧
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 結(jié)果交給了ResultSetHandler 去處理
    return resultSetHandler.<E> handleResultSets(ps);
  }

結(jié)果處理使用ResultSetHandler來完成,默認的ResultSetHandler是FastResultSetHandler,它在創(chuàng)建StatementHandler時一起創(chuàng)建,代碼如下:

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內(nèi)部一條記錄一條記錄的處理,在處理每條記錄的每一列時會調(diào)用TypeHandler轉(zhuǎn)換結(jié)果,如下:

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使用的是結(jié)果參數(shù)的屬性類型。因此我們在定義作為結(jié)果的對象的屬性時一定要考慮與數(shù)據(jù)庫字段類型的兼容性。到此, 一次sql的執(zhí)行流程就完了。 我這兒僅拋磚引玉,建議有興趣的去看看Mybatis3的源碼。

? 著作權(quán)歸作者所有

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容