91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Mybatis SqlSession執行流程介紹

發布時間:2021-07-12 09:10:11 來源:億速云 閱讀:122 作者:chen 欄目:開發技術

這篇文章主要講解了“Mybatis SqlSession執行流程介紹”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Mybatis SqlSession執行流程介紹”吧!

目錄
  • Mybatis執行SQL流程

    • SqlSession

    • Executor

  • Mybatis之Executor

    • Mybatis之StatementHandler

      • 進入ResultSetHandler

    Mybatis執行SQL流程

    在看源碼之前,我們需要了解一些基本知識,如果您沒有閱讀Mybatis SqlSessionFactory 初始化原理,可以先閱讀Mybatis SqlSessionFactory 初始化原理這篇文章,這用更有助于我們理解接下來的文章
    在看源碼之前,我們需要了解一些基本知識

    SqlSession

    SqlSession是一個接口,它有兩個實現類:
     - DefaultSqlSession:默認實現類
     - SqlSessionManager:已經棄用的實現類,所以我們不需要關注他
    SqlSession是與數據庫交互的頂層類,通常與ThreadLocal綁定,一個會話使用一個SqlSession,SqlSession是線程不安全的,使用完畢需要close()

    public class DefaultSqlSession implements SqlSession {
     private final Configuration configuration;
     private final Executor executor;
    }

    SqlSession中最重要的兩個變量:
     - Configuration:核心配置類,也是初始化時傳過來的
     - Executor:實際執行SQL的執行器

    Executor

    Executor是一個接口,有三個實現類
     - BatchExecutor 重用語句,并執行批量更新
     - ReuseExecutor 重用預處理語句prepared statements
     - SimpleExecutor 普通的執行器,默認使用

    了解完基本知識后,我們接著往下看代碼。

    當創建完SqlSessionFactory后,就可以創建SqlSession,然后使用SqlSession進行增刪改查:

    // 1. 讀取配置文件,讀成字節輸入流,注意:現在還沒解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析配置文件,封裝Configuration對象   創建DefaultSqlSessionFactory對象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> objects = sqlSession.selectList("namespace.id");

    我們先去看openSession()方法,創建了SqlSession

    //6. 進入openSession方法
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()傳遞的是SimpleExecutor
        // level:數據庫事物級別,null
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    //7. 進入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();
        }
    }

    通過源碼可以清晰的看到,會話工廠創建了Environment,Transaction,Executor,DefaultSqlSession對象,并且對于會話對象來說,他的autoCommit默認為false,默認不自動提交。

    然后我回到原來的代碼,接著就需要使用SqlSession進行增刪改查操作了

    所以我們進入selectList()查看

    //8.進入selectList方法,多個重載方法
    @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 statement, 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();
        }
    }

    selectList有多個重載方法,進入到最終方法后,我們可以看到它做了兩件事

    • 通過statementId,從Configuration中取MappedStatement對象,就是存放了sql語句,返回值類型,輸入值類型的對象

    • 然后委派Executor執行器去執行具體的增刪改查方法

    所以,對于實際JDBC的操作,我們還需要進入Executor中查看

    Mybatis之Executor

    我們繼續從剛剛selectList源碼中,進入Executor查看

    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    //此方法在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);
    }

    拆分成了三大步:

    (1)先調用MappedStatement的getBoundSql方法,獲取解析后的SQL語句,解析工作是由SqlSourceBuilder完成的

    什么叫解析后的SQL語句呢?因為Mybatis編寫SQL語句時,會用到動態SQL,比如#{}占位符,這種占位符JDBC是不認識的,所以需要將其轉換成?占位符,并且將其內部的字段名存儲起來,后面填充參數的時候好使用反射獲取值。

    /**
     * 執行解析原始 SQL ,成為 SqlSource 對象
     *
     * @param originalSql 原始 SQL
     * @param parameterType 參數類型
     * @param additionalParameters 附加參數集合。可能是空集合,也可能是 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合
     * @return SqlSource 對象
     */
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // 創建 ParameterMappingTokenHandler 對象
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 創建 GenericTokenParser 對象
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // 執行解析
        String sql = parser.parse(originalSql);
        // 創建 StaticSqlSource 對象
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

    上面代碼就可以看到,會將拆分#{和},進行解析

    (2)根據查詢條件,創建緩存key,用來接下來去緩存查找是否有已經執行過的結果

    (3)調用重載query()方法

    接著我們進入重載方法查看:

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

    通過代碼可以看到,對于實際與JDBC交互的代碼,Executor也懶得搞,又像SqlSession一樣,委派給小弟StatementHandler了。

    Mybatis之StatementHandler

    我們從剛剛的Executor的代碼查看

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

    可以看到,這里創建完StatementHandler后,回調用prepareStatement()方法,用來創建Statement對象

    我們進入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;
    }
    
    @Override
    public void parameterize(Statement statement) throws SQLException {
        //使用ParameterHandler對象來完成對Statement的設值
        parameterHandler.setParameters((PreparedStatement) statement);
    }

    這里可以看到,它實際是使用ParameterHandler來設置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是具體進行參數設置的對象

    所以handler.prepare(connection, transaction.getTimeout());方法,就是使用ParameterHandler來對占位符位置的參數進行值設置

    然后我們回到Executor,查看handler.query()方法

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

    代碼很簡單,這里直接使用JDBC的PreparedStatement來進行SQL執行,然后使用ResultSetHandler進行結果數據封裝處理。

    進入ResultSetHandler

    @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` 只在存儲過程中使用,本系列暫時不考慮,忽略即可
        // ···
    
        // 如果是 multipleResults 單元素,則取首元素返回
        return collapseSingleResultList(multipleResults);
    }
    
     // 處理 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());
        }
    }

    代碼比較多,實際最重要的代碼就是

    // 添加 defaultResultHandler 的處理的結果,到 multipleResults 中
    multipleResults.add(defaultResultHandler.getResultList());

    將處理后的結果封裝到集合中返回,這樣基本Mybatis邏輯就走完了.

    我們來回顧一下,都用到了哪些類

    Mybatis SqlSession執行流程介紹

    簡單總結
    SqlSessionFactoryBuilder:

    • 解析核心配置文件,創建Configuration

      • XMLConfigBuilder.parse():解析核心配置文件

      • XMLMapperBuilder.parse():解析映射配置文件MappedStatement

    • 創建SqlSessionFactory,默認創建DefaultSqlSessionFactory

    SqlSessionFactory:

    • openSession():構建Executor,SqlSession等

    SqlSession:

    • 根據statementId獲取MappedStatement

    • 委派給Executor執行器執行

    Executor:

    • 使用SqlSourceBuilder,將SQL解析成JDBC認可的

    • 查詢緩存,是否存在結果

    • 結果不存在,委派給StatementHandler處理器

    StatementHandler:

    • PreparedStatement:處理參數,將參數賦值到占位符上

      • TypeHandler:具體設置值的類

    • ResultSetHandler:封裝結果集,封裝成設置的返回值類型

      • TypeHandler:根據結果集,取出對應列

    感謝各位的閱讀,以上就是“Mybatis SqlSession執行流程介紹”的內容了,經過本文的學習后,相信大家對Mybatis SqlSession執行流程介紹這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    泰和县| 鸡泽县| 拉孜县| 洛阳市| 大田县| 株洲市| 安义县| 如皋市| 左贡县| 宁津县| 改则县| 赣榆县| 清镇市| 德令哈市| 荆州市| 海淀区| 大新县| 健康| 藁城市| 随州市| 玉山县| 封丘县| 临颍县| 无棣县| 旌德县| 静安区| 石台县| 乡宁县| 泗洪县| 西和县| 浦县| 从化市| 巴彦淖尔市| 云龙县| 桂阳县| 吉木乃县| 拜城县| 万安县| 鄂温| 临江市| 佛学|