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

SpringBoot項(xiàng)目實(shí)現(xiàn)MyBatis流式查詢的教程詳解

 更新時(shí)間:2023年06月20日 10:37:42   作者:SZleoWang  
這篇文章主要介紹了SpringBoot項(xiàng)目如何實(shí)現(xiàn)MyBatis的流式查詢,mybatis的流式查詢,有點(diǎn)冷門,實(shí)際用的場景比較少,但是在某些特殊場景下,卻是十分有效的一個(gè)方法,感興趣的同學(xué)可以參考一下

前言

mybatis的流式查詢,有點(diǎn)冷門,實(shí)際用的場景比較少,但是在某些特殊場景下,卻是十分有效的一個(gè)方法。很多人沒有聽說過,實(shí)際上是對(duì)mybatis沒有太重視,對(duì)mybatis想法還停留一個(gè)dao接口對(duì)應(yīng)著mapper里的一個(gè)sql,mybatis的關(guān)鍵是如何寫好sql以及sql的優(yōu)化上;

其實(shí)mybatis遠(yuǎn)不止這些,通過這篇文章,和大家一塊來見識(shí)一下流式查詢,mybatis相對(duì)冷門的神秘面紗。文章的內(nèi)容將從以下幾個(gè)方面展開:

  • 什么是mybatis的流式查詢;

  • Cursor接口的主要方法;

  • 代碼層面如何實(shí)現(xiàn);

  • 具體的應(yīng)用場景;

  • 使用中的一些注意事項(xiàng);

環(huán)境 配置

  • jdk版本:1.8

  • 開發(fā)工具:Intellij iDEA 2020.1

  • springboot:2.3.9.RELEASE

  • mybatis-spring-boot-starter:2.1.4

什么是mybatis流式查詢?

使用mybatis作為持久層的框架時(shí),通過mybatis執(zhí)行查詢數(shù)據(jù)的請求執(zhí)行成功后,mybatis返回的結(jié)果集不是一個(gè)集合或?qū)ο?,而是一個(gè)迭代器,可以通過遍歷迭代器來取出結(jié)果集,避免一次性取出大量的數(shù)據(jù)而占用太多的內(nèi)存。

Cursor

org.apache.ibatis.cursor.Cursor接口有三個(gè)抽象方法,分別是

  • isOpen() :判斷cursor是否正處于打開狀態(tài);

  • isConsumed() :判斷查詢結(jié)果是否全部讀取完;

  • getCurrentIndex() :查詢已讀取數(shù)據(jù)在全部數(shù)據(jù)里的索引位置;

public interface Cursor<T> extends Closeable, Iterable<T> {
 //判斷cursor是否正處于打開狀態(tài)
 //當(dāng)返回true,則表示cursor已經(jīng)開始從數(shù)據(jù)庫里刷新數(shù)據(jù)了;
  boolean isOpen();
  //判斷查詢結(jié)果是否全部讀取完;
  //當(dāng)返回true,則表示查詢sql匹配的全部數(shù)據(jù)都消費(fèi)完了;
  boolean isConsumed();
   //查詢已讀取數(shù)據(jù)在全部數(shù)據(jù)里的索引位置;
   //第一條數(shù)據(jù)的索引位置為0;當(dāng)返回索引位置為-1時(shí),則表示已經(jīng)沒有數(shù)據(jù)可以讀取;
  int getCurrentIndex();
}

代碼實(shí)現(xiàn)

mybatis的所謂流式查詢,就是服務(wù)端程序查詢數(shù)據(jù)的過程中,與遠(yuǎn)程數(shù)據(jù)庫一直保持連接,不斷的去數(shù)據(jù)庫拉取數(shù)據(jù),提交事務(wù)并關(guān)閉sqlsession后,數(shù)據(jù)庫連接斷開,停止數(shù)據(jù)拉取,需要注意的是使用這種方式,需要自己手動(dòng)維護(hù)sqlsession和事務(wù)的提交。

1、實(shí)現(xiàn)方式很簡單,原來返回的類型是集合或?qū)ο?,流式查詢返回的的類型Curor,泛型內(nèi)表示實(shí)際的類型,其他沒有變化;

@Mapper
public interface PersonDao {
    Cursor<Person> selectByCursor();
    Integer queryCount();
}
<select id="selectByCursor" resultMap="personMap">
    select * from sys_person order by id desc
</select>
<select id="queryCount" resultType="java.lang.Integer">
    select count(*) from sys_person
</select>

2、dao層向service層返回的是Cursor類型對(duì)象,只要不提交關(guān)閉sqlsession,服務(wù)端程序就可以一直從數(shù)據(jù)數(shù)據(jù)庫讀取數(shù)據(jù),直到查詢sql匹配到數(shù)據(jù)全部讀取完;

示例里的主要業(yè)務(wù)邏輯是:從sys_person表中讀取所有的人員信息數(shù)據(jù),然后按照每1000條數(shù)據(jù)為一組,讀取到內(nèi)存里進(jìn)行處理,以此類推,直到查詢sql匹配到數(shù)據(jù)全部處理完,再提交事務(wù),關(guān)閉sqlSession;

@Service
@Slf4j
public class PersonServiceImpl implements IPersonService {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    @Override
    public void getOneByAsync() throws InterruptedException {
        new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                //使用sqlSessionFactory打開一個(gè)sqlSession,在沒有讀取完數(shù)據(jù)之前不要提交事務(wù)或關(guān)閉sqlSession
                log.info("----開啟sqlSession");
                SqlSession sqlSession = sqlSessionFactory.openSession();
                 try {
                     //獲取到指定mapper
                     PersonDao mapper = sqlSession.getMapper(PersonDao.class);
                     //調(diào)用指定mapper的方法,返回一個(gè)cursor
                     Cursor<Person> cursor = mapper.selectByCursor();
                     //查詢數(shù)據(jù)總量
                     Integer total = mapper.queryCount();
                     //定義一個(gè)list,用來從cursor中讀取數(shù)據(jù),每讀取夠1000條的時(shí)候,開始處理這批數(shù)據(jù);
                     //當(dāng)前批數(shù)據(jù)處理完之后,清空list,準(zhǔn)備接收下一批次數(shù)據(jù);直到大量的數(shù)據(jù)全部處理完;
                     List<Person> personList = new ArrayList<>();
                     int i = 0;
                     if (cursor != null) {
                         for (Person person : cursor) {
                             if (personList.size() < 1000) {
//                            log.info("----id:{},userName:{}", person.getId(), person.getUserName());
                                 personList.add(person);
                             } else if (personList.size() == 1000) {
                                 ++i;
                                 log.info("----{}、從cursor取數(shù)據(jù)達(dá)到1000條,開始處理數(shù)據(jù)", i);
                                 log.info("----處理數(shù)據(jù)中...");
                                 Thread.sleep(1000);//休眠1s模擬處理數(shù)據(jù)需要消耗的時(shí)間;
                                 log.info("----{}、從cursor中取出的1000條數(shù)據(jù)已經(jīng)處理完畢", i);
                                 personList.clear();
                                 personList.add(person);
                             }
                             if (total == (cursor.getCurrentIndex() + 1)) {
                                 ++i;
                                 log.info("----{}、從cursor取數(shù)據(jù)達(dá)到1000條,開始處理數(shù)據(jù)", i);
                                 log.info("----處理數(shù)據(jù)中...");
                                 Thread.sleep(1000);//休眠1s模擬處理數(shù)據(jù)需要消耗的時(shí)間;
                                 log.info("----{}、從cursor中取出的1000條數(shù)據(jù)已經(jīng)處理完畢", i);
                                 personList.clear();
                             }
                         }
                         if (cursor.isConsumed()) {
                             log.info("----查詢sql匹配中的數(shù)據(jù)已經(jīng)消費(fèi)完畢!");
                         }
                     }
                     sqlSession.commit();
                     log.info("----提交事務(wù)");
                 }catch (Exception e){
                     e.printStackTrace();
                     sqlSession.rollback();
                 }
                 finally {
                     if (sqlSession != null) {
                         //全部數(shù)據(jù)讀取并且做好其他業(yè)務(wù)操作之后,提交事務(wù)并關(guān)閉連接;
                         sqlSession.close();
                         log.info("----關(guān)閉sqlSession");  
                     }
                 }
            }
        }).start();
    }
}

應(yīng)用場景

其實(shí)mybatis的流式查詢適用范圍很有限,這里舉個(gè)例子,假如有這樣一個(gè)需求 :有50萬員工的一年的工資數(shù)據(jù)明細(xì),需要輸出一張公司支出工資的數(shù)據(jù)報(bào)表。

需求很簡單,估計(jì)有人是這樣想:這太簡單了,查詢出員工的工資數(shù)據(jù)明細(xì),然后按照套上公式逐條計(jì)算出結(jié)果,然后匯總計(jì)算結(jié)果,插入到新的結(jié)果表里不就行了。事實(shí)上這件事絕對(duì)不簡單:

  • 50萬的數(shù)據(jù)全部讀取到j(luò)vm的內(nèi)存里得占用多大空間?

  • 這么多對(duì)象的垃圾回收又需要多久?

  • 這么多數(shù)據(jù)計(jì)算是高頻行為還是低步行為?

  • 如果計(jì)算到某條員工的數(shù)據(jù)發(fā)生異常,已經(jīng)計(jì)算好的數(shù)據(jù)要不要全部回滾?...

總之,直接取出50萬數(shù)據(jù)來計(jì)算,風(fēng)險(xiǎn)肯定不小。那怎么辦呢?

在實(shí)際的開發(fā)中,也經(jīng)常遇到一些百十萬,說大不大,說小不小的數(shù)據(jù)報(bào)表處理,我的主要設(shè)計(jì)思路通常就是數(shù)據(jù)切隔+異步,具體怎么做呢?結(jié)合上面的例子,是這樣的:

1、按照月份、省份或者部門,對(duì)工資明細(xì)數(shù)據(jù)進(jìn)行數(shù)據(jù)切隔分組;

2、把不同月份、省份、部門的工資數(shù)據(jù)包裝成多線程任務(wù),放到線程池中去執(zhí)行;

3、根據(jù)切隔的多線程任務(wù)數(shù)量,定義一個(gè)同步工具類CountDownLatch;

4、根據(jù)同步工具類CountDownLatch,來判斷所有的多線程任務(wù)是否全部執(zhí)行完;等到所有的多線程任務(wù)全部執(zhí)行完成后,再執(zhí)行匯總的邏輯;

5、在多線程任務(wù)里,查詢具體月份、省份的員工工資數(shù)據(jù)明細(xì)的時(shí)候,如果數(shù)據(jù)量還是不少,就可以使用mybatis的流式查詢,分批獲取員工工資明細(xì)數(shù)據(jù),進(jìn)行當(dāng)前批的計(jì)算、匯總,然后所有分批數(shù)據(jù)都計(jì)算完成后,再匯總所有分批數(shù)據(jù);

注意事項(xiàng)

mybatis的流式查詢的本意,是避免大量數(shù)據(jù)的查詢而導(dǎo)致內(nèi)存溢出,因此dao層查詢返回的是一個(gè)迭代器(Cursor),可以每次從迭代器中取出一條查詢結(jié)果,在實(shí)際業(yè)務(wù)開發(fā)過程中,即是根據(jù)實(shí)際的jvm內(nèi)存大小,從迭代器中取出一定數(shù)量的數(shù)據(jù)后,再進(jìn)行數(shù)據(jù)處理,待處理完之后,繼續(xù)取出一定數(shù)據(jù)再處理,以此類推直到全部數(shù)據(jù)處理完,這樣做的最大好處就是能夠降低內(nèi)存使用和垃圾回收器的負(fù)擔(dān),使數(shù)據(jù)處理的過程相對(duì)更加高效、可控,內(nèi)存溢出的風(fēng)險(xiǎn)較??;

好處很明顯,缺點(diǎn)也很就明顯,處理的時(shí)間可能會(huì)變長,需要引入多線程異步操作,并且在迭代器遍歷和數(shù)據(jù)處理的過程中,數(shù)據(jù)庫連接不能斷開,即當(dāng)前sqlSession要保持持續(xù)打開狀態(tài),一量斷開,數(shù)據(jù)讀取就會(huì)中斷,所以關(guān)于這塊的處理,使用mybatis原生的sqlSession進(jìn)行手動(dòng)查詢、提交事務(wù)、回滾和關(guān)閉sqlSession最為穩(wěn)妥、最簡單

以上就是SpringBoot項(xiàng)目實(shí)現(xiàn)MyBatis流式查詢的教程詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot MyBatis流式查詢的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java設(shè)計(jì)模式之java訪問者模式詳解

    Java設(shè)計(jì)模式之java訪問者模式詳解

    這篇文章主要介紹了JAVA設(shè)計(jì)模式之訪問者模式,簡單說明了訪問者模式的原理,并結(jié)合實(shí)例分析了java訪問者模式的定義與用法,需要的朋友可以參考下
    2021-09-09
  • Java9垃圾回收方法finalize() 原理解析

    Java9垃圾回收方法finalize() 原理解析

    這篇文章主要介紹了Java9垃圾回收方法finalize() 原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • mybatis-plus條件構(gòu)造器的操作代碼

    mybatis-plus條件構(gòu)造器的操作代碼

    mybatis-plus提供了AbstractWrapper抽象類,提供了很多sql語法支持的方法,比如模糊查詢,比較,區(qū)間,分組查詢,排序,判斷空,子查詢等等,方便我們用面向?qū)ο蟮姆绞饺?shí)現(xiàn)sql語句,本文重點(diǎn)給大家介紹mybatis-plus條件構(gòu)造器的操作代碼,感興趣的朋友一起看看吧
    2022-03-03
  • ByteArrayOutputStream簡介和使用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    ByteArrayOutputStream簡介和使用_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    ByteArrayOutputStream 是字節(jié)數(shù)組輸出流。它繼承于OutputStream。這篇文章主要介紹了ByteArrayOutputStream簡介和使用,需要的朋友可以參考下
    2017-05-05
  • nas實(shí)現(xiàn)java開發(fā)的環(huán)境詳解

    nas實(shí)現(xiàn)java開發(fā)的環(huán)境詳解

    這篇文章主要為大家介紹了nas實(shí)現(xiàn)java開發(fā)的環(huán)境詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • SpringBoot登錄驗(yàn)證token攔截器的實(shí)現(xiàn)

    SpringBoot登錄驗(yàn)證token攔截器的實(shí)現(xiàn)

    本文主要介紹了SpringBoot登錄驗(yàn)證token攔截器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 解決spring?security?loginProcessingUrl無效問題

    解決spring?security?loginProcessingUrl無效問題

    這篇文章主要介紹了解決spring?security?loginProcessingUrl無效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • mybatis實(shí)現(xiàn)遍歷Map的key和value

    mybatis實(shí)現(xiàn)遍歷Map的key和value

    這篇文章主要介紹了mybatis實(shí)現(xiàn)遍歷Map的key和value方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 詳解Java 集合類 List 的那些坑

    詳解Java 集合類 List 的那些坑

    這篇文章主要介紹了Java 集合類 List 的那些坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 通過實(shí)例了解java checked和unchecked異常

    通過實(shí)例了解java checked和unchecked異常

    這篇文章主要介紹了通過實(shí)例了解checked和unchecked異常,Java異常分為兩種類型,checked異常和unchecked異常,另一種叫法是異常和錯(cuò)誤。下面小編就帶大家來一起學(xué)習(xí)一下吧
    2019-06-06

最新評(píng)論