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

Mybatis的Cursor避免OOM異常的方法詳解

 更新時間:2024年06月27日 08:53:07   作者:Azir12138  
在Mybatis中,有一個特殊的對象Cursor,這個對象的注釋上清晰的說明了,這個類的用途,在Mybatis中使用Cursor非常簡單,只要在Mapper文件中將方法的返回值設置成Cursor<T>即可,本文給大家介紹了Mybatis的Cursor避免OOM異常的方法,需要的朋友可以參考下

Cursor是啥

研究Cursor如何避免OOM異常之前,先了解一下Cursor是啥。
在Mybatis中,有一個特殊的對象Cursor,這個對象的注釋上清晰的說明了,這個類的用途。

/**
 * Cursor contract to handle fetching items lazily using an Iterator.
 * Cursors are a perfect fit to handle millions of items queries that would not normally fits in memory.
 * If you use collections in resultMaps then cursor SQL queries must be ordered (resultOrdered="true")
 * using the id columns of the resultMap.
 *
 * @author Guillaume Darmont / guillaume@dropinocean.com
 */

Cursors are a perfect fit to handle millions of items queries that would not normally fits in memory. Cursor非常適合處理通常不適合內存的數百萬項查詢

甚至在說明中還著重的說明了是非常適合的。
這個類的作用其實就是為了避免在數據庫批量查詢到大數據時導致程序OOM錯誤。

如何使用Cursor

Mybatis中使用Cursor非常簡單,只要在Mapper文件中將方法的返回值設置成Cursor<T>即可。

@Select("SELECT * FROM log")
Cursor<Log> selectAll();

注意:要是想在SpringBoot中使用Cursor的話,需要下面方式二選一,不然的話使用Cursor會報錯。

  • 手動創(chuàng)建SqlSession
  • 在調用Mapper方法的方法上標注@Transactional事務注解。

之所以需要額外配置是因為在SpringBoot中,Mybatis的SqlSession生命周期只在Mapper方法中,并且在關閉SqlSession時,還會將SqlSession**綁定的Cursor關閉,**所以就需要延長SqlSession的存活時間了。

Cursor原理

解析Mapper方法返回值

在Mybatis中,調用Mapper方法時,會由MapperProxy進行方法的代理。此時就會根據具體的方法進行不同的解析

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
    // 解析方法返回值
    Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
    if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
    } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
    } else {
        this.returnType = method.getReturnType();
    }
    this.returnsVoid = void.class.equals(this.returnType);
    this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
    // 方法是否返回Cursor類型
    this.returnsCursor = Cursor.class.equals(this.returnType);
    this.returnsOptional = Optional.class.equals(this.returnType);
    this.mapKey = getMapKey(method);
    this.returnsMap = this.mapKey != null;
    this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
    this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    this.paramNameResolver = new ParamNameResolver(configuration, method);
}

根據Cursor返回值調用selectCursor

解析Mapper方法得到返回值后,就會根據返回值的類型來決定具體調用的查詢方法。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
    // ---------- 其他查詢----------------
        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()) {
                // Cursor返回類型
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
    // ---------- 其他查詢----------------
    return result;
}

構建statement

使用上面解析Mapper方法后得到的Sql,從數據庫鏈接中創(chuàng)建一個PreparedStatement并填充對應的參數值。

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

封裝Cursor

在調用的最后,會將從數據庫得到的ResultSet以及Mybatis內部ResultSetHandler封裝成Cursor對象供用戶使用。

public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
    
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    if (resultMapCount != 1) {
        throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
    }
    
    ResultMap resultMap = resultMaps.get(0);
    return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
}

為啥能避免內存溢出

在討論這個問題前,我們可以看一下在Mybatis中,Cursor返回值的查詢以及批量查詢的實際調用邏輯。

Cursor查詢

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

批量查詢

  @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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
          boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

可以對比一下兩個實際執(zhí)行的方法,比較明顯的區(qū)別就是在批量搜索中,顯式關閉了打開的Statement,而在Cursor查詢中,并沒有關閉與數據庫的連接。歸根結底就是因為Cursor在使用上就是在操作原生的Statement,故不能在查詢后關閉。
另外,在批量查詢的handler.query(stmt, resultHandler)方法中,是獲取本次查詢所有數據后返回的,而這就會導致在大批量數據時塞爆內存導致OOM了。
然而在Cursor查詢中,并不會獲取全部數據后返回,而是根據用戶操作來獲取對于數據,自然而然也就不會塞爆內存了。

總結

類型如何獲取數據返回值是否關閉Statement、ResultSet
Cursor用戶自行根據Cursor迭代器獲取Cursor游標類型不關閉,需要一直持有
普通搜索Mybatis內部操控JDBC指針,獲取所有查詢數據后返回。具體實體類型獲取完數據后關閉

以上就是Mybatis的Cursor避免OOM異常的方法詳解的詳細內容,更多關于Mybatis Cursor避免OOM異常的資料請關注腳本之家其它相關文章!

相關文章

  • java 學習筆記(入門篇)_java的安裝與配置

    java 學習筆記(入門篇)_java的安裝與配置

    學習Java已經很長時間了,由于基礎不好遇到問題就無從下手,所以,打算寫Java的隨手筆記來鞏固基礎,加強學習,接下來講解java的安裝,配置等,感興趣的朋友可以參考下
    2013-01-01
  • Eclipse 安裝 SVN 在線插件教程

    Eclipse 安裝 SVN 在線插件教程

    這篇文章主要介紹了Eclipse 安裝 SVN 在線插件教程的相關資料,這里對安裝步驟進行了詳細介紹,需要的朋友可以參考下
    2016-11-11
  • Spring的@CrossOrigin注解使用與CrossFilter對象自定義詳解

    Spring的@CrossOrigin注解使用與CrossFilter對象自定義詳解

    這篇文章主要介紹了Spring的@CrossOrigin注解使用與CrossFilter對象自定義詳解,跨域,指的是瀏覽器不能執(zhí)行其他網站的腳本,它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制,所謂同源是指,域名,協議,端口均相同,需要的朋友可以參考下
    2023-12-12
  • java 遞歸查詢所有子節(jié)點id的方法實現

    java 遞歸查詢所有子節(jié)點id的方法實現

    在多層次的數據結構中,經常需要查詢一個節(jié)點下的所有子節(jié)點,本文主要介紹了java 遞歸查詢所有子節(jié)點id的方法實現,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Flink實現特定統(tǒng)計的歸約聚合reduce操作

    Flink實現特定統(tǒng)計的歸約聚合reduce操作

    這篇文章主要介紹了Flink實現特定統(tǒng)計的歸約聚合reduce操作,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-02-02
  • 小米Java程序員第二輪面試10個問題 你是否會被刷掉?

    小米Java程序員第二輪面試10個問題 你是否會被刷掉?

    小米Java程序員第二輪面試10個問題,你是否會被刷掉?掌握好基礎知識,祝大家面試順利
    2017-11-11
  • Java多線程并發(fā)synchronized?關鍵字

    Java多線程并發(fā)synchronized?關鍵字

    這篇文章主要介紹了Java多線程并發(fā)synchronized?關鍵字,Java?在虛擬機層面提供了?synchronized?關鍵字供開發(fā)者快速實現互斥同步的重量級鎖來保障線程安全。
    2022-06-06
  • 詳解SpringBoot構建的Web項目如何在服務端校驗表單輸入

    詳解SpringBoot構建的Web項目如何在服務端校驗表單輸入

    這篇文章主要介紹了詳解SpringBoot構建的Web項目如何在服務端校驗表單輸入,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • Java使用J4L識別驗證碼的操作方法

    Java使用J4L識別驗證碼的操作方法

    這篇文章主要介紹了Java使用J4L識別驗證碼的操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-02-02
  • Spring依賴注入與第三方Bean管理基礎詳解

    Spring依賴注入與第三方Bean管理基礎詳解

    依賴注入(Dependency Injection)和控制反轉(Inversion of Control)是同一個概念。具體含義是:當某個角色(可能是一個Java實例,調用者)需要另一個角色(另一個Java實例,被調用者)的協助時,在 傳統(tǒng)的程序設計過程中,通常由調用者來創(chuàng)建被調用者的實例
    2022-12-12

最新評論