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

一文搞懂Mybatis的工作原理

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

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

啟動階段:

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

運(yùn)行階段:

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

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

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

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

一、代理類的生成

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

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

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

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

二、執(zhí)行SQL

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

首先會進(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;
}

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

這里一共做了兩件事情,一件事情是通過 BoundSql 將方法的入?yún)⑥D(zhuǎn)換為 SQL 需要的入?yún)⑿问?,第二件事情就是通過 SqlSession 來執(zhí)行對應(yīng)的 Sql。下面我們通過 select 來舉例。

三、緩存

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

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

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

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

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

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

如果一級緩存中沒有的話,則需要調(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;
}

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

獲取到Connection之后,還不夠,因?yàn)镴DBC的數(shù)據(jù)庫操作是需要Statement的,所以Mybatis專門抽象出來了 StatementHandler 處理類來專門處理和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是如何組裝的呢?是通過BoundSql來完成的,具體組裝的邏輯大家可以從 org.apache.ibatis.mapping.MappedStatement#getBoundSql 中了解,這里不再贅述。

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

    淮北市| 新建县| 青海省| 宁德市| 蚌埠市| 陕西省| 长武县| 清新县| 壤塘县| 丹阳市| 揭西县| 榆树市| 徐闻县| 老河口市| 正安县| 阜城县| 武宁县| 阿克苏市| 灵武市| 长顺县| 靖州| 咸阳市| 科技| 黄龙县| 松江区| 兴业县| 洞头县| 正镶白旗| 丁青县| 都江堰市| 灵台县| 安西县| 桂林市| 泸州市| 浦城县| 西丰县| 疏附县| 绥德县| 东平县| 辽宁省| 长海县|