欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解MyBatis中Executor執(zhí)行SQL語句的過程

 更新時(shí)間:2023年07月10日 10:33:00   作者:半夏之沫  
MyBatis中獲取SqlSession時(shí)會(huì)創(chuàng)建執(zhí)行器Executor并存放在SqlSession中,本篇文章將以MapperMethod的execute() 方法作為起點(diǎn),對(duì)MyBatis中的一次實(shí)際執(zhí)行請(qǐng)求進(jìn)行說明,并結(jié)合源碼對(duì)執(zhí)行器Executor的原理進(jìn)行闡釋

前言

詳解MyBatis的SqlSession獲取流程文章中已經(jīng)知道,MyBatis中獲取SqlSession時(shí)會(huì)創(chuàng)建執(zhí)行器Executor并存放在SqlSession中,通過SqlSession可以獲取映射接口的動(dòng)態(tài)代理對(duì)象,可以用下圖進(jìn)行概括

所以,映射接口的動(dòng)態(tài)代理對(duì)象實(shí)際執(zhí)行方法時(shí),執(zhí)行的請(qǐng)求最終會(huì)由MapperMethodexecute() 方法完成。從MapperMethodexecute() 方法開始,后續(xù)執(zhí)行流程,可以用下圖進(jìn)行示意。

本篇文章將以MapperMethodexecute() 方法作為起點(diǎn),對(duì)MyBatis中的一次實(shí)際執(zhí)行請(qǐng)求進(jìn)行說明,并結(jié)合源碼對(duì)執(zhí)行器Executor的原理進(jìn)行闡釋。

本篇文章不會(huì)對(duì)MyBatis中的緩存進(jìn)行說明,關(guān)于MyBatis中的一級(jí)緩存二級(jí)緩存相關(guān)內(nèi)容,會(huì)在后續(xù)的文章中單獨(dú)進(jìn)行分析,為了屏蔽MyBatis中的二級(jí)緩存的干擾,需要在MyBatis的配置文件中添加如下配置以禁用二級(jí)緩存。

<settings>
    <setting name="cacheEnabled" value="false"/>
</settings>

MyBatis版本:3.5.6

正文

本節(jié)將以一個(gè)實(shí)際的查詢例子,以單步跟蹤并結(jié)合源碼的方法,對(duì)MyBatis的一次實(shí)際執(zhí)行請(qǐng)求進(jìn)行說明。給定映射接口如下所示。

public interface BookMapper {
    Book selectBookById(int id);
}

給定映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.learn.dao.BookMapper">
    <resultMap id="bookResultMap" type="com.mybatis.learn.entity.Book">
        <result property="bookName" column="b_name"/>
        <result property="bookPrice" column="b_price"/>
    </resultMap>
    <select id="selectBookById" resultMap="bookResultMap">
        SELECT
            b.id, 
            b.b_name, 
            b.b_price
        FROM book b
        WHERE b.id=#{id}
    </select>
</mapper>

MyBatis的執(zhí)行代碼如下所示。

public class MybatisTest {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream(resource));
        // 獲取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 獲取映射接口的動(dòng)態(tài)代理對(duì)象
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
        // 執(zhí)行一次查詢操作
        System.out.println(bookMapper.selectBookById(1));
    }
}

基于上述的映射接口,映射文件和執(zhí)行代碼,最終執(zhí)行查詢操作時(shí),會(huì)調(diào)用到MapperMethodexecute() 方法并進(jìn)入查詢的邏輯分支,這部分源碼如下所示。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        // ......
        case SELECT:
            // 根據(jù)實(shí)際執(zhí)行的方法的返回值的情況進(jìn)入不同的邏輯分支
            if (method.returnsVoid() && method.hasResultHandler()) {
                // 無返回值情況
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // 返回值為集合的情況
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                // 返回值為map的情況
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                // 返回值為迭代器的情況
                result = executeForCursor(sqlSession, args);
            } else {
                // 上述情況之外的情況
                // 將方法的入?yún)⑥D(zhuǎn)換為Sql語句的參數(shù)
                Object param = method.convertArgsToSqlCommandParam(args);
                // 調(diào)用DefaultSqlSession的selectOne()方法執(zhí)行查詢操作
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        // ......
    }
    // ......
    return result;
}

已知映射接口中的每個(gè)方法都會(huì)對(duì)應(yīng)一個(gè)MapperMethod,MapperMethod中的SqlCommand會(huì)指示該方法對(duì)應(yīng)的MappedStatement信息和類型信息(SELECTUPDATE等),MapperMethod中的MethodSignature會(huì)存儲(chǔ)該方法的參數(shù)信息和返回值信息,所以在上述的MapperMethodexecute() 方法中,首先根據(jù)SqlCommand的指示的類型進(jìn)入不同的邏輯分支,本示例中會(huì)進(jìn)入SELECT的邏輯分支,然后又會(huì)根據(jù)MethodSignature中指示的方法返回值情況進(jìn)入不同的查詢分支,本示例中的方法返回值既不是集合,map或迭代器,也不是空,所以會(huì)進(jìn)入查詢一條數(shù)據(jù)的查詢分支。

MapperMethod中的execute() 方法中會(huì)調(diào)用到DefaultSqlSessionselectOne() 方法執(zhí)行查詢操作,該方法實(shí)現(xiàn)如下所示。

@Override
public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
    // 查詢操作會(huì)由selectList()完成
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        // 查詢結(jié)果只有一個(gè)時(shí),返回查詢結(jié)果
        return list.get(0);
    } else if (list.size() > 1) {
        // 查詢結(jié)果大于一個(gè)時(shí),報(bào)錯(cuò)
        throw new TooManyResultsException(
            "Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

DefaultSqlSessionselectOne() 方法中會(huì)將查詢請(qǐng)求交由DefaultSqlSessionselectList() 方法完成,如果selectList() 方法返回的結(jié)果集合中只有一個(gè)返回值,就將這個(gè)返回值返回,如果多于一個(gè)返回值,就報(bào)錯(cuò)。DefaultSqlSessionselectList() 方法如下所示。

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 從Configuration中的mappedStatements緩存中獲取MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 調(diào)用Executor的query()方法執(zhí)行查詢操作
        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();
    }
}

DefaultSqlSessionselectList() 方法中,會(huì)先根據(jù)statement參數(shù)值在Configuration中的mappedStatements緩存中獲取MappedStatement,statement參數(shù)值其實(shí)就是MapperMethod中的SqlCommandname字段,是MappedStatementmappedStatements緩存中的唯一標(biāo)識(shí)。獲取到MappedStatement后,就會(huì)調(diào)用Executorquery() 方法執(zhí)行查詢操作,因?yàn)榻昧硕?jí)緩存,所以這里的Executor實(shí)際上為SimpleExecutor。本示例中單步跟蹤到這里時(shí),數(shù)據(jù)如下所示。

SimpleExecutor的類圖如下所示。

SimpleExecutorBaseExecutor之間使用了模板設(shè)計(jì)模式,調(diào)用SimpleExecutorquery() 方法時(shí)會(huì)調(diào)用到BaseExecutorquery() 方法,如下所示。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
                       ResultHandler resultHandler) throws SQLException {
    // 獲取Sql語句
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 生成CacheKey
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 調(diào)用重載的query()方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

繼續(xù)看BaseExecutor中的重載的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());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        // 先從一級(jí)緩存中根據(jù)CacheKey命中查詢結(jié)果
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            // 成功命中,則返回緩存中的查詢結(jié)果
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 未命中,則直接查數(shù)據(jù)庫
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
    }
    return list;
}

上述的query() 方法大部分邏輯是在為MyBatis中的一級(jí)緩存服務(wù),這里暫時(shí)不分析,除開緩存的邏輯,上述query() 方法做的事情可以概括為:

  • 先從緩存中獲取查詢結(jié)果;
  • 獲取到則返回緩存中的查詢結(jié)果;
  • 否則直接查詢數(shù)據(jù)庫。

下面分析直接查詢數(shù)據(jù)庫的邏輯,queryFromDatabase() 方法的實(shí)現(xiàn)如下所示。

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 {
        // 調(diào)用doQuery()進(jìn)行查詢操作
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    // 將查詢結(jié)果添加到一級(jí)緩存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    // 返回查詢結(jié)果
    return list;
}

上述queryFromDatabase() 方法中,會(huì)調(diào)用BaseExecutor定義的抽象方法doQuery() 進(jìn)行查詢,本示例中,doQuery() 方法由SimpleExecutor進(jìn)行了實(shí)現(xiàn),如下所示。

@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();
        // 創(chuàng)建RoutingStatementHandler
        StatementHandler handler = configuration.newStatementHandler(
                wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 實(shí)例化Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 執(zhí)行查詢
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

上述的doQuery() 方法中,做了三件事情:

  • 第一件事情是創(chuàng)建RoutingStatementHandler;
  • 第二件事情是實(shí)例化Statement;
  • 第三件事情是執(zhí)行查詢。

第一件事情:創(chuàng)建RoutingStatementHandler

實(shí)際上RoutingStatementHandler正如其名字所示,僅僅只是做一個(gè)路由轉(zhuǎn)發(fā)的作用,在創(chuàng)建RoutingStatementHandler時(shí),會(huì)根據(jù)映射文件中CURD標(biāo)簽上的statementType屬性決定創(chuàng)建什么類型的StatementHandler并賦值給RoutingStatementHandler中的delegate字段,后續(xù)對(duì)RoutingStatementHandler的所有操作均會(huì)被其轉(zhuǎn)發(fā)給delegate。

此外在初始化SimpleStatementHandler,PreparedStatementHandlerCallableStatementHandler時(shí)還會(huì)一并初始化ParameterHandlerResultSetHandler

映射文件中CURD標(biāo)簽上的statementType屬性與StatementHandler的對(duì)應(yīng)關(guān)系如下。

statementType屬性對(duì)應(yīng)的StatementHandler作用
STATEMENTSimpleStatementHandler直接操作SQL,不進(jìn)行預(yù)編譯
PREPAREDPreparedStatementHandler預(yù)編譯SQL
CALLABLECallableStatementHandler執(zhí)行存儲(chǔ)過程

RoutingStatementHandlerSimpleStatementHandler,PreparedStatementHandlerCallableStatementHandler的關(guān)系可以用下圖示意。

在創(chuàng)建RoutingStatementHandler之后,還會(huì)為RoutingStatementHandler植入插件邏輯。ConfigurationnewStatementHandler() 方法實(shí)現(xiàn)如下。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
            Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 創(chuàng)建RoutingStatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(
                executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 為RoutingStatementHandler植入插件邏輯
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

第二件事情:實(shí)例化Statement

prepareStatement() 方法實(shí)現(xiàn)如下所示。

private Statement prepareStatement(StatementHandler handler, Log statementLog) 
                throws SQLException {
    Statement stmt;
    // 獲取到Connection對(duì)象并為Connection對(duì)象生成動(dòng)態(tài)代理對(duì)象
    Connection connection = getConnection(statementLog);
    // 通過Connection對(duì)象的動(dòng)態(tài)代理對(duì)象實(shí)例化Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

prepareStatement() 方法中首先會(huì)從Transaction中將數(shù)據(jù)庫連接對(duì)象Connection對(duì)象獲取出來并為其生成動(dòng)態(tài)代理對(duì)象以實(shí)現(xiàn)日志打印功能的增強(qiáng),然后會(huì)通過Connection的動(dòng)態(tài)代理對(duì)象實(shí)例化Statement,最后會(huì)處理Statement中的占位符,比如將PreparedStatement中的?替換為實(shí)際的參數(shù)值。

第三件事情:執(zhí)行查詢

本篇文章的示例中,映射文件的CURD標(biāo)簽沒有對(duì)statementType屬性進(jìn)行設(shè)置,因此查詢的操作最終會(huì)被RoutingStatementHandler路由轉(zhuǎn)發(fā)給PreparedStatementHandlerquery() 方法,如下所示。

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) 
            throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 調(diào)用到JDBC的邏輯了
    ps.execute();
    // 調(diào)用ResultSetHandler處理查詢結(jié)果
    return resultSetHandler.handleResultSets(ps);
}

如上所示,在PreparedStatementHandlerquery() 方法中就會(huì)調(diào)用到JDBC的邏輯向數(shù)據(jù)庫進(jìn)行查詢,最后還會(huì)使用已經(jīng)初始化好并植入了插件邏輯的ResultSetHandler處理查詢結(jié)果并返回。

至此,對(duì)MyBatis的一次實(shí)際執(zhí)行請(qǐng)求的說明到此為止,本篇文章中的示例以查詢?yōu)槔鰟h改大體類似,故不再贅述。

總結(jié)

MyBatis中的執(zhí)行器Executor會(huì)在創(chuàng)建SqlSession時(shí)一并被創(chuàng)建出來并被存放于SqlSession中,如果禁用了二級(jí)緩存,則Executor實(shí)際為SimpleExecutor,否則為CachingExecutor

MyBatis中的一次實(shí)際執(zhí)行,會(huì)由所執(zhí)行方法對(duì)應(yīng)的MapperMethodexecute() 方法完成。在execute() 方法中,會(huì)根據(jù)執(zhí)行操作的類型(增改刪查)調(diào)用SqlSession中的相應(yīng)的方法,例如insert(),update(),delete()select() 等。MapperMethod在這其中的作用就是MapperMethod關(guān)聯(lián)著本次執(zhí)行方法所對(duì)應(yīng)的SQL語句以及入?yún)⒑统鰠⒌刃畔ⅰ?/p>

SqlSessioninsert()update(),delete()select() 等方法中,SqlSession會(huì)將與數(shù)據(jù)庫的操作交由執(zhí)行器Executor來完成。無論是在SimpleExecutor還是CachingExecutor中,如果拋開緩存相關(guān)的邏輯,這些Executor均會(huì)先根據(jù)映射文件中CURD標(biāo)簽的statementType字段創(chuàng)建相應(yīng)的StatementHandler,創(chuàng)建StatementHandler的過程中還會(huì)一并將處理參數(shù)和處理結(jié)果的ParameterHandlerResultSetHandler創(chuàng)建出來,創(chuàng)建好StatementHandler之后,會(huì)基于StatementHandler實(shí)例化Statement,最后在StatementHandler中基于實(shí)例化好的Statement完成和數(shù)據(jù)庫的交互,基于創(chuàng)建好的ResultSetHandler處理交互結(jié)果并將結(jié)果返回。

以上就是詳解MyBatis中Executor執(zhí)行SQL語句的過程的詳細(xì)內(nèi)容,更多關(guān)于MyBatis Executor執(zhí)行SQL語句的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • redis.clients.jedis.exceptions.JedisAskDataException異常解決

    redis.clients.jedis.exceptions.JedisAskDataException異常解決

    redis.clients.jedis.exceptions.JedisAskDataExceptio異常是在使用Jedis客戶端與Redis集群交互時(shí)遇到的一種重定向異常,本文就來介紹一下解決方法,感興趣的可以了解一下
    2024-05-05
  • RocketMQ中消費(fèi)者的消費(fèi)進(jìn)度管理

    RocketMQ中消費(fèi)者的消費(fèi)進(jìn)度管理

    這篇文章主要介紹了RocketMQ中消費(fèi)者的消費(fèi)進(jìn)度管理,業(yè)務(wù)實(shí)現(xiàn)消費(fèi)回調(diào)的時(shí)候,當(dāng)且僅當(dāng)此回調(diào)函數(shù)返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS ,RocketMQ才會(huì)認(rèn)為這批消息(默認(rèn)是1條)是消費(fèi)完成的,需要的朋友可以參考下
    2023-10-10
  • Hibernate實(shí)現(xiàn)悲觀鎖和樂觀鎖代碼介紹

    Hibernate實(shí)現(xiàn)悲觀鎖和樂觀鎖代碼介紹

    這篇文章主要介紹了Hibernate實(shí)現(xiàn)悲觀鎖和樂觀鎖的有關(guān)內(nèi)容,涉及hibernate的隔離機(jī)制,以及實(shí)現(xiàn)悲觀鎖和樂觀鎖的代碼實(shí)現(xiàn),需要的朋友可以了解下。
    2017-09-09
  • Java concurrency之Condition條件_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java concurrency之Condition條件_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Condition的作用是對(duì)鎖進(jìn)行更精確的控制。下面通過本文給大家分享Java concurrency之Condition條件的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-06-06
  • Java 如何從list中刪除符合條件的數(shù)據(jù)

    Java 如何從list中刪除符合條件的數(shù)據(jù)

    這篇文章主要介紹了Java 如何從list中刪除符合條件的數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot 引?MybatisGenerator的實(shí)現(xiàn)步驟

    SpringBoot 引?MybatisGenerator的實(shí)現(xiàn)步驟

    本文主要介紹了SpringBoot 引?MybatisGenerator的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • 解決Java 部署Tomcat時(shí)使用jni和jna調(diào)用DLL文件的問題

    解決Java 部署Tomcat時(shí)使用jni和jna調(diào)用DLL文件的問題

    這篇文章主要介紹了解決Java 部署Tomcat時(shí)使用jni和jna調(diào)用DLL文件的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • Java類的序列化版本唯一標(biāo)識(shí)符serialVersionUID使用

    Java類的序列化版本唯一標(biāo)識(shí)符serialVersionUID使用

    serialVersionUID是一個(gè)類的序列化版本唯一標(biāo)識(shí)符,用于確保在反序列化過程中類的實(shí)例與序列化文件中的類版本相匹配,它在版本兼容性和安全性方面起著關(guān)鍵作用
    2025-01-01
  • java判斷字符串是正整數(shù)的實(shí)例

    java判斷字符串是正整數(shù)的實(shí)例

    今天小編就為大家分享一篇java判斷字符串是正整數(shù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • 如何寫好一個(gè)Spring組件的實(shí)現(xiàn)步驟

    如何寫好一個(gè)Spring組件的實(shí)現(xiàn)步驟

    這篇文章主要介紹了如何寫好一個(gè)Spring組件的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06

最新評(píng)論