最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文搞懂Mybatis的工作原理

 更新時(shí)間:2025年11月13日 09:30:00   作者:Calvad0s  
MyBatis和Spring的執(zhí)行過(guò)程包括啟動(dòng)階段和運(yùn)行階段,啟動(dòng)階段定義和解析配置文件,運(yùn)行階段讀取配置文件并實(shí)現(xiàn)功能,執(zhí)行SQL的邏輯包括代理類的生成、SQL的執(zhí)行、緩存和查詢數(shù)據(jù)庫(kù),本文給大家介紹Mybatis的工作原理,感興趣的朋友跟隨小編一起看看吧

無(wú)論是Mybatis也好,Spring也罷,它們的執(zhí)行過(guò)程無(wú)非可分為啟動(dòng)階段和運(yùn)行階段:

啟動(dòng)階段:

  1. 定義配置文件,如XML、注解
  2. 解析配置文件,將配置文件加載到內(nèi)存當(dāng)中

運(yùn)行階段:

  1. 讀取內(nèi)存中的配置文件,并根據(jù)配置文件實(shí)現(xiàn)對(duì)應(yīng)的功能

對(duì)于執(zhí)行SQL的邏輯來(lái)講,有如下步驟:

當(dāng)配置完成之后,假如說(shuō)我們要執(zhí)行一個(gè)下面一個(gè)sql,那么該如何執(zhí)行呢?

TestMapper testMapper = session.getMapper(TestMapper.class);
Test test = testMapper.findOne(1);

一、代理類的生成

首先 MyBatis 會(huì)根據(jù)我們傳入接口通過(guò) JDK 動(dòng)態(tài)代理,生成一個(gè)代理對(duì)象 TestMapper,生成邏輯如下所示:

public T newInstance(SqlSession sqlSession) {
    // mapperProxy 實(shí)現(xiàn)了 InvocationHandler 接口,用于 JDK 動(dòng)態(tài)代理
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
// 通過(guò) JDK 動(dòng)態(tài)代理生成對(duì)象
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
        new Class[]{ mapperInterface }, mapperProxy);
}

代理類的主要邏輯在 MapperProxy 中,而代理邏輯則是通過(guò) MapperMethod 完成的。

對(duì)于 MapperMethod 來(lái)說(shuō),它在創(chuàng)建的時(shí)候是需要讀取 XML 或者方法注解的配置項(xiàng),所以在使用的時(shí)候才能知道具體代理的方法的 SQL 內(nèi)容。同時(shí),這個(gè)類也會(huì)解析和記錄被代理方法的入?yún)⒑统鰠?,以方便?duì) SQL 的查詢占位符進(jìn)行替換,同時(shí)對(duì)查詢到的 SQL 結(jié)果進(jìn)行轉(zhuǎn)換。

二、執(zhí)行SQL

代理類生成之后,就可以執(zhí)行代理類的具體邏輯,也就是真正開始執(zhí)行用戶自定義的SQL邏輯了。

首先會(huì)進(jìn)入到 MapperMethod 核心的執(zhí)行邏輯,如下所示:

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;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // ...
    return result;
}

通過(guò)代碼我們可以很清晰地發(fā)現(xiàn),為什么 MyBatis 的 insert、update 和 delete 會(huì)返回行數(shù)的原因。業(yè)務(wù)處理上,我們經(jīng)常通過(guò) update = 1 來(lái)判斷當(dāng)前語(yǔ)句是否更新成功。

這里一共做了兩件事情,一件事情是通過(guò) BoundSql 將方法的入?yún)⑥D(zhuǎn)換為 SQL 需要的入?yún)⑿问剑诙虑榫褪峭ㄟ^(guò) SqlSession 來(lái)執(zhí)行對(duì)應(yīng)的 Sql。下面我們通過(guò) select 來(lái)舉例。

三、緩存

SqlSession 是 MyBatis 對(duì) SQL 執(zhí)行的封裝,真正的 SQL 處理邏輯要通過(guò) Executor 來(lái)執(zhí)行。Executor 有多個(gè)實(shí)現(xiàn)類,因?yàn)樵诓樵冎?,要?check 緩存是否存在,所以默認(rèn)使用的是 CachingExecutor 類,顧名思義,它的作用就是二級(jí)緩存。

CachingExecutor 的執(zhí)行邏輯如下所示:

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;
        }
    }
    // 若二級(jí)緩存為空,則重新查詢數(shù)據(jù)庫(kù)
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

二級(jí)緩存是和命名空間綁定的,如果多表操作的 SQL 的話,是會(huì)出現(xiàn)臟數(shù)據(jù)的。同時(shí)如果是不同的事務(wù),也可能引起臟讀,所以要慎重。

如果二級(jí)緩存沒(méi)有命中,則會(huì)進(jìn)入到 BaseExecutor 中繼續(xù)執(zhí)行,在這個(gè)過(guò)程中,會(huì)調(diào)用一級(jí)緩存執(zhí)行。

值得一提的是,在 MyBatis 中,緩存分為 PerpetualCache、BlockingCache、LruCache 等,這些 cache 的實(shí)現(xiàn)則是借用了裝飾者模式。一級(jí)緩存使用的是 PerpetualCache,里面是一個(gè)簡(jiǎn)單的 HashMap。一級(jí)緩存會(huì)在更新的時(shí)候,事務(wù)提交或者回滾的時(shí)候被清空。換句話說(shuō),一級(jí)緩存是和 SqlSession 綁定的。

四、查詢數(shù)據(jù)庫(kù)

如果一級(jí)緩存中沒(méi)有的話,則需要調(diào)用JDBC執(zhí)行真正的SQL邏輯。我們知道,在調(diào)用JDBC之前,是需要建立連接的,如下代碼所示:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

我們會(huì)發(fā)現(xiàn),Mybatis并不是直接從JDBC獲取連接的,通過(guò)數(shù)據(jù)源來(lái)獲取的,Mybatis默認(rèn)提供了是那種數(shù)據(jù)源:JNDI,PooledDataSource和UnpooledDataSource,我們也可以引入第三方數(shù)據(jù)源,如Druid等。包括驅(qū)動(dòng)等都是通過(guò)數(shù)據(jù)源獲取的。

獲取到Connection之后,還不夠,因?yàn)镴DBC的數(shù)據(jù)庫(kù)操作是需要Statement的,所以Mybatis專門抽象出來(lái)了 StatementHandler 處理類來(lái)專門處理和JDBC的交互,如下所示:

SimpleStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
}

其實(shí)這三代代碼就代表了Mybatis執(zhí)行SQL的核心邏輯:組裝SQL,執(zhí)行SQL,組裝結(jié)果。僅此而已。

具體Sql是如何組裝的呢?是通過(guò)BoundSql來(lái)完成的,具體組裝的邏輯大家可以從 org.apache.ibatis.mapping.MappedStatement#getBoundSql 中了解,這里不再贅述。

五、處理查詢結(jié)果

當(dāng)我們獲取到查詢結(jié)果之后,就需要對(duì)查詢結(jié)果進(jìn)行封裝,即把查詢到的數(shù)據(jù)庫(kù)字段映射為DO對(duì)象。

因?yàn)榇藭r(shí)我們已經(jīng)拿到了執(zhí)行結(jié)果ResultSet,同時(shí)我們也在應(yīng)用啟動(dòng)的時(shí)候在配置文件中配置了DO到數(shù)據(jù)庫(kù)字段的映射ResultMap,所以通過(guò)這兩個(gè)配置就可以轉(zhuǎn)換。核心的轉(zhuǎn)換邏輯是通過(guò)TypeHandler完成的,流程如下所示:

  1. 創(chuàng)建返回的實(shí)體類對(duì)象,如果該類是延遲加載,則先生成代理類
  2. 根據(jù)ResultMap中配置的數(shù)據(jù)庫(kù)字段,將該字段從ResultSet取出來(lái)
  3. 從ResultMap中獲取映射關(guān)系,如果沒(méi)有,則默認(rèn)將下劃線轉(zhuǎn)為駝峰式命名來(lái)映射
  4. 通過(guò)setter方法反射調(diào)用,將數(shù)據(jù)庫(kù)的值設(shè)置到實(shí)體類對(duì)象當(dāng)中

到此這篇關(guān)于一文搞懂Mybatis的工作原理的文章就介紹到這了,更多相關(guān)mybatis工作原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))

    java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))

    這篇文章主要介紹了java智能問(wèn)答圖靈機(jī)器人AI接口(聚合數(shù)據(jù)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • java中Ajax與Axios的使用小結(jié)

    java中Ajax與Axios的使用小結(jié)

    在項(xiàng)目中我們經(jīng)常會(huì)遇到需要向請(qǐng)求頭中添加消息的場(chǎng)景,本文主要介紹了java中Ajax與Axios的使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-02-02
  • JAVA如何使用Math類操作數(shù)據(jù)

    JAVA如何使用Math類操作數(shù)據(jù)

    這篇文章主要介紹了JAVA如何使用Math類操作數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 最新評(píng)論

    烟台市| 科技| 沂源县| 耒阳市| 丹东市| 米脂县| 蓬莱市| 安西县| 新兴县| 南京市| 汉沽区| 昌江| 分宜县| 浮山县| 若尔盖县| 梨树县| 定安县| 舟曲县| 安国市| 高密市| 宣汉县| 通海县| 肇东市| 慈溪市| 凤台县| 德化县| 台江县| 怀柔区| 昌江| 井陉县| 阜康市| 屯门区| 宁蒗| 济阳县| 商河县| 洪泽县| 泗阳县| 新平| 淳化县| 毕节市| 桓台县|