1. 程式人生 > 實用技巧 >Mybatis執行SQL的流程

Mybatis執行SQL的流程

SqlSession

SqlSession是一個介面,它有兩個實現類:DefaultSqlSession (預設)和

SqlSessionManager (棄用,不做介紹)

SqlSession是MyBatis中用於和資料庫互動的頂層類,通常將它與ThreadLocal繫結,一個會話使用一 個

SqlSession,並且在使用完畢後需要close (SqlSession是執行緒不安全的,因此我們使用完後要記得close)

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration; 
    private final Executor executor;
    //SqlSession中的兩個最重要的引數,configuration與初始化時的相同,Executor為執行器
	//......
}

Executor

Executor也是一個介面,他有三個常用的實現類:

BatchExecutor (重用語句並執行批量更新)

ReuseExecutor (重用預處理語句 prepared statements)

SimpleExecutor (普通的執行器,預設)

原始碼分析:

1. SqlSession sqlSession = sqlSessionFactory.openSession();

// 1. 讀取配置檔案,讀成位元組輸入流,注意:現在還沒解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. 解析配置檔案,封裝Configuration物件   建立DefaultSqlSessionFactory物件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

// 3. 生產了DefaultSqlsession例項物件   設定了事務不自動提交  完成了executor物件的建立
SqlSession sqlSession = sqlSessionFactory.openSession();
//進入openSession方法
@Override
public SqlSession openSession() {
    //getDefaultExecutorType()傳遞的是SimpleExecutor
    //第一個引數:當前預設執行器的型別
    //第二個引數:資料庫的隔離級別
    //第三個引數:是否自動提交事務
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

//configuration.getDefaultExecutorType() --- 當前預設執行器的型別
public ExecutorType getDefaultExecutorType() {
    return defaultExecutorType;
}

protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//進入openSessionFromDataSource。
//ExecutorType 為Executor的型別,TransactionIsolationLevel為事務隔離級別,autoCommit是否開啟事務
//openSession的多個過載方法可以指定獲得的SeqSession的Executor型別和事務的處理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 獲得 Environment (執行環境)物件 
        final Environment environment = configuration.getEnvironment();
        // 建立 Transaction (事務)物件
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 建立 Executor 物件
        final Executor executor = configuration.newExecutor(tx, execType);
        // 建立 DefaultSqlSession 物件
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        // 如果發生異常,則關閉 Transaction 物件
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

這一步我們就完成了SqlSession物件的建立

2. List objects = sqlSession.selectList("namespace.id");
// 4.(1)根據statementid來從Configuration中map集合中獲取到了指定的MappedStatement物件
   //(2)將查詢任務委派了executor執行器
List<Object> objects = sqlSession.selectList("namespace.id");
//DefaultSqlSession.java
@Override
public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
}

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statementId, Object parameter, RowBounds rowBounds) {
    try {
        // 獲得 MappedStatement 物件
        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();
    }
}

我們在生產DefaultSqlsession例項物件的時候傳入了configuration,而這一步就是從configuration物件中根據statementId取出MappedStatement物件(MappedStatement 封裝了這條SQL的一些資訊)

然後執行executor.query()方法,把查詢任務委派給執行器executor

3. executor.query()

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

//↓↓↓↓↓↓↓↓

// 查詢,帶 ResultHandler
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

//↓↓↓↓↓↓↓↓

//此方法在SimpleExecutor的父類BaseExecutor中實現
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //根據傳入的引數動態獲得SQL語句,最後返回用BoundSql物件表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //為本次查詢建立快取的Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 查詢
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
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());
    // 已經關閉,則丟擲 ExecutorException 異常
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    // 清空本地快取,如果 queryStack 為零,並且要求清空本地快取。
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        // queryStack + 1
        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 - 1
        queryStack--;
    }
    if (queryStack == 0) {
        // 執行延遲載入
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        // 清空 deferredLoads
        deferredLoads.clear();
        // 如果快取級別是 LocalCacheScope.STATEMENT ,則進行清理
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

queryFromDatabase

// 從資料庫中讀取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 在快取中,新增佔位物件。此處的佔位符,和延遲載入有關,可見 `DeferredLoad#canLoad()` 方法
    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

@Override
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();
        // 傳入引數建立StatementHanlder物件來執行查詢
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 建立jdbc中的statement物件
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 執行 StatementHandler  ,進行讀操作
        return handler.query(stmt, resultHandler);
    } finally {
        // 關閉 StatementHandler 物件
        closeStatement(stmt);
    }
}

prepareStatement()

// 初始化 StatementHandler 物件
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 獲得 Connection 物件
    Connection connection = getConnection(statementLog);
    // 建立 Statement 或 PrepareStatement 物件
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 設定 SQL 上的引數,例如 PrepareStatement 物件上的佔位符
    handler.parameterize(stmt);
    return stmt;
}

4.StatementHandler

StatementHandler物件主要完成兩個工作:設定引數(prepareStatement),解析返回結果集(resultSetHandler)

對於JDBC的PreparedStatement型別的物件,建立的過程中,我們使用的是SQL語句字串會包含若干個?佔位符,我們其後再對佔位符進行設值。

StatementHandler通過parameterize(statement)方法對 Statement 進行設值;

StatementHandler 通過 List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement物件返回的resultSet封裝成List;

在哪裡設定引數?

在prepareStatement方法中有一句話:

handler.parameterize(stmt);

//PreparedStatementHandler.java
@Override
public void parameterize(Statement statement) throws SQLException {
    //使用ParameterHandler物件來完成對Statement的設值
    parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 遍歷 ParameterMapping 陣列
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
            // 獲得 ParameterMapping 物件
            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);
                }
                // 獲得 typeHandler、jdbcType 屬性
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                // 設定 ? 佔位符的引數
                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException | SQLException e) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

typeHandler.setParameter(ps, i + 1, value, jdbcType);

可以看到,我們實際設定引數是通過parameterHandler設值的。並且呼叫了 typeHandler.setParameter()方法來給?賦值。同時指定了jdbcType。

回到doQuery()方法,進入到handler.query(stmt, resultHandler)方法,找到其實現類PreparedStatementHandler

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執行查詢
    ps.execute();
    // 處理返回結果
    return resultSetHandler.handleResultSets(ps);
}

如何處理結果集?

進入 return resultSetHandler.handleResultSets(ps)

//
// HANDLE RESULT SETS
//
// 處理 {@link java.sql.ResultSet} 結果集
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    // 多 ResultSet 的結果集合,每個 ResultSet 對應一個 Object 物件。而實際上,每個 Object 是 List<Object> 物件。
    // 在不考慮儲存過程的多 ResultSet 的情況,普通的查詢,實際就一個 ResultSet ,也就是說,multipleResults 最多就一個元素。
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 獲得首個 ResultSet 物件,並封裝成 ResultSetWrapper 物件
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    // 獲得 ResultMap 陣列
    // 在不考慮儲存過程的多 ResultSet 的情況,普通的查詢,實際就一個 ResultSet ,也就是說,resultMaps 就一個元素。
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount); // 校驗
    while (rsw != null && resultMapCount > resultSetCount) {
        // 獲得 ResultMap 物件
        ResultMap resultMap = resultMaps.get(resultSetCount);
        // 處理 ResultSet ,將結果新增到 multipleResults 中
        handleResultSet(rsw, resultMap, multipleResults, null);
        // 獲得下一個 ResultSet 物件,並封裝成 ResultSetWrapper 物件
        rsw = getNextResultSet(stmt);
        // 清理
        cleanUpAfterHandlingResultSet();
        // resultSetCount ++
        resultSetCount++;
    }

    // 因為 `mappedStatement.resultSets` 只在儲存過程中使用,本系列暫時不考慮,忽略即可
    String[] resultSets = mappedStatement.getResultSets();
    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++;
        }
    }

    // 如果是 multipleResults 單元素,則取首元素返回
    return collapseSingleResultList(multipleResults);
}
/**
 * 將 ResultSet 物件,封裝成 ResultSetWrapper 物件
 *
 * @param stmt Statement 物件
 * @return ResultSetWrapper 物件
 */
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    // 可以忽略
    while (rs == null) {
        // move forward to get the first resultset in case the driver
        // doesn't return the resultset as the first result (HSQLDB 2.1)
        if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
        } else {
            if (stmt.getUpdateCount() == -1) {
                // no more results. Must be no resultset
                break;
            }
        }
    }
    // 將 ResultSet 物件,封裝成 ResultSetWrapper 物件
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
// 處理 ResultSet ,將結果新增到 multipleResults 中
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        // 暫時忽略,因為只有儲存過程的情況,呼叫該方法,parentMapping 為非空
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            // 如果沒有自定義的 resultHandler ,則建立預設的 DefaultResultHandler 物件
            if (resultHandler == null) {
                // 建立 DefaultResultHandler 物件
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                // 處理 ResultSet 返回的每一行 Row
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                // 新增 defaultResultHandler 的處理的結果,到 multipleResults 中
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                // 處理 ResultSet 返回的每一行 Row
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
        // issue #228 (close resultsets)
        // 關閉 ResultSet 物件
        closeResultSet(rsw.getResultSet());
    }
}