mybatis_sql執行過程源碼解析

測試代碼

public class MybatisTests {

    SqlSessionFactory factory;

    @Before
    public void loadConfig() {
        try (InputStream inputStream = Resources.getResourceAsStream("mybatis2.xml")) {
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testInsert2() {
        SqlSession session = factory.openSession();
        User user = session.selectOne("findUserById", 1);
        System.out.println(user);
        session.close();
    }
}

經過跟蹤testInsert2()方法去查看mybatis內部的sql執行過程。sql

獲取SqlSession對象

DefaultSqlSessionFactory的重載方法中支持傳入autoCommit(是否自動提交),transactionIsolationLevel(事務隔離級別),ExecutorType(sql執行器類型:SIMPLE, REUSE, BATCH),Connection 等來建立SqlSession對象。數據庫

事務隔離級別:緩存

public enum TransactionIsolationLevel {
  NONE(Connection.TRANSACTION_NONE),
  READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
  READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
  REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
  SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
}

進入openSession()方法,實際上執行的方法是factory.openSessionFromDataSource()方法:session

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
}

調用 session.selectOne()方法進行查詢

@Override
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
}

繼續跟蹤,進入的是DefaultSqlSession.selectList()方法:mybatis

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
}

先根據key從Configuration中獲取到MappedStatement對象。而後執行excutor.query()方法。app

先跟蹤wrapCollection(parameter)方法:ide

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
}

wrapCollection(parameter)方法就是在處理集合類型的參數:若是Collection對象,就放到一個Map對象中,key爲"collection",若是是list對象,key爲"list",若是是array對象,key爲"array".不然就是本來的對象。(這裏也就能理解以前在mapper.xml中傳參爲集合的時候爲何名稱必定要是collectin,list之類的了)。源碼分析

Executor執行器的操做流程

跟蹤excutor.query()方法:(sqlSession委託executor去真正執行數據庫的增刪改查操做),這裏進入的是CachingExecutor實現類。在建立sqlSession的時候從源碼分析中能夠知道默認使用的是CachingExecutor實現類。測試

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

getBoundSql()方法負責構造sql語句和輸入參數。 fetch

BoundSql對象中已經將sql轉爲了?形式,而且parameterMappings中放了sql入參的各類屬性,parameterObject放的是真正的參數對象。

createCacheKey()方法先略過。

繼續跟蹤query方法:

@Override
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方法(委託給BaseExector去繼續處理)。

也就是說CachingExecutor只負責處理二級緩存,若是沒有二級緩存,則委託給BaseExector處理。

再進去query()方法:

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 {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
}

localCache這個對象負責的是一級緩存,若是一級緩存中沒有數據,就進入queryFromDatabase()方法去數據庫中查詢。

跟蹤queryFromDatabase()方法:

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

這個方法內進行了防止緩存穿透的防範。(先在緩存中存儲一個佔位符,這個緩存的數據主要是爲了在查詢的過程當中,對外提供緩存數據,以此來保護數據庫。)

跟蹤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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

跟蹤newStatementHandler()方法,發現這裏面實際上是在構造PreparedStatementHandler或者StatementHandler對象。

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

跟蹤prepareStatement()方法,發現裏面就是在構造prepareStatement對象或者Statement對象併爲其設置參數。 而後後面的query()方法就是直接調用JDBC的代碼了。

總結

相關文章
相關標籤/搜索