天天看點

MyBatis 源碼解析(二):SqlSession 執行流程

上一篇文章分析了 MyBatis 解析配置檔案初始化的流程,本篇主要分析一下SqlSession 具體的執行流程。

MyBatis 在解析完配置檔案後生成了一個

DefaultSqlSessionFactory

對象,後續執行 SQL 請求的時候都是調用其

openSession

方法獲得

SqlSessison

,相當于一個 SQL 會話。

SqlSession

提供了操作資料庫的一些方法,如

select

update

等。

openSession

先看一下

DefaultSqlSessionFactory

openSession

的代碼:

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 從 configuration 取出配置
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 每個 SqlSession 都有一個單獨的 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();
    }
  }
           

主要代碼在

openSessionFromDataSource

,首先是從

Configuration

中取出相關的配置,生成

Transaction

,接着又建立了一個

Executor

,最後傳回了

DefaultSqlSession

對象。

這裡的

Executor

是什麼呢?它其實是一個執行器,

SqlSession

的操作會交給

Executor

去執行。MyBatis 的

Executor

常用的有以下幾種:

  • SimpleExecutor: 預設的 Executor,每個 SQL 執行時都會建立新的 Statement
  • ResuseExecutor: 相同的 SQL 會複用 Statement
  • BatchExecutor: 用于批處理的 Executor
  • CachingExecutor: 可緩存資料的 Executor,用代理模式包裝了其它類型的 Executor

了解了

Executor

的類型後,看一下

configuration.newExecutor(tx, execType, autoCommit)

的代碼:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 預設是 SimpleExecutor
    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 = new SimpleExecutor(this, transaction);
    }
    // 預設啟動緩存
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
           

MyBatis 預設啟用一級緩存,即同一個

SqlSession

會共用同一個緩存,上面代碼最終傳回的是

CachingExecutor

getMapper

我們看一下SqlSession的getMapper方法實作,一路點過去,可以看到SqlSession -> DefaultSqlSession->Configuration->MapperRegistry->MapperProxyFactory->MapperProxy

public class Configuration {
    ...
	public <T> void addMapper(Class<T> type) {
    	this.mapperRegistry.addMapper(type);
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
}
           
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }
           

在建立了

SqlSession

之後,下一步是生成 Mapper 接口的代理類,代碼如下:

public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
           

可以看出是從

configuration

中取得

Mapper

,最終調用了

MapperProxyFactory

newInstance

MapperProxyFactory

在上一篇文章已經分析過,它是為了給

Mapper

接口生成代理類,其中關鍵的攔截邏輯在

MapperProxy

中,下面是其

invoke

方法:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 過濾一些不需要被代理的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 從緩存中擷取 MapperMethod 然後調用
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
           

MapperProxy

中調用了

MapperMethod

execute

,下面是部分代碼:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
      ...
}
           

可以看出,最終調用了

SqlSession

的對應方法,也就是

DefaultSqlSession

中的方法。

select

先看一下

DefaultSqlSession

select

的代碼:

public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
           

select

中調用了

executor

query

,上面提到,預設的

Executor

CachingExecutor

,看其中的代碼:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 擷取緩存的key
    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, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 調用代理對象的緩存
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
           

首先檢查緩存中是否有資料,如果沒有再調用代理對象的

query

,預設是

SimpleExecutor

Executor

是一個接口,下面有個實作類是

BaseExecutor

,其中實作了其它

Executor

通用的一些邏輯,包括

doQuery

以及

doUpdate

等,其中封裝了 JDBC 的相關操作。

update

update

的執行與

select

類似, 都是從

CachingExecutor

開始,看代碼:

@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 檢查是否需要重新整理緩存
    flushCacheIfRequired(ms);
    // 調用代理類的 update
    return delegate.update(ms, parameterObject);
  }
           

update

會使得緩存的失效,是以第一步是檢查是否需要重新整理緩存,接下來再交給代理類去執行真正的資料庫更新操作。

SqlSession下的四大對象

通過上面的分析,映射器就是一個動态代理對象,進入到了MapperMethod的execute方法,它經過簡單的判斷就進入了SqlSession的删除、更新、插入、選擇等方法,這些方法如何執行是下面要介紹的内容。

Mapper執行的過程是通過Executor、StatementHandler、ParameterHandler和ResultHandler來完成資料庫操作和結果傳回的,了解他們是編寫插件的關鍵:

  • Executor:執行器,由它統一排程其他三個對象來執行對應的SQL;
  • StatementHandler:使用資料庫的Statement執行操作;
  • ParameterHandler:用于SQL對參數的處理;
  • ResultHandler:進行最後資料集的封裝傳回處理;

在MyBatis中存在三種執行器:

  • SIMPLE:簡易執行器,預設的執行器;
  • REUSE:執行重用預處理語句;
  • BATCH:執行重用語句和批量更新,針對批量專用的執行器;

以SimpleExecutor為例,說明執行過程

public class SimpleExecutor extends BaseExecutor {

  /**
  * 執行查詢操作
  */
  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);
    }
  }
  
  /**
  * 初始化StatementHandler
  */
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }
  
  /**
  * 執行查詢
  */
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }
}
           

可以看到最後會委托給StatementHandler會話器進行處理,它是一個接口,實際建立的是RoutingStatementHandler對象,但它不是真實的服務對象,它是通過擴充卡模式找到對應的StatementHandler執行的。在MyBatis中,StatementHandler和Executor一樣分為三種:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。

Executor會先調用StatementHandler的prepare方法預編譯SQL語句,同時設定一些基本運作的參數。然後調用parameterize()方法啟用ParameterHandler設定參數,完成預編譯,跟着執行查詢,用ResultHandler封裝結果傳回給調用者。

總結

本文主要分析了

SqlSession

的執行流程,結合上一篇文章基本了解了 MyBatis 的運作原理。

繼續閱讀